// Copyright (c) 2012 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/window_animations.h"

#include <math.h>

#include <algorithm>
#include <vector>

#include "ash/ash_switches.h"
#include "ash/launcher/launcher.h"
#include "ash/screen_ash.h"
#include "ash/shell.h"
#include "ash/wm/window_util.h"
#include "ash/wm/workspace_controller.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/stl_util.h"
#include "base/time.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#include "ui/aura/window_property.h"
#include "ui/compositor/compositor_observer.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/interpolated_transform.h"
#include "ui/gfx/screen.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"

DECLARE_WINDOW_PROPERTY_TYPE(int)
DECLARE_WINDOW_PROPERTY_TYPE(ash::WindowVisibilityAnimationType)
DECLARE_WINDOW_PROPERTY_TYPE(ash::WindowVisibilityAnimationTransition)
DECLARE_WINDOW_PROPERTY_TYPE(float)

using aura::Window;
using base::TimeDelta;
using ui::Layer;

namespace ash {
namespace internal {
namespace {
const float kWindowAnimation_Vertical_TranslateY = 15.f;

}

DEFINE_WINDOW_PROPERTY_KEY(WindowVisibilityAnimationType,
                           kWindowVisibilityAnimationTypeKey,
                           WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT);
DEFINE_WINDOW_PROPERTY_KEY(int, kWindowVisibilityAnimationDurationKey, 0);
DEFINE_WINDOW_PROPERTY_KEY(WindowVisibilityAnimationTransition,
                           kWindowVisibilityAnimationTransitionKey,
                           ANIMATE_BOTH);
DEFINE_WINDOW_PROPERTY_KEY(float,
                           kWindowVisibilityAnimationVerticalPositionKey,
                           kWindowAnimation_Vertical_TranslateY);

namespace {

const int kDefaultAnimationDurationForMenuMS = 150;
const int kLayerAnimationsForMinimizeDurationMS = 200;

// Durations for the cross-fade animation, in milliseconds.
const float kCrossFadeDurationMinMs = 100.f;
const float kCrossFadeDurationMaxMs = 400.f;

// Durations for the brightness/grayscale fade animation, in milliseconds.
const int kBrightnessGrayscaleFadeDurationMs = 1000;

// Brightness/grayscale values for hide/show window animations.
const float kWindowAnimation_HideBrightnessGrayscale = 1.f;
const float kWindowAnimation_ShowBrightnessGrayscale = 0.f;

const float kWindowAnimation_HideOpacity = 0.f;
const float kWindowAnimation_ShowOpacity = 1.f;
const float kWindowAnimation_TranslateFactor = 0.025f;
const float kWindowAnimation_ScaleFactor = .95f;
// TODO(sky): if we end up sticking with 0, nuke the code doing the rotation.
const float kWindowAnimation_MinimizeRotate = 0.f;

// Amount windows are scaled during workspace animations.
const float kWorkspaceScale = .95f;

int64 Round64(float f) {
  return static_cast<int64>(f + 0.5f);
}

base::TimeDelta GetWindowVisibilityAnimationDuration(aura::Window* window) {
  int duration =
      window->GetProperty(kWindowVisibilityAnimationDurationKey);
  if (duration == 0 && window->type() == aura::client::WINDOW_TYPE_MENU) {
    return base::TimeDelta::FromMilliseconds(
        kDefaultAnimationDurationForMenuMS);
  }
  return TimeDelta::FromInternalValue(duration);
}

bool HasWindowVisibilityAnimationTransition(
    aura::Window* window,
    WindowVisibilityAnimationTransition transition) {
  WindowVisibilityAnimationTransition prop = window->GetProperty(
      kWindowVisibilityAnimationTransitionKey);
  return (prop & transition) != 0;
}

// Gets/sets the WindowVisibilityAnimationType associated with a window.
WindowVisibilityAnimationType GetWindowVisibilityAnimationType(
    aura::Window* window) {
  WindowVisibilityAnimationType type =
      window->GetProperty(kWindowVisibilityAnimationTypeKey);
  if (type == WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT) {
    return (window->type() == aura::client::WINDOW_TYPE_MENU ||
            window->type() == aura::client::WINDOW_TYPE_TOOLTIP) ?
        WINDOW_VISIBILITY_ANIMATION_TYPE_FADE :
        WINDOW_VISIBILITY_ANIMATION_TYPE_DROP;
  }
  return type;
}

// Observes a hide animation.
// A window can be hidden for a variety of reasons. Sometimes, Hide() will be
// called and life is simple. Sometimes, the window is actually bound to a
// views::Widget and that Widget is closed, and life is a little more
// complicated. When a Widget is closed the aura::Window* is actually not
// destroyed immediately - it is actually just immediately hidden and then
// destroyed when the stack unwinds. To handle this case, we start the hide
// animation immediately when the window is hidden, then when the window is
// subsequently destroyed this object acquires ownership of the window's layer,
// so that it can continue animating it until the animation completes.
// Regardless of whether or not the window is destroyed, this object deletes
// itself when the animation completes.
class HidingWindowAnimationObserver : public ui::ImplicitAnimationObserver,
                                      public aura::WindowObserver {
 public:
  explicit HidingWindowAnimationObserver(aura::Window* window)
      : window_(window) {
    window_->AddObserver(this);
  }
  virtual ~HidingWindowAnimationObserver() {
    STLDeleteElements(&layers_);
  }

 private:
  // Overridden from ui::ImplicitAnimationObserver:
  virtual void OnImplicitAnimationsCompleted() OVERRIDE {
    // Window may have been destroyed by this point.
    if (window_)
      window_->RemoveObserver(this);
    delete this;
  }

  // Overridden from aura::WindowObserver:
  virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {
    DCHECK_EQ(window, window_);
    DCHECK(layers_.empty());
    AcquireAllLayers(window_);

    // If the Widget has views with layers, then it is necessary to take
    // ownership of those layers too.
    views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window_);
    const views::Widget* const_widget = widget;
    if (widget && const_widget->GetRootView() && widget->GetContentsView())
      AcquireAllViewLayers(widget->GetContentsView());
    window_->RemoveObserver(this);
    window_ = NULL;
  }

  void AcquireAllLayers(aura::Window* window) {
    ui::Layer* layer = window->AcquireLayer();
    DCHECK(layer);
    layers_.push_back(layer);
    for (aura::Window::Windows::const_iterator it = window->children().begin();
         it != window->children().end();
         ++it)
      AcquireAllLayers(*it);
  }

  void AcquireAllViewLayers(views::View* view) {
    for (int i = 0; i < view->child_count(); ++i)
      AcquireAllViewLayers(view->child_at(i));
    if (view->layer()) {
      ui::Layer* layer = view->RecreateLayer();
      if (layer) {
        layer->SuppressPaint();
        layers_.push_back(layer);
      }
    }
  }

  aura::Window* window_;
  std::vector<ui::Layer*> layers_;

  DISALLOW_COPY_AND_ASSIGN(HidingWindowAnimationObserver);
};

// ImplicitAnimationObserver used when switching workspaces. Resets the layer
// visibility to 'false' when done. This doesn't need the complexity of
// HidingWindowAnimationObserver as the window isn't closing, and if it does a
// HidingWindowAnimationObserver will be created.
class WorkspaceHidingWindowAnimationObserver
    : public ui::ImplicitAnimationObserver {
 public:
  explicit WorkspaceHidingWindowAnimationObserver(aura::Window* window)
      : layer_(window->layer()) {
  }
  virtual ~WorkspaceHidingWindowAnimationObserver() {
  }

 private:
  // Overridden from ui::ImplicitAnimationObserver:
  virtual void OnImplicitAnimationsCompleted() OVERRIDE {
    // Restore the correct visibility value (overridden for the duration of the
    // animation in AnimateHideWindow()).
    layer_->SetVisible(false);
    delete this;
  }

  ui::Layer* layer_;

  DISALLOW_COPY_AND_ASSIGN(WorkspaceHidingWindowAnimationObserver);
};

// Shows a window using an animation, animating its opacity from 0.f to 1.f,
// its visibility to true, and its transform from |start_transform| to
// |end_transform|.
void AnimateShowWindowCommon(aura::Window* window,
                             const ui::Transform& start_transform,
                             const ui::Transform& end_transform) {
  window->layer()->set_delegate(window);
  window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
  window->layer()->SetTransform(start_transform);

  {
    // Property sets within this scope will be implicitly animated.
    ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
    base::TimeDelta duration = GetWindowVisibilityAnimationDuration(window);
    if (duration.ToInternalValue() > 0)
      settings.SetTransitionDuration(duration);

    window->layer()->SetVisible(true);
    window->layer()->SetTransform(end_transform);
    window->layer()->SetOpacity(kWindowAnimation_ShowOpacity);
  }
}

// Hides a window using an animation, animating its opacity from 1.f to 0.f,
// its visibility to false, and its transform to |end_transform|.
void AnimateHideWindowCommon(aura::Window* window,
                             const ui::Transform& end_transform) {
  window->layer()->set_delegate(NULL);

  // Property sets within this scope will be implicitly animated.
  ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
  settings.AddObserver(new HidingWindowAnimationObserver(window));

  base::TimeDelta duration = GetWindowVisibilityAnimationDuration(window);
  if (duration.ToInternalValue() > 0)
    settings.SetTransitionDuration(duration);

  window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
  window->layer()->SetTransform(end_transform);
  window->layer()->SetVisible(false);
}

// Show/Hide windows using a shrink animation.
void AnimateShowWindow_Drop(aura::Window* window) {
  ui::Transform transform;
  transform.ConcatScale(kWindowAnimation_ScaleFactor,
                        kWindowAnimation_ScaleFactor);
  gfx::Rect bounds = window->bounds();
  transform.ConcatTranslate(
      kWindowAnimation_TranslateFactor * bounds.width(),
      kWindowAnimation_TranslateFactor * bounds.height());
  AnimateShowWindowCommon(window, transform, ui::Transform());
}

void AnimateHideWindow_Drop(aura::Window* window) {
  ui::Transform transform;
  transform.ConcatScale(kWindowAnimation_ScaleFactor,
                        kWindowAnimation_ScaleFactor);
  gfx::Rect bounds = window->bounds();
  transform.ConcatTranslate(
      kWindowAnimation_TranslateFactor * bounds.width(),
      kWindowAnimation_TranslateFactor * bounds.height());
  AnimateHideWindowCommon(window, transform);
}

// Show/Hide windows using a vertical Glenimation.
void AnimateShowWindow_Vertical(aura::Window* window) {
  ui::Transform transform;
  transform.ConcatTranslate(0, window->GetProperty(
      kWindowVisibilityAnimationVerticalPositionKey));
  AnimateShowWindowCommon(window, transform, ui::Transform());
}

void AnimateHideWindow_Vertical(aura::Window* window) {
  ui::Transform transform;
  transform.ConcatTranslate(0, window->GetProperty(
      kWindowVisibilityAnimationVerticalPositionKey));
  AnimateHideWindowCommon(window, transform);
}

// Show/Hide windows using a fade.
void AnimateShowWindow_Fade(aura::Window* window) {
  AnimateShowWindowCommon(window, ui::Transform(), ui::Transform());
}

void AnimateHideWindow_Fade(aura::Window* window) {
  AnimateHideWindowCommon(window, ui::Transform());
}

// Builds the transform used when switching workspaces for the specified
// window.
ui::Transform BuildWorkspaceSwitchTransform(aura::Window* window, float scale) {
  // Animations for transitioning workspaces scale all windows. To give the
  // effect of scaling from the center of the screen the windows are translated.
  gfx::Rect bounds = window->bounds();
  gfx::Rect parent_bounds(window->parent()->bounds());

  float mid_x = static_cast<float>(parent_bounds.width()) / 2.0f;
  float initial_x =
      (static_cast<float>(bounds.x()) - mid_x) * scale + mid_x;
  float mid_y = static_cast<float>(parent_bounds.height()) / 2.0f;
  float initial_y =
      (static_cast<float>(bounds.y()) - mid_y) * scale + mid_y;

  ui::Transform transform;
  transform.ConcatTranslate(
      initial_x - static_cast<float>(bounds.x()),
      initial_y - static_cast<float>(bounds.y()));
  transform.ConcatScale(scale, scale);
  return transform;
}

void AnimateShowWindow_Workspace(aura::Window* window) {
  ui::Transform transform(
      BuildWorkspaceSwitchTransform(window, kWorkspaceScale));
  // When we call SetOpacity here, if a hide sequence is already running,
  // the default animation preemption strategy fast forwards the hide sequence
  // to completion and notifies the WorkspaceHidingWindowAnimationObserver to
  // set the layer to be invisible. We should call SetVisible after SetOpacity
  // to ensure our layer is visible again.
  window->layer()->SetOpacity(0.0f);
  window->layer()->SetTransform(transform);
  window->layer()->SetVisible(true);

  {
    // Property sets within this scope will be implicitly animated.
    ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());

    window->layer()->SetTransform(ui::Transform());
    // Opacity animates only during the first half of the animation.
    settings.SetTransitionDuration(settings.GetTransitionDuration() / 2);
    window->layer()->SetOpacity(1.0f);
  }
}

void AnimateHideWindow_Workspace(aura::Window* window) {
  ui::Transform transform(
      BuildWorkspaceSwitchTransform(window, kWorkspaceScale));
  window->layer()->SetOpacity(1.0f);
  window->layer()->SetTransform(ui::Transform());

  // Opacity animates from 1 to 0 only over the second half of the animation. To
  // get this functionality two animations are schedule for opacity, the first
  // from 1 to 1 (which effectively does nothing) the second from 1 to 0.
  // Because we're scheduling two animations of the same property we need to
  // change the preemption strategy.
  ui::LayerAnimator* animator = window->layer()->GetAnimator();
  animator->set_preemption_strategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
  {
    // Property sets within this scope will be implicitly animated.
    ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
    // Add an observer that sets visibility of the layer to false once animation
    // completes.
    settings.AddObserver(new WorkspaceHidingWindowAnimationObserver(window));
    window->layer()->SetTransform(transform);
    settings.SetTransitionDuration(settings.GetTransitionDuration() / 2);
    window->layer()->SetOpacity(1.0f);
    window->layer()->SetOpacity(0.0f);
  }
  animator->set_preemption_strategy(
      ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET);
}

gfx::Rect GetMinimizeRectForWindow(aura::Window* window) {
  gfx::Rect target_bounds = Shell::GetInstance()->launcher()->
      GetScreenBoundsOfItemIconForWindow(window);
  if (target_bounds.IsEmpty()) {
    // Assume the launcher is overflowed, zoom off to the bottom right of the
    // work area.
    gfx::Rect work_area =
        gfx::Screen::GetDisplayNearestWindow(window).work_area();
    target_bounds.SetRect(work_area.right(), work_area.bottom(), 0, 0);
  }
  target_bounds =
      ScreenAsh::ConvertRectFromScreen(window->parent(), target_bounds);
  return target_bounds;
}

void AddLayerAnimationsForMinimize(aura::Window* window, bool show) {
  // Recalculate the transform at restore time since the launcher item may have
  // moved while the window was minimized.
  gfx::Rect bounds = window->bounds();
  gfx::Rect target_bounds = GetMinimizeRectForWindow(window);

  float scale_x = static_cast<float>(target_bounds.height()) / bounds.width();
  float scale_y = static_cast<float>(target_bounds.width()) / bounds.height();

  scoped_ptr<ui::InterpolatedTransform> scale(
      new ui::InterpolatedScale(gfx::Point3f(1, 1, 1),
                                gfx::Point3f(scale_x, scale_y, 1)));

  scoped_ptr<ui::InterpolatedTransform> translation(
      new ui::InterpolatedTranslation(
          gfx::Point(),
          gfx::Point(target_bounds.x() - bounds.x(),
                     target_bounds.y() - bounds.y())));

  scoped_ptr<ui::InterpolatedTransform> rotation(
      new ui::InterpolatedRotation(0, kWindowAnimation_MinimizeRotate));

  scoped_ptr<ui::InterpolatedTransform> rotation_about_pivot(
      new ui::InterpolatedTransformAboutPivot(
          gfx::Point(bounds.width() * 0.5, bounds.height() * 0.5),
          rotation.release()));

  scale->SetChild(translation.release());
  rotation_about_pivot->SetChild(scale.release());

  rotation_about_pivot->SetReversed(show);

  base::TimeDelta duration = base::TimeDelta::FromMilliseconds(
      kLayerAnimationsForMinimizeDurationMS);

  scoped_ptr<ui::LayerAnimationElement> transition(
      ui::LayerAnimationElement::CreateInterpolatedTransformElement(
          rotation_about_pivot.release(), duration));

  transition->set_tween_type(
      show ? ui::Tween::EASE_IN : ui::Tween::EASE_IN_OUT);

  window->layer()->GetAnimator()->ScheduleAnimation(
      new ui::LayerAnimationSequence(transition.release()));

  // When hiding a window, turn off blending until the animation is 3 / 4 done
  // to save bandwidth and reduce jank
  if (!show) {
    window->layer()->GetAnimator()->SchedulePauseForProperties(
        (duration * 3 ) / 4, ui::LayerAnimationElement::OPACITY, -1);
  }

  // Fade in and out quickly when the window is small to reduce jank
  float opacity = show ? 1.0f : 0.0f;
  window->layer()->GetAnimator()->ScheduleAnimation(
      new ui::LayerAnimationSequence(
          ui::LayerAnimationElement::CreateOpacityElement(
              opacity, duration / 4)));
}

void AnimateShowWindow_Minimize(aura::Window* window) {
  window->layer()->set_delegate(window);
  window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
  AddLayerAnimationsForMinimize(window, true);

  // Now that the window has been restored, we need to clear its animation style
  // to default so that normal animation applies.
  SetWindowVisibilityAnimationType(
      window, WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT);
}

void AnimateHideWindow_Minimize(aura::Window* window) {
  window->layer()->set_delegate(NULL);

  // Property sets within this scope will be implicitly animated.
  ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
  base::TimeDelta duration = base::TimeDelta::FromMilliseconds(
      kLayerAnimationsForMinimizeDurationMS);
  settings.SetTransitionDuration(duration);
  settings.AddObserver(new HidingWindowAnimationObserver(window));
  window->layer()->SetVisible(false);

  AddLayerAnimationsForMinimize(window, false);
}

void AnimateShowHideWindowCommon_BrightnessGrayscale(aura::Window* window,
                                                     bool show) {
  window->layer()->set_delegate(window);

  float start_value, end_value;
  if (show) {
    start_value = kWindowAnimation_HideBrightnessGrayscale;
    end_value = kWindowAnimation_ShowBrightnessGrayscale;
  } else {
    start_value = kWindowAnimation_ShowBrightnessGrayscale;
    end_value = kWindowAnimation_HideBrightnessGrayscale;
  }

  window->layer()->SetLayerBrightness(start_value);
  window->layer()->SetLayerGrayscale(start_value);
  if (show) {
    window->layer()->SetOpacity(kWindowAnimation_ShowOpacity);
    window->layer()->SetVisible(true);
  }

  int animation_duration = kBrightnessGrayscaleFadeDurationMs;
  ui::Tween::Type animation_type = ui::Tween::EASE_OUT;
  if (CommandLine::ForCurrentProcess()->HasSwitch(
          ash::switches::kAshBootAnimationFunction2)) {
    animation_type = ui::Tween::EASE_OUT_2;
  } else if (CommandLine::ForCurrentProcess()->HasSwitch(
                  ash::switches::kAshBootAnimationFunction3)) {
    animation_type = ui::Tween::EASE_OUT_3;
  }

  ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
  settings.SetTransitionDuration(
      base::TimeDelta::FromMilliseconds(animation_duration));
  if (!show)
    settings.AddObserver(new HidingWindowAnimationObserver(window));

  scoped_ptr<ui::LayerAnimationSequence> brightness_sequence(
      new ui::LayerAnimationSequence());
  scoped_ptr<ui::LayerAnimationSequence> grayscale_sequence(
      new ui::LayerAnimationSequence());

  scoped_ptr<ui::LayerAnimationElement> brightness_element(
      ui::LayerAnimationElement::CreateBrightnessElement(
          end_value,
          base::TimeDelta::FromMilliseconds(animation_duration)));
  brightness_element->set_tween_type(animation_type);
  brightness_sequence->AddElement(brightness_element.release());

  scoped_ptr<ui::LayerAnimationElement> grayscale_element(
      ui::LayerAnimationElement::CreateGrayscaleElement(
          end_value,
          base::TimeDelta::FromMilliseconds(animation_duration)));
  grayscale_element->set_tween_type(animation_type);
  grayscale_sequence->AddElement(grayscale_element.release());

   std::vector<ui::LayerAnimationSequence*> animations;
   animations.push_back(brightness_sequence.release());
   animations.push_back(grayscale_sequence.release());
   window->layer()->GetAnimator()->ScheduleTogether(animations);
   if (!show) {
     window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
     window->layer()->SetVisible(false);
   }
}

void AnimateShowWindow_BrightnessGrayscale(aura::Window* window) {
  AnimateShowHideWindowCommon_BrightnessGrayscale(window, true);
}

void AnimateHideWindow_BrightnessGrayscale(aura::Window* window) {
  AnimateShowHideWindowCommon_BrightnessGrayscale(window, false);
}

bool AnimateShowWindow(aura::Window* window) {
  if (!HasWindowVisibilityAnimationTransition(window, ANIMATE_SHOW))
    return false;

  switch (GetWindowVisibilityAnimationType(window)) {
    case WINDOW_VISIBILITY_ANIMATION_TYPE_DROP:
      AnimateShowWindow_Drop(window);
      return true;
    case WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL:
      AnimateShowWindow_Vertical(window);
      return true;
    case WINDOW_VISIBILITY_ANIMATION_TYPE_FADE:
      AnimateShowWindow_Fade(window);
      return true;
    case WINDOW_VISIBILITY_ANIMATION_TYPE_WORKSPACE_SHOW:
      AnimateShowWindow_Workspace(window);
      return true;
    case WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE:
      AnimateShowWindow_Minimize(window);
      return true;
    case WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE:
        AnimateShowWindow_BrightnessGrayscale(window);
        return true;
    default:
      NOTREACHED();
      return false;
  }
}

bool AnimateHideWindow(aura::Window* window) {
  if (!HasWindowVisibilityAnimationTransition(window, ANIMATE_HIDE))
    return false;

  switch (GetWindowVisibilityAnimationType(window)) {
    case WINDOW_VISIBILITY_ANIMATION_TYPE_DROP:
      AnimateHideWindow_Drop(window);
      return true;
    case WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL:
      AnimateHideWindow_Vertical(window);
      return true;
    case WINDOW_VISIBILITY_ANIMATION_TYPE_FADE:
      AnimateHideWindow_Fade(window);
      return true;
    case WINDOW_VISIBILITY_ANIMATION_TYPE_WORKSPACE_HIDE:
      AnimateHideWindow_Workspace(window);
      return true;
    case WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE:
      AnimateHideWindow_Minimize(window);
      return true;
    case WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE:
      AnimateHideWindow_BrightnessGrayscale(window);
      return true;
    default:
      NOTREACHED();
      return false;
  }
}

// Observer for a window cross-fade animation. If either the window closes or
// the layer's animation completes or compositing is aborted due to GPU crash,
// it deletes the layer and removes itself as an observer.
class CrossFadeObserver : public ui::CompositorObserver,
                          public aura::WindowObserver,
                          public ui::ImplicitAnimationObserver {
 public:
  // Observes |window| for destruction, but does not take ownership.
  // Takes ownership of |layer| and its child layers.
  CrossFadeObserver(Window* window, Layer* layer)
      : window_(window),
        layer_(layer) {
    window_->AddObserver(this);
    layer_->GetCompositor()->AddObserver(this);
  }
  virtual ~CrossFadeObserver() {
    window_->RemoveObserver(this);
    window_ = NULL;
    layer_->GetCompositor()->RemoveObserver(this);
    wm::DeepDeleteLayers(layer_);
    layer_ = NULL;
  }

  // ui::CompositorObserver overrides:
  virtual void OnCompositingDidCommit(ui::Compositor* compositor) OVERRIDE {
  }
  virtual void OnCompositingWillStart(ui::Compositor* compositor) OVERRIDE {
  }
  virtual void OnCompositingStarted(ui::Compositor* compositor) OVERRIDE {
  }
  virtual void OnCompositingEnded(ui::Compositor* compositor) OVERRIDE {
  }
  virtual void OnCompositingAborted(ui::Compositor* compositor) OVERRIDE {
    // Triggers OnImplicitAnimationsCompleted() to be called and deletes us.
    layer_->GetAnimator()->StopAnimating();
  }

  // aura::WindowObserver overrides:
  virtual void OnWindowDestroying(Window* window) OVERRIDE {
    // Triggers OnImplicitAnimationsCompleted() to be called and deletes us.
    layer_->GetAnimator()->StopAnimating();
  }

  // ui::ImplicitAnimationObserver overrides:
  virtual void OnImplicitAnimationsCompleted() OVERRIDE {
    delete this;
  }

 private:
  Window* window_;  // not owned
  Layer* layer_;  // owned

  DISALLOW_COPY_AND_ASSIGN(CrossFadeObserver);
};

}  // namespace
}  // namespace internal

namespace {

// Scales for workspaces above/below current workspace.
const float kWorkspaceScaleAbove = 1.1f;
const float kWorkspaceScaleBelow = .9f;

// Amount of time to pause before animating anything. Only used during initial
// animation (when logging in).
const int kWorkspaceInitialPauseTimeMS = 750;

// TODO: leaving in for now since Nicholas wants to play with this, remove if we
// leave it at 0.
const int kPauseTimeMS = 0;

}  // namespace

// Amount of time for animating a workspace in or out.
const int kWorkspaceSwitchTimeMS = 200 + kPauseTimeMS;

namespace {

// Brightness for the non-active workspace.
// TODO(sky): ideally this would be -.33f, but it slows down animations by a
// factor of 2. Figure out why.
const float kWorkspaceBrightness = 0.0f;

enum WorkspaceScaleType {
  WORKSPACE_SCALE_ABOVE,
  WORKSPACE_SCALE_BELOW,
};

// Used to identify what should animate in AnimateWorkspaceIn/Out.
enum WorkspaceAnimateTypes {
  WORKSPACE_ANIMATE_OPACITY =    1 << 0,
  WORKSPACE_ANIMATE_BRIGHTNESS = 1 << 1,
};

void ApplyWorkspaceScale(ui::Layer* layer, WorkspaceScaleType type) {
  const float scale = type == WORKSPACE_SCALE_ABOVE ? kWorkspaceScaleAbove :
      kWorkspaceScaleBelow;
  ui::Transform transform;
  transform.ConcatScale(scale, scale);
  transform.ConcatTranslate(
      -layer->bounds().width() * (scale - 1.0f) / 2,
      -layer->bounds().height() * (scale - 1.0f) / 2);
  layer->SetTransform(transform);
}

// Implementation of cross fading. Window is the window being cross faded. It
// should be at the target bounds. |old_layer| the previous layer from |window|.
// This takes ownership of |old_layer| and deletes when the animation is done.
// |pause_duration| is the duration to pause at the current bounds before
// animating. Returns the duration of the fade.
TimeDelta CrossFadeImpl(aura::Window* window,
                        ui::Layer* old_layer,
                        ui::Tween::Type tween_type,
                        base::TimeDelta pause_duration) {
  const gfx::Rect old_bounds(old_layer->bounds());
  const gfx::Rect new_bounds(window->bounds());
  const bool old_on_top = (old_bounds.width() > new_bounds.width());

  // Shorten the animation if there's not much visual movement.
  const TimeDelta duration = GetCrossFadeDuration(old_bounds, new_bounds);

  // Scale up the old layer while translating to new position.
  {
    old_layer->GetAnimator()->StopAnimating();
    ui::ScopedLayerAnimationSettings settings(old_layer->GetAnimator());
    settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
    old_layer->GetAnimator()->SchedulePauseForProperties(
        pause_duration, ui::LayerAnimationElement::TRANSFORM,
        ui::LayerAnimationElement::OPACITY, -1);

    // Animation observer owns the old layer and deletes itself.
    settings.AddObserver(new internal::CrossFadeObserver(window, old_layer));
    settings.SetTransitionDuration(duration);
    settings.SetTweenType(tween_type);
    ui::Transform out_transform;
    float scale_x = static_cast<float>(new_bounds.width()) /
        static_cast<float>(old_bounds.width());
    float scale_y = static_cast<float>(new_bounds.height()) /
        static_cast<float>(old_bounds.height());
    out_transform.ConcatScale(scale_x, scale_y);
    out_transform.ConcatTranslate(new_bounds.x() - old_bounds.x(),
                                  new_bounds.y() - old_bounds.y());
    old_layer->SetTransform(out_transform);
    if (old_on_top) {
      // The old layer is on top, and should fade out.  The new layer below will
      // stay opaque to block the desktop.
      old_layer->SetOpacity(0.f);
    }
    // In tests |old_layer| is deleted here, as animations have zero duration.
    old_layer = NULL;
  }

  // Set the new layer's current transform, such that the user sees a scaled
  // version of the window with the original bounds at the original position.
  ui::Transform in_transform;
  const float scale_x = static_cast<float>(old_bounds.width()) /
      static_cast<float>(new_bounds.width());
  const float scale_y = static_cast<float>(old_bounds.height()) /
      static_cast<float>(new_bounds.height());
  in_transform.ConcatScale(scale_x, scale_y);
  in_transform.ConcatTranslate(old_bounds.x() - new_bounds.x(),
                               old_bounds.y() - new_bounds.y());
  window->layer()->SetTransform(in_transform);
  if (!old_on_top) {
    // The new layer is on top and should fade in.  The old layer below will
    // stay opaque and block the desktop.
    window->layer()->SetOpacity(0.f);
  }
  {
    // Animate the new layer to the identity transform, so the window goes to
    // its newly set bounds.
    ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
    settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
    window->layer()->GetAnimator()->SchedulePauseForProperties(
        pause_duration, ui::LayerAnimationElement::TRANSFORM,
        ui::LayerAnimationElement::OPACITY, -1);
    settings.SetTransitionDuration(duration);
    settings.SetTweenType(tween_type);
    window->layer()->SetTransform(ui::Transform());
    if (!old_on_top) {
      // New layer is on top, fade it in.
      window->layer()->SetOpacity(1.f);
    }
  }
  return duration;
}

// Returns a TimeDelta from |time_ms|. If animations are disabled this returns
// a TimeDelta of 0 (so the animation completes immediately).
base::TimeDelta AdjustAnimationTime(int time_ms) {
  if (CommandLine::ForCurrentProcess()->HasSwitch(
          ash::switches::kAshWindowAnimationsDisabled))
    time_ms = 0;
  return base::TimeDelta::FromMilliseconds(time_ms);
}

// Returns a TimeDelta adjusted for animations. If animations are disabled this
// returns 0. If animations are not disabled and |time_delta| is non-empty, it
// is returned. Otherwise |delta_time_ms| is returned.
base::TimeDelta AdjustAnimationTimeDelta(base::TimeDelta time_delta,
                                         int delta_time_ms) {
  if (CommandLine::ForCurrentProcess()->HasSwitch(
          ash::switches::kAshWindowAnimationsDisabled))
    return base::TimeDelta();
  return time_delta == base::TimeDelta() ?
      base::TimeDelta::FromMilliseconds(delta_time_ms) : time_delta;
}

void AnimateWorkspaceInImpl(aura::Window* window,
                            WorkspaceAnimationDirection direction,
                            uint32 animate_types,
                            int pause_time_ms,
                            ui::Tween::Type tween_type,
                            base::TimeDelta duration) {
  window->layer()->SetOpacity(
      (animate_types & WORKSPACE_ANIMATE_OPACITY) ? 0.0f : 1.0f);
  window->layer()->SetLayerBrightness(
      (animate_types & WORKSPACE_ANIMATE_BRIGHTNESS) ?
      kWorkspaceBrightness : 0.0f);
  window->Show();
  ApplyWorkspaceScale(window->layer(),
                      direction == WORKSPACE_ANIMATE_UP ?
                          WORKSPACE_SCALE_BELOW : WORKSPACE_SCALE_ABOVE);
  window->layer()->GetAnimator()->StopAnimating();

  {
    ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());

    if (pause_time_ms > 0) {
      settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
      window->layer()->GetAnimator()->SchedulePauseForProperties(
          AdjustAnimationTime(pause_time_ms),
          ui::LayerAnimationElement::TRANSFORM,
          ui::LayerAnimationElement::OPACITY,
          ui::LayerAnimationElement::BRIGHTNESS,
          ui::LayerAnimationElement::VISIBILITY,
          -1);
    }

    settings.SetTweenType(tween_type);
    settings.SetTransitionDuration(duration);
    window->layer()->SetTransform(ui::Transform());
    window->layer()->SetOpacity(1.0f);
    window->layer()->SetLayerBrightness(0.0f);
  }
}

void AnimateWorkspaceOutImpl(aura::Window* window,
                             WorkspaceAnimationDirection direction,
                             uint32 animate_types,
                             int pause_time_ms,
                             ui::Tween::Type tween_type,
                             TimeDelta duration) {
  window->Show();
  window->layer()->SetTransform(ui::Transform());
  window->layer()->SetLayerBrightness(0.0f);
  window->layer()->SetOpacity(1.0f);
  window->layer()->GetAnimator()->StopAnimating();

  {
    ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());

    if (pause_time_ms > 0) {
      settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
      window->layer()->GetAnimator()->SchedulePauseForProperties(
          AdjustAnimationTime(pause_time_ms),
          ui::LayerAnimationElement::TRANSFORM,
          ui::LayerAnimationElement::OPACITY,
          ui::LayerAnimationElement::BRIGHTNESS,
          ui::LayerAnimationElement::VISIBILITY,
          -1);
    }

    settings.SetTransitionDuration(duration);
    settings.SetTweenType(tween_type);
    ApplyWorkspaceScale(window->layer(),
                        direction == WORKSPACE_ANIMATE_UP ?
                            WORKSPACE_SCALE_ABOVE : WORKSPACE_SCALE_BELOW);
    // NOTE: Hide() must be before SetOpacity(), else
    // VisibilityController::UpdateLayerVisibility doesn't pass the false to the
    // layer so that the layer and window end up out of sync and confused.
    window->Hide();
    if (animate_types & WORKSPACE_ANIMATE_OPACITY)
      window->layer()->SetOpacity(0.0f);
    if (animate_types & WORKSPACE_ANIMATE_BRIGHTNESS)
      window->layer()->SetLayerBrightness(kWorkspaceBrightness);

    // After the animation completes snap the transform back to the identity,
    // otherwise any one that asks for screen bounds gets a slightly scaled
    // version.
    settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
    settings.SetTransitionDuration(base::TimeDelta());
    window->layer()->SetTransform(ui::Transform());
  }
}

ui::Tween::Type TweenTypeForWorskpaceOut(WorkspaceType type) {
  return WORKSPACE_DESKTOP ? ui::Tween::LINEAR : ui::Tween::EASE_OUT;
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// External interface

void SetWindowVisibilityAnimationType(aura::Window* window,
                                      WindowVisibilityAnimationType type) {
  window->SetProperty(internal::kWindowVisibilityAnimationTypeKey, type);
}

WindowVisibilityAnimationType GetWindowVisibilityAnimationType(
    aura::Window* window) {
  return window->GetProperty(internal::kWindowVisibilityAnimationTypeKey);
}

void SetWindowVisibilityAnimationTransition(
    aura::Window* window,
    WindowVisibilityAnimationTransition transition) {
  window->SetProperty(internal::kWindowVisibilityAnimationTransitionKey,
                      transition);
}

void SetWindowVisibilityAnimationDuration(aura::Window* window,
                                          const TimeDelta& duration) {
  window->SetProperty(internal::kWindowVisibilityAnimationDurationKey,
                      static_cast<int>(duration.ToInternalValue()));
}

void SetWindowVisibilityAnimationVerticalPosition(aura::Window* window,
                                                  float position) {
  window->SetProperty(internal::kWindowVisibilityAnimationVerticalPositionKey,
                      position);
}

ui::ImplicitAnimationObserver* CreateHidingWindowAnimationObserver(
    aura::Window* window) {
  return new internal::HidingWindowAnimationObserver(window);
}

void CrossFadeToBounds(aura::Window* window, const gfx::Rect& new_bounds) {
  DCHECK(window->TargetVisibility());
  const gfx::Rect old_bounds = window->bounds();

  // Create fresh layers for the window and all its children to paint into.
  // Takes ownership of the old layer and all its children, which will be
  // cleaned up after the animation completes.
  ui::Layer* old_layer = wm::RecreateWindowLayers(window, false);
  ui::Layer* new_layer = window->layer();

  // Resize the window to the new size, which will force a layout and paint.
  window->SetBounds(new_bounds);

  // Ensure the higher-resolution layer is on top.
  bool old_on_top = (old_bounds.width() > new_bounds.width());
  if (old_on_top)
    old_layer->parent()->StackBelow(new_layer, old_layer);
  else
    old_layer->parent()->StackAbove(new_layer, old_layer);

  CrossFadeImpl(window, old_layer, ui::Tween::EASE_OUT, base::TimeDelta());
}

void CrossFadeWindowBetweenWorkspaces(aura::Window* old_workspace,
                                      aura::Window* new_workspace,
                                      aura::Window* window,
                                      ui::Layer* old_layer) {
  ui::Layer* layer_parent = new_workspace->layer()->parent();
  layer_parent->Add(old_layer);
  const bool restoring = old_layer->bounds().width() > window->bounds().width();
  ui::Tween::Type tween_type, workspace_tween_type;
  if (restoring) {
    layer_parent->StackAbove(old_layer, new_workspace->layer());
    tween_type = ui::Tween::EASE_OUT;
    workspace_tween_type = ui::Tween::EASE_OUT;
  } else {
    layer_parent->StackBelow(old_layer, new_workspace->layer());
    tween_type = ui::Tween::EASE_IN_2;
    workspace_tween_type = ui::Tween::LINEAR;
  }

  const TimeDelta duration =
      CrossFadeImpl(window, old_layer, tween_type,
                    AdjustAnimationTime(restoring ? 0 : kPauseTimeMS));

  if (restoring) {
    typedef aura::Window::Windows Windows;
    if (old_workspace)
      AnimateWorkspaceOutImpl(old_workspace, WORKSPACE_ANIMATE_UP,
                              WORKSPACE_ANIMATE_BRIGHTNESS,
                              0, workspace_tween_type, duration);

    // Ideally we would use AnimateWorkspaceIn() for |new_workspace|, but that
    // results in |window| animating with the workspace scale. We don't want
    // that, so we explicitly animate each of the children to give the effect of
    // the workspace scaling.
    new_workspace->Show();
    new_workspace->SetTransform(ui::Transform());
    new_workspace->layer()->SetOpacity(1.0f);
    new_workspace->layer()->SetLayerBrightness(0.0f);
    const Windows& children(new_workspace->children());
    for (Windows::const_iterator i = children.begin(); i != children.end();
         ++i) {
      aura::Window* child = *i;
      if (child == window)
        continue;
      child->SetTransform(ash::internal::BuildWorkspaceSwitchTransform(
                              child, kWorkspaceScaleBelow));
      child->layer()->SetLayerBrightness(kWorkspaceBrightness);
      ui::ScopedLayerAnimationSettings settings(child->layer()->GetAnimator());
      settings.SetTweenType(ui::Tween::EASE_OUT);
      settings.SetTransitionDuration(duration);
      child->SetTransform(ui::Transform());
      child->layer()->SetLayerBrightness(0.0f);
    }
  } else {
    if (old_workspace) {
      AnimateWorkspaceOutImpl(old_workspace, WORKSPACE_ANIMATE_DOWN,
                              WORKSPACE_ANIMATE_BRIGHTNESS,
                              0, workspace_tween_type, duration);
    }

    new_workspace->Show();
    new_workspace->layer()->SetOpacity(1.f);
    new_workspace->layer()->SetTransform(ui::Transform());
    new_workspace->layer()->SetLayerBrightness(0.0f);
  }
}

void AnimateBetweenWorkspaces(aura::Window* old_window,
                              WorkspaceType old_type,
                              bool animate_old,
                              aura::Window* new_window,
                              WorkspaceType new_type,
                              bool is_restoring_maximized_window) {
  uint32 common_animate_types = 0;
  if (animate_old) {
    // When switching to the desktop the old window lifts off.
    uint32 animate_types = 0;
    if (!(new_type == WORKSPACE_MAXIMIZED && old_type == WORKSPACE_MAXIMIZED))
      animate_types |= WORKSPACE_ANIMATE_BRIGHTNESS;
    if ((new_type == WORKSPACE_DESKTOP || old_type == WORKSPACE_MAXIMIZED) &&
        !is_restoring_maximized_window)
      animate_types |= WORKSPACE_ANIMATE_OPACITY;
    AnimateWorkspaceOutImpl(
        old_window,
        new_type == WORKSPACE_DESKTOP ? WORKSPACE_ANIMATE_UP :
            WORKSPACE_ANIMATE_DOWN,
        animate_types,
        0,
        TweenTypeForWorskpaceOut(old_type),
        AdjustAnimationTime(kWorkspaceSwitchTimeMS));
  }

  // Switching from the desktop to a maximized animates down.
  uint32 animate_types = common_animate_types;
  if (!(new_type == WORKSPACE_MAXIMIZED && old_type == WORKSPACE_MAXIMIZED) &&
      !is_restoring_maximized_window)
    animate_types |= WORKSPACE_ANIMATE_BRIGHTNESS;
  if (old_type == WORKSPACE_DESKTOP)
    animate_types |= WORKSPACE_ANIMATE_OPACITY;
  AnimateWorkspaceInImpl(
      new_window,
      old_type == WORKSPACE_DESKTOP ?
          WORKSPACE_ANIMATE_DOWN : WORKSPACE_ANIMATE_UP,
      animate_types,
      0,
      ui::Tween::EASE_OUT,
      AdjustAnimationTime(kWorkspaceSwitchTimeMS));
}

void AnimateWorkspaceIn(aura::Window* window,
                        WorkspaceAnimationDirection direction,
                        bool initial_animate,
                        base::TimeDelta delta) {
  AnimateWorkspaceInImpl(
      window, direction,
      WORKSPACE_ANIMATE_BRIGHTNESS |
          (initial_animate ? WORKSPACE_ANIMATE_OPACITY : 0),
      initial_animate ? kWorkspaceInitialPauseTimeMS : 0,
      ui::Tween::EASE_OUT,
      AdjustAnimationTimeDelta(delta, kWorkspaceSwitchTimeMS));
}

void AnimateWorkspaceOut(aura::Window* window,
                         WorkspaceAnimationDirection direction,
                         WorkspaceType type,
                         bool initial_animate,
                         base::TimeDelta delta) {
  AnimateWorkspaceOutImpl(window, direction, WORKSPACE_ANIMATE_BRIGHTNESS,
                          initial_animate ? kWorkspaceInitialPauseTimeMS : 0,
                          TweenTypeForWorskpaceOut(type),
                          AdjustAnimationTimeDelta(delta,
                                                   kWorkspaceSwitchTimeMS));
}

base::TimeDelta GetSystemBackgroundDestroyDuration() {
  return base::TimeDelta::FromMilliseconds(
      std::max(static_cast<int>(internal::kCrossFadeDurationMaxMs),
               kWorkspaceSwitchTimeMS));
}

TimeDelta GetCrossFadeDuration(const gfx::Rect& old_bounds,
                               const gfx::Rect& new_bounds) {
  if (CommandLine::ForCurrentProcess()->HasSwitch(
          ash::switches::kAshWindowAnimationsDisabled))
    return base::TimeDelta();

  const int min_time_ms = internal::WorkspaceController::IsWorkspace2Enabled() ?
      kWorkspaceSwitchTimeMS : 0;
  int old_area = old_bounds.width() * old_bounds.height();
  int new_area = new_bounds.width() * new_bounds.height();
  int max_area = std::max(old_area, new_area);
  // Avoid divide by zero.
  if (max_area == 0)
    return TimeDelta::FromMilliseconds(min_time_ms);

  int delta_area = std::abs(old_area - new_area);
  // If the area didn't change, the animation is instantaneous.
  if (delta_area == 0)
    return TimeDelta::FromMilliseconds(min_time_ms);

  float factor =
      static_cast<float>(delta_area) / static_cast<float>(max_area);
  const float kRange = internal::kCrossFadeDurationMaxMs -
      internal::kCrossFadeDurationMinMs;
  return TimeDelta::FromMilliseconds(
      internal::Round64(internal::kCrossFadeDurationMinMs + (factor * kRange)));
}

namespace internal {

bool AnimateOnChildWindowVisibilityChanged(aura::Window* window, bool visible) {
  if (window->GetProperty(aura::client::kAnimationsDisabledKey) ||
      CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kAshWindowAnimationsDisabled)) {
    return false;
  }
  if (visible) {
    return AnimateShowWindow(window);
  } else {
    // Don't start hiding the window again if it's already being hidden.
    return window->layer()->GetTargetOpacity() != 0.0f &&
        AnimateHideWindow(window);
  }
}

}  // namespace internal
}  // namespace ash