// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/wm/overview/scoped_transform_overview_window.h"

#include "ash/screen_util.h"
#include "ash/shell_window_ids.h"
#include "ash/wm/overview/scoped_window_copy.h"
#include "ash/wm/overview/window_selector_item.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/window.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/animation/tween.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/window_animations.h"
#include "ui/wm/core/window_util.h"

namespace ash {

namespace {

// The animation settings used for window selector animations.
class WindowSelectorAnimationSettings
    : public ui::ScopedLayerAnimationSettings {
 public:
  WindowSelectorAnimationSettings(aura::Window* window) :
      ui::ScopedLayerAnimationSettings(window->layer()->GetAnimator()) {
    SetPreemptionStrategy(
        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
    SetTransitionDuration(base::TimeDelta::FromMilliseconds(
        ScopedTransformOverviewWindow::kTransitionMilliseconds));
    SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
  }

  virtual ~WindowSelectorAnimationSettings() {
  }
};

void SetTransformOnWindow(aura::Window* window,
                          const gfx::Transform& transform,
                          bool animate) {
  if (animate) {
    WindowSelectorAnimationSettings animation_settings(window);
    window->SetTransform(transform);
  } else {
    window->SetTransform(transform);
  }
}

gfx::Transform TranslateTransformOrigin(const gfx::Vector2d& new_origin,
                                        const gfx::Transform& transform) {
  gfx::Transform result;
  result.Translate(-new_origin.x(), -new_origin.y());
  result.PreconcatTransform(transform);
  result.Translate(new_origin.x(), new_origin.y());
  return result;
}

void SetTransformOnWindowAndAllTransientChildren(
    aura::Window* window,
    const gfx::Transform& transform,
    bool animate) {
  SetTransformOnWindow(window, transform, animate);

  aura::Window::Windows transient_children =
      ::wm::GetTransientChildren(window);
  for (aura::Window::Windows::iterator iter = transient_children.begin();
       iter != transient_children.end(); ++iter) {
    aura::Window* transient_child = *iter;
    gfx::Rect window_bounds = window->bounds();
    gfx::Rect child_bounds = transient_child->bounds();
    gfx::Transform transient_window_transform(
        TranslateTransformOrigin(child_bounds.origin() - window_bounds.origin(),
                                 transform));
    SetTransformOnWindow(transient_child, transient_window_transform, animate);
  }
}

aura::Window* GetModalTransientParent(aura::Window* window) {
  if (window->GetProperty(aura::client::kModalKey) == ui::MODAL_TYPE_WINDOW)
    return ::wm::GetTransientParent(window);
  return NULL;
}

}  // namespace

const int ScopedTransformOverviewWindow::kTransitionMilliseconds = 200;

ScopedTransformOverviewWindow::ScopedTransformOverviewWindow(
        aura::Window* window)
    : window_(window),
      minimized_(window->GetProperty(aura::client::kShowStateKey) ==
                 ui::SHOW_STATE_MINIMIZED),
      ignored_by_shelf_(ash::wm::GetWindowState(window)->ignored_by_shelf()),
      overview_started_(false),
      original_transform_(window->layer()->GetTargetTransform()) {
}

ScopedTransformOverviewWindow::~ScopedTransformOverviewWindow() {
  if (window_) {
    WindowSelectorAnimationSettings animation_settings(window_);
    gfx::Transform transform;
    SetTransformOnWindowAndTransientChildren(original_transform_, true);
    if (minimized_ && window_->GetProperty(aura::client::kShowStateKey) !=
        ui::SHOW_STATE_MINIMIZED) {
      // Setting opacity 0 and visible false ensures that the property change
      // to SHOW_STATE_MINIMIZED will not animate the window from its original
      // bounds to the minimized position.
      // Hiding the window needs to be done before the target opacity is 0,
      // otherwise the layer's visibility will not be updated
      // (See VisibilityController::UpdateLayerVisibility).
      window_->Hide();
      window_->layer()->SetOpacity(0);
      window_->SetProperty(aura::client::kShowStateKey,
                           ui::SHOW_STATE_MINIMIZED);
    }
    ash::wm::GetWindowState(window_)->set_ignored_by_shelf(ignored_by_shelf_);
  }
}

bool ScopedTransformOverviewWindow::Contains(const aura::Window* target) const {
  for (ScopedVector<ScopedWindowCopy>::const_iterator iter =
      window_copies_.begin(); iter != window_copies_.end(); ++iter) {
    if ((*iter)->GetWindow()->Contains(target))
      return true;
  }
  aura::Window* window = window_;
  while (window) {
    if (window->Contains(target))
      return true;
    window = GetModalTransientParent(window);
  }
  return false;
}

gfx::Rect ScopedTransformOverviewWindow::GetBoundsInScreen() const {
  gfx::Rect bounds;
  aura::Window* window = window_;
  while (window) {
    bounds.Union(ScreenUtil::ConvertRectToScreen(window->parent(),
                                                window->GetTargetBounds()));
    window = GetModalTransientParent(window);
  }
  return bounds;
}

void ScopedTransformOverviewWindow::RestoreWindow() {
  if (minimized_ && window_->GetProperty(aura::client::kShowStateKey) ==
      ui::SHOW_STATE_MINIMIZED) {
    window_->Show();
  }
}

void ScopedTransformOverviewWindow::RestoreWindowOnExit() {
  minimized_ = false;
  original_transform_ = gfx::Transform();
}

void ScopedTransformOverviewWindow::OnWindowDestroyed() {
  window_ = NULL;
}

gfx::Rect ScopedTransformOverviewWindow::ShrinkRectToFitPreservingAspectRatio(
    const gfx::Rect& rect,
    const gfx::Rect& bounds) {
  DCHECK(!rect.IsEmpty());
  DCHECK(!bounds.IsEmpty());
  float scale = std::min(1.0f,
      std::min(static_cast<float>(bounds.width()) / rect.width(),
               static_cast<float>(bounds.height()) / rect.height()));
  return gfx::Rect(bounds.x() + 0.5 * (bounds.width() - scale * rect.width()),
                   bounds.y() + 0.5 * (bounds.height() - scale * rect.height()),
                   rect.width() * scale,
                   rect.height() * scale);
}

gfx::Transform ScopedTransformOverviewWindow::GetTransformForRect(
    const gfx::Rect& src_rect,
    const gfx::Rect& dst_rect) {
  DCHECK(!src_rect.IsEmpty());
  DCHECK(!dst_rect.IsEmpty());
  gfx::Transform transform;
  transform.Translate(dst_rect.x() - src_rect.x(),
                      dst_rect.y() - src_rect.y());
  transform.Scale(static_cast<float>(dst_rect.width()) / src_rect.width(),
                  static_cast<float>(dst_rect.height()) / src_rect.height());
  return transform;
}

void ScopedTransformOverviewWindow::SetTransform(
    aura::Window* root_window,
    const gfx::Transform& transform,
    bool animate) {
  DCHECK(overview_started_);

  if (root_window != window_->GetRootWindow()) {
    if (!window_copies_.empty()) {
      bool bounds_or_hierarchy_changed = false;
      aura::Window* window = window_;
      for (ScopedVector<ScopedWindowCopy>::reverse_iterator iter =
               window_copies_.rbegin();
           !bounds_or_hierarchy_changed && iter != window_copies_.rend();
           ++iter, window = GetModalTransientParent(window)) {
        if (!window) {
          bounds_or_hierarchy_changed = true;
        } else if ((*iter)->GetWindow()->GetBoundsInScreen() !=
                window->GetBoundsInScreen()) {
          bounds_or_hierarchy_changed = true;
        }
      }
      // Clearing the window copies array will force it to be recreated.
      // TODO(flackr): If only the position changed and not the size,
      // update the existing window copy's position and continue to use it.
      if (bounds_or_hierarchy_changed)
        window_copies_.clear();
    }
    if (window_copies_.empty()) {
      // TODO(flackr): Create copies of the transient children windows as well.
      // Currently they will only be visible on the window's initial display.
      CopyWindowAndTransientParents(root_window, window_);
    }
  }
  SetTransformOnWindowAndTransientChildren(transform, animate);
}

void ScopedTransformOverviewWindow::CopyWindowAndTransientParents(
    aura::Window* target_root,
    aura::Window* window) {
  aura::Window* modal_parent = GetModalTransientParent(window);
  if (modal_parent)
    CopyWindowAndTransientParents(target_root, modal_parent);
  window_copies_.push_back(new ScopedWindowCopy(target_root, window));
}

void ScopedTransformOverviewWindow::SetTransformOnWindowAndTransientChildren(
    const gfx::Transform& transform,
    bool animate) {
  gfx::Point origin(GetBoundsInScreen().origin());
  aura::Window* window = window_;
  while (::wm::GetTransientParent(window))
    window = ::wm::GetTransientParent(window);
  for (ScopedVector<ScopedWindowCopy>::const_iterator iter =
      window_copies_.begin(); iter != window_copies_.end(); ++iter) {
    SetTransformOnWindow(
        (*iter)->GetWindow(),
        TranslateTransformOrigin(ScreenUtil::ConvertRectToScreen(
            (*iter)->GetWindow()->parent(),
            (*iter)->GetWindow()->GetTargetBounds()).origin() - origin,
            transform),
        animate);
  }
  SetTransformOnWindowAndAllTransientChildren(
      window,
      TranslateTransformOrigin(ScreenUtil::ConvertRectToScreen(
          window->parent(), window->GetTargetBounds()).origin() - origin,
          transform),
      animate);
}

void ScopedTransformOverviewWindow::PrepareForOverview() {
  DCHECK(!overview_started_);
  overview_started_ = true;
  ash::wm::GetWindowState(window_)->set_ignored_by_shelf(true);
  RestoreWindow();
}

}  // namespace ash
