#include "ash/wm/session_state_animator_impl.h"

#include <vector>

#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/wm/window_animations.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/views/widget/widget.h"

namespace ash {
namespace {

const float kSlowCloseSizeRatio = 0.95f;

const float kPartialFadeRatio = 0.3f;

const float kMinimumScale = 1e-4f;

aura::Window* GetBackground() {
  aura::Window* root_window = Shell::GetPrimaryRootWindow();
  return Shell::GetContainer(root_window,
                             kShellWindowId_DesktopBackgroundContainer);
}

gfx::Transform GetSlowCloseTransform() {
  gfx::Size root_size = Shell::GetPrimaryRootWindow()->bounds().size();
  gfx::Transform transform;
  transform.Translate(
      floor(0.5 * (1.0 - kSlowCloseSizeRatio) * root_size.width() + 0.5),
      floor(0.5 * (1.0 - kSlowCloseSizeRatio) * root_size.height() + 0.5));
  transform.Scale(kSlowCloseSizeRatio, kSlowCloseSizeRatio);
  return transform;
}

gfx::Transform GetFastCloseTransform() {
  gfx::Size root_size = Shell::GetPrimaryRootWindow()->bounds().size();
  gfx::Transform transform;

  transform.Translate(floor(0.5 * root_size.width() + 0.5),
                      floor(0.5 * root_size.height() + 0.5));
  transform.Scale(kMinimumScale, kMinimumScale);
  return transform;
}

void StartSlowCloseAnimationForWindow(aura::Window* window,
                                      base::TimeDelta duration,
                                      ui::LayerAnimationObserver* observer) {
  ui::LayerAnimator* animator = window->layer()->GetAnimator();
  animator->set_preemption_strategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
      ui::LayerAnimationElement::CreateTransformElement(
          GetSlowCloseTransform(),
          duration));
  if (observer)
    sequence->AddObserver(observer);
  animator->StartAnimation(sequence);
}

void StartUndoSlowCloseAnimationForWindow(
    aura::Window* window,
    base::TimeDelta duration,
    ui::LayerAnimationObserver* observer) {
  ui::LayerAnimator* animator = window->layer()->GetAnimator();
  animator->set_preemption_strategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
      ui::LayerAnimationElement::CreateTransformElement(
          gfx::Transform(),
          duration));
  if (observer)
    sequence->AddObserver(observer);
  animator->StartAnimation(sequence);
}

void StartFastCloseAnimationForWindow(aura::Window* window,
                                      base::TimeDelta duration,
                                      ui::LayerAnimationObserver* observer) {
  ui::LayerAnimator* animator = window->layer()->GetAnimator();
  animator->set_preemption_strategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  animator->StartAnimation(
      new ui::LayerAnimationSequence(
          ui::LayerAnimationElement::CreateTransformElement(
              GetFastCloseTransform(), duration)));
  ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
      ui::LayerAnimationElement::CreateOpacityElement(0.0, duration));
  if (observer)
    sequence->AddObserver(observer);
  animator->StartAnimation(sequence);
}

void StartPartialFadeAnimation(aura::Window* window,
                               float target_opacity,
                               base::TimeDelta duration,
                               ui::LayerAnimationObserver* observer) {
  ui::LayerAnimator* animator = window->layer()->GetAnimator();
  animator->set_preemption_strategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
      ui::LayerAnimationElement::CreateOpacityElement(
          target_opacity, duration));
  if (observer)
    sequence->AddObserver(observer);
  animator->StartAnimation(sequence);
}

void StartOpacityAnimationForWindow(aura::Window* window,
                                    float opacity,
                                    base::TimeDelta duration,
                                    ui::LayerAnimationObserver* observer) {
  ui::LayerAnimator* animator = window->layer()->GetAnimator();
  animator->set_preemption_strategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
      ui::LayerAnimationElement::CreateOpacityElement(opacity, duration));
  if (observer)
    sequence->AddObserver(observer);
  animator->StartAnimation(sequence);
}

void HideWindowImmediately(aura::Window* window,
                           ui::LayerAnimationObserver* observer) {
  window->layer()->SetOpacity(0.0);
  if (observer)
    observer->OnLayerAnimationEnded(NULL);
}

void RestoreWindow(aura::Window* window, ui::LayerAnimationObserver* observer) {
  window->layer()->SetTransform(gfx::Transform());
  window->layer()->SetOpacity(1.0);
  if (observer)
    observer->OnLayerAnimationEnded(NULL);
}

void HideWindow(aura::Window* window,
                base::TimeDelta duration,
                bool above,
                ui::LayerAnimationObserver* observer) {
  ui::Layer* layer = window->layer();
  ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());

  settings.SetPreemptionStrategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  settings.SetTransitionDuration(duration);

  settings.SetTweenType(gfx::Tween::EASE_OUT);
  SetTransformForScaleAnimation(layer,
      above ? LAYER_SCALE_ANIMATION_ABOVE : LAYER_SCALE_ANIMATION_BELOW);

  settings.SetTweenType(gfx::Tween::EASE_IN_OUT);
  layer->SetOpacity(0.0f);

  settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
  settings.SetTransitionDuration(base::TimeDelta());
  layer->SetTransform(gfx::Transform());

  if (observer) {
    ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
        ui::LayerAnimationElement::CreateOpacityElement(
            0.0, base::TimeDelta()));
      sequence->AddObserver(observer);
    layer->GetAnimator()->ScheduleAnimation(sequence);
  }
}

void TransformWindowToBaseState(aura::Window* window,
                                base::TimeDelta duration,
                                ui::LayerAnimationObserver* observer) {
  ui::Layer* layer = window->layer();
  ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());

  settings.SetPreemptionStrategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  settings.SetTransitionDuration(duration);

  settings.SetTweenType(gfx::Tween::EASE_OUT);
  layer->SetTransform(gfx::Transform());

  settings.SetTweenType(gfx::Tween::EASE_IN_OUT);
  layer->SetOpacity(1.0f);

  if (observer) {
    ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
        ui::LayerAnimationElement::CreateOpacityElement(
            1.0, base::TimeDelta()));
    sequence->AddObserver(observer);
    layer->GetAnimator()->ScheduleAnimation(sequence);
  }
}

void ShowWindow(aura::Window* window,
                base::TimeDelta duration,
                bool above,
                ui::LayerAnimationObserver* observer) {
  ui::Layer* layer = window->layer();
  ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());

  settings.SetPreemptionStrategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  settings.SetTransitionDuration(base::TimeDelta());
  SetTransformForScaleAnimation(layer,
      above ? LAYER_SCALE_ANIMATION_ABOVE : LAYER_SCALE_ANIMATION_BELOW);

  TransformWindowToBaseState(window, duration, observer);
}

void StartGrayscaleBrightnessAnimationForWindow(
    aura::Window* window,
    float target,
    base::TimeDelta duration,
    gfx::Tween::Type tween_type,
    ui::LayerAnimationObserver* observer) {
  ui::LayerAnimator* animator = window->layer()->GetAnimator();

  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(
          target, duration));
  brightness_element->set_tween_type(tween_type);
  brightness_sequence->AddElement(brightness_element.release());

  scoped_ptr<ui::LayerAnimationElement> grayscale_element(
      ui::LayerAnimationElement::CreateGrayscaleElement(
          target, duration));
  grayscale_element->set_tween_type(tween_type);
  grayscale_sequence->AddElement(grayscale_element.release());

  std::vector<ui::LayerAnimationSequence*> animations;
  animations.push_back(brightness_sequence.release());
  animations.push_back(grayscale_sequence.release());

  if (observer)
    animations[0]->AddObserver(observer);

  animator->set_preemption_strategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);

  animator->StartTogether(animations);
}

class CallbackAnimationObserver : public ui::LayerAnimationObserver {
 public:
  explicit CallbackAnimationObserver(base::Closure callback)
      : callback_(callback) {
  }
  virtual ~CallbackAnimationObserver() {
  }

 private:

  virtual void OnLayerAnimationEnded(ui::LayerAnimationSequence* seq)
      OVERRIDE {

    callback_.Run();
    delete this;
  }

  virtual void OnLayerAnimationAborted(ui::LayerAnimationSequence* seq)
      OVERRIDE {

    callback_.Run();
    delete this;
  }

  virtual void OnLayerAnimationScheduled(ui::LayerAnimationSequence* seq)
      OVERRIDE {}

  base::Closure callback_;

  DISALLOW_COPY_AND_ASSIGN(CallbackAnimationObserver);
};

bool IsLayerAnimated(ui::Layer* layer,
                     SessionStateAnimator::AnimationType type) {
  switch (type) {
    case SessionStateAnimator::ANIMATION_PARTIAL_CLOSE:
      if (layer->GetTargetTransform() != GetSlowCloseTransform())
        return false;
      break;
    case SessionStateAnimator::ANIMATION_UNDO_PARTIAL_CLOSE:
      if (layer->GetTargetTransform() != gfx::Transform())
        return false;
      break;
    case SessionStateAnimator::ANIMATION_FULL_CLOSE:
      if (layer->GetTargetTransform() != GetFastCloseTransform() ||
          layer->GetTargetOpacity() > 0.0001)
        return false;
      break;
    case SessionStateAnimator::ANIMATION_FADE_IN:
      if (layer->GetTargetOpacity() < 0.9999)
        return false;
      break;
    case SessionStateAnimator::ANIMATION_FADE_OUT:
      if (layer->GetTargetOpacity() > 0.0001)
        return false;
      break;
    case SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY:
      if (layer->GetTargetOpacity() > 0.0001)
        return false;
      break;
    case SessionStateAnimator::ANIMATION_RESTORE:
      if (layer->opacity() < 0.9999 || layer->transform() != gfx::Transform())
        return false;
      break;
    case SessionStateAnimator::ANIMATION_GRAYSCALE_BRIGHTNESS:
      if ((layer->GetTargetBrightness() < 0.9999) ||
          (layer->GetTargetGrayscale() < 0.9999))
        return false;
      break;
    case SessionStateAnimator::ANIMATION_UNDO_GRAYSCALE_BRIGHTNESS:
      if ((layer->GetTargetBrightness() > 0.0001) ||
          (layer->GetTargetGrayscale() > 0.0001))
        return false;
      break;
    case SessionStateAnimator::ANIMATION_DROP:
    case SessionStateAnimator::ANIMATION_UNDO_LIFT:

      if (layer->GetTargetOpacity() < 0.9999)
        return false;
      break;

    case SessionStateAnimator::ANIMATION_LIFT:
      if (layer->GetTargetOpacity() > 0.0001)
        return false;
      break;
    case SessionStateAnimator::ANIMATION_RAISE_TO_SCREEN:

      if (layer->GetTargetOpacity() < 0.9999)
        return false;
      break;

    case SessionStateAnimator::ANIMATION_LOWER_BELOW_SCREEN:
      if (layer->GetTargetOpacity() > 0.0001)
        return false;
      break;
    default:
      NOTREACHED() << "Unhandled animation type " << type;
      return false;
  }
  return true;
}

}  

class SessionStateAnimatorImpl::AnimationSequence
  : public SessionStateAnimator::AnimationSequence,
    public ui::LayerAnimationObserver {
 public:
  explicit AnimationSequence(
      SessionStateAnimatorImpl* animator,
      base::Closure callback)
      : SessionStateAnimator::AnimationSequence(callback),
        animator_(animator),
        sequences_attached_(0),
        sequences_completed_(0) {
  }

  virtual void StartAnimation(
      int container_mask,
      SessionStateAnimator::AnimationType type,
      SessionStateAnimator::AnimationSpeed speed) OVERRIDE {
    animator_->StartAnimationInSequence(container_mask, type, speed, this);
  }

 private:
  virtual ~AnimationSequence() {}

  virtual void OnLayerAnimationEnded(
      ui::LayerAnimationSequence* sequence) OVERRIDE {
    sequences_completed_++;
    if (sequences_completed_ == sequences_attached_)
      OnAnimationCompleted();
  }

  virtual void OnLayerAnimationAborted(
      ui::LayerAnimationSequence* sequence) OVERRIDE {
    sequences_completed_++;
    if (sequences_completed_ == sequences_attached_)
      OnAnimationAborted();
  }

  virtual void OnLayerAnimationScheduled(
      ui::LayerAnimationSequence* sequence) OVERRIDE {}

  virtual void OnAttachedToSequence(
      ui::LayerAnimationSequence* sequence) OVERRIDE {
    LayerAnimationObserver::OnAttachedToSequence(sequence);
    sequences_attached_++;
  }

  SessionStateAnimatorImpl* animator_;  

  int sequences_attached_;

  int sequences_completed_;

  DISALLOW_COPY_AND_ASSIGN(AnimationSequence);
};

bool SessionStateAnimatorImpl::TestApi::ContainersAreAnimated(
    int container_mask, AnimationType type) const {
  aura::Window::Windows containers;
  animator_->GetContainers(container_mask, &containers);
  for (aura::Window::Windows::const_iterator it = containers.begin();
       it != containers.end(); ++it) {
    aura::Window* window = *it;
    ui::Layer* layer = window->layer();
    if (!IsLayerAnimated(layer, type))
      return false;
  }
  return true;
}

bool SessionStateAnimatorImpl::TestApi::RootWindowIsAnimated(AnimationType type)
    const {
  aura::Window* root_window = Shell::GetPrimaryRootWindow();
  ui::Layer* layer = root_window->layer();
  return IsLayerAnimated(layer, type);
}

SessionStateAnimatorImpl::SessionStateAnimatorImpl() {
}

SessionStateAnimatorImpl::~SessionStateAnimatorImpl() {
}

void SessionStateAnimatorImpl::GetContainers(int container_mask,
    aura::Window::Windows* containers) {
  aura::Window* root_window = Shell::GetPrimaryRootWindow();
  containers->clear();

  if (container_mask & ROOT_CONTAINER) {
    containers->push_back(Shell::GetPrimaryRootWindow());
  }

  if (container_mask & DESKTOP_BACKGROUND) {
    containers->push_back(Shell::GetContainer(
        root_window, kShellWindowId_DesktopBackgroundContainer));
  }
  if (container_mask & LAUNCHER) {
    containers->push_back(
        Shell::GetContainer(root_window, kShellWindowId_ShelfContainer));
  }
  if (container_mask & NON_LOCK_SCREEN_CONTAINERS) {

    aura::Window* non_lock_screen_containers = Shell::GetContainer(
        root_window, kShellWindowId_NonLockScreenContainersContainer);
    aura::Window::Windows children = non_lock_screen_containers->children();

    for (aura::Window::Windows::const_iterator it = children.begin();
         it != children.end(); ++it) {
      aura::Window* window = *it;
      if (window->id() == kShellWindowId_ShelfContainer)
        continue;
      containers->push_back(window);
    }
  }
  if (container_mask & LOCK_SCREEN_BACKGROUND) {
    containers->push_back(Shell::GetContainer(
        root_window, kShellWindowId_LockScreenBackgroundContainer));
  }
  if (container_mask & LOCK_SCREEN_CONTAINERS) {
    containers->push_back(Shell::GetContainer(
        root_window, kShellWindowId_LockScreenContainersContainer));
  }
  if (container_mask & LOCK_SCREEN_RELATED_CONTAINERS) {
    containers->push_back(Shell::GetContainer(
        root_window, kShellWindowId_LockScreenRelatedContainersContainer));
  }
}

void SessionStateAnimatorImpl::StartAnimation(int container_mask,
                                              AnimationType type,
                                              AnimationSpeed speed) {
  aura::Window::Windows containers;
  GetContainers(container_mask, &containers);
  for (aura::Window::Windows::const_iterator it = containers.begin();
       it != containers.end(); ++it) {
    RunAnimationForWindow(*it, type, speed, NULL);
  }
}

void SessionStateAnimatorImpl::StartAnimationWithCallback(
    int container_mask,
    AnimationType type,
    AnimationSpeed speed,
    base::Closure callback) {
  aura::Window::Windows containers;
  GetContainers(container_mask, &containers);
  for (aura::Window::Windows::const_iterator it = containers.begin();
       it != containers.end(); ++it) {
    ui::LayerAnimationObserver* observer =
        new CallbackAnimationObserver(callback);
    RunAnimationForWindow(*it, type, speed, observer);
  }
}

SessionStateAnimator::AnimationSequence*
    SessionStateAnimatorImpl::BeginAnimationSequence(base::Closure callback) {
  return new AnimationSequence(this, callback);
}

bool SessionStateAnimatorImpl::IsBackgroundHidden() const {
  return !GetBackground()->IsVisible();
}

void SessionStateAnimatorImpl::ShowBackground() {
  ui::ScopedLayerAnimationSettings settings(
      GetBackground()->layer()->GetAnimator());
  settings.SetTransitionDuration(base::TimeDelta());
  GetBackground()->Show();
}

void SessionStateAnimatorImpl::HideBackground() {
  ui::ScopedLayerAnimationSettings settings(
      GetBackground()->layer()->GetAnimator());
  settings.SetTransitionDuration(base::TimeDelta());
  GetBackground()->Hide();
}

void SessionStateAnimatorImpl::StartAnimationInSequence(
    int container_mask,
    AnimationType type,
    AnimationSpeed speed,
    AnimationSequence* observer) {
  aura::Window::Windows containers;
  GetContainers(container_mask, &containers);
  for (aura::Window::Windows::const_iterator it = containers.begin();
       it != containers.end(); ++it) {
    RunAnimationForWindow(*it, type, speed, observer);
  }
}

void SessionStateAnimatorImpl::RunAnimationForWindow(
    aura::Window* window,
    AnimationType type,
    AnimationSpeed speed,
    ui::LayerAnimationObserver* observer) {
  base::TimeDelta duration = GetDuration(speed);

  switch (type) {
    case ANIMATION_PARTIAL_CLOSE:
      StartSlowCloseAnimationForWindow(window, duration, observer);
      break;
    case ANIMATION_UNDO_PARTIAL_CLOSE:
      StartUndoSlowCloseAnimationForWindow(window, duration, observer);
      break;
    case ANIMATION_FULL_CLOSE:
      StartFastCloseAnimationForWindow(window, duration, observer);
      break;
    case ANIMATION_FADE_IN:
      StartOpacityAnimationForWindow(window, 1.0, duration, observer);
      break;
    case ANIMATION_FADE_OUT:
      StartOpacityAnimationForWindow(window, 0.0, duration, observer);
      break;
    case ANIMATION_HIDE_IMMEDIATELY:
      DCHECK_EQ(speed, ANIMATION_SPEED_IMMEDIATE);
      HideWindowImmediately(window, observer);
      break;
    case ANIMATION_RESTORE:
      DCHECK_EQ(speed, ANIMATION_SPEED_IMMEDIATE);
      RestoreWindow(window, observer);
      break;
    case ANIMATION_LIFT:
      HideWindow(window, duration, true, observer);
      break;
    case ANIMATION_DROP:
      ShowWindow(window, duration, true, observer);
      break;
    case ANIMATION_UNDO_LIFT:
      TransformWindowToBaseState(window, duration, observer);
      break;
    case ANIMATION_RAISE_TO_SCREEN:
      ShowWindow(window, duration, false, observer);
      break;
    case ANIMATION_LOWER_BELOW_SCREEN:
      HideWindow(window, duration, false, observer);
      break;
    case ANIMATION_PARTIAL_FADE_IN:
      StartPartialFadeAnimation(
          window, kPartialFadeRatio, duration, observer);
      break;
    case ANIMATION_UNDO_PARTIAL_FADE_IN:
      StartPartialFadeAnimation(window, 0.0, duration, observer);
      break;
    case ANIMATION_FULL_FADE_IN:
      StartPartialFadeAnimation(window, 1.0, duration, observer);
      break;
    case ANIMATION_GRAYSCALE_BRIGHTNESS:
      StartGrayscaleBrightnessAnimationForWindow(
          window, 1.0, duration, gfx::Tween::EASE_IN, observer);
      break;
    case ANIMATION_UNDO_GRAYSCALE_BRIGHTNESS:
      StartGrayscaleBrightnessAnimationForWindow(
          window, 0.0, duration, gfx::Tween::EASE_IN_OUT, observer);
      break;
  }
}

}
// 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/session_state_animator_impl.h"

#include <vector>

#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/wm/window_animations.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/views/widget/widget.h"

namespace ash {
namespace {

// Slightly-smaller size that we scale the screen down to for the pre-lock and
// pre-shutdown states.
const float kSlowCloseSizeRatio = 0.95f;

// Maximum opacity of white layer when animating pre-shutdown state.
const float kPartialFadeRatio = 0.3f;

// Minimum size. Not zero as it causes numeric issues.
const float kMinimumScale = 1e-4f;

// Returns the primary root window's container.
aura::Window* GetBackground() {
  aura::Window* root_window = Shell::GetPrimaryRootWindow();
  return Shell::GetContainer(root_window,
                             kShellWindowId_DesktopBackgroundContainer);
}

// Returns the transform that should be applied to containers for the slow-close
// animation.
gfx::Transform GetSlowCloseTransform() {
  gfx::Size root_size = Shell::GetPrimaryRootWindow()->bounds().size();
  gfx::Transform transform;
  transform.Translate(
      floor(0.5 * (1.0 - kSlowCloseSizeRatio) * root_size.width() + 0.5),
      floor(0.5 * (1.0 - kSlowCloseSizeRatio) * root_size.height() + 0.5));
  transform.Scale(kSlowCloseSizeRatio, kSlowCloseSizeRatio);
  return transform;
}

// Returns the transform that should be applied to containers for the fast-close
// animation.
gfx::Transform GetFastCloseTransform() {
  gfx::Size root_size = Shell::GetPrimaryRootWindow()->bounds().size();
  gfx::Transform transform;

  transform.Translate(floor(0.5 * root_size.width() + 0.5),
                      floor(0.5 * root_size.height() + 0.5));
  transform.Scale(kMinimumScale, kMinimumScale);
  return transform;
}

// Slowly shrinks |window| to a slightly-smaller size.
void StartSlowCloseAnimationForWindow(aura::Window* window,
                                      base::TimeDelta duration,
                                      ui::LayerAnimationObserver* observer) {
  ui::LayerAnimator* animator = window->layer()->GetAnimator();
  animator->set_preemption_strategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
      ui::LayerAnimationElement::CreateTransformElement(
          GetSlowCloseTransform(),
          duration));
  if (observer)
    sequence->AddObserver(observer);
  animator->StartAnimation(sequence);
}

// Quickly undoes the effects of the slow-close animation on |window|.
void StartUndoSlowCloseAnimationForWindow(
    aura::Window* window,
    base::TimeDelta duration,
    ui::LayerAnimationObserver* observer) {
  ui::LayerAnimator* animator = window->layer()->GetAnimator();
  animator->set_preemption_strategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
      ui::LayerAnimationElement::CreateTransformElement(
          gfx::Transform(),
          duration));
  if (observer)
    sequence->AddObserver(observer);
  animator->StartAnimation(sequence);
}

// Quickly shrinks |window| down to a point in the center of the screen and
// fades it out to 0 opacity.
void StartFastCloseAnimationForWindow(aura::Window* window,
                                      base::TimeDelta duration,
                                      ui::LayerAnimationObserver* observer) {
  ui::LayerAnimator* animator = window->layer()->GetAnimator();
  animator->set_preemption_strategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  animator->StartAnimation(
      new ui::LayerAnimationSequence(
          ui::LayerAnimationElement::CreateTransformElement(
              GetFastCloseTransform(), duration)));
  ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
      ui::LayerAnimationElement::CreateOpacityElement(0.0, duration));
  if (observer)
    sequence->AddObserver(observer);
  animator->StartAnimation(sequence);
}

// Fades |window| to |target_opacity| over |duration|.
void StartPartialFadeAnimation(aura::Window* window,
                               float target_opacity,
                               base::TimeDelta duration,
                               ui::LayerAnimationObserver* observer) {
  ui::LayerAnimator* animator = window->layer()->GetAnimator();
  animator->set_preemption_strategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
      ui::LayerAnimationElement::CreateOpacityElement(
          target_opacity, duration));
  if (observer)
    sequence->AddObserver(observer);
  animator->StartAnimation(sequence);
}

// Fades |window| to |opacity| over |duration|.
void StartOpacityAnimationForWindow(aura::Window* window,
                                    float opacity,
                                    base::TimeDelta duration,
                                    ui::LayerAnimationObserver* observer) {
  ui::LayerAnimator* animator = window->layer()->GetAnimator();
  animator->set_preemption_strategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
      ui::LayerAnimationElement::CreateOpacityElement(opacity, duration));
  if (observer)
    sequence->AddObserver(observer);
  animator->StartAnimation(sequence);
}

// Makes |window| fully transparent instantaneously.
void HideWindowImmediately(aura::Window* window,
                           ui::LayerAnimationObserver* observer) {
  window->layer()->SetOpacity(0.0);
  if (observer)
    observer->OnLayerAnimationEnded(NULL);
}

// Restores |window| to its original position and scale and full opacity
// instantaneously.
void RestoreWindow(aura::Window* window, ui::LayerAnimationObserver* observer) {
  window->layer()->SetTransform(gfx::Transform());
  window->layer()->SetOpacity(1.0);
  if (observer)
    observer->OnLayerAnimationEnded(NULL);
}

void HideWindow(aura::Window* window,
                base::TimeDelta duration,
                bool above,
                ui::LayerAnimationObserver* observer) {
  ui::Layer* layer = window->layer();
  ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());

  settings.SetPreemptionStrategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  settings.SetTransitionDuration(duration);

  settings.SetTweenType(gfx::Tween::EASE_OUT);
  SetTransformForScaleAnimation(layer,
      above ? LAYER_SCALE_ANIMATION_ABOVE : LAYER_SCALE_ANIMATION_BELOW);

  settings.SetTweenType(gfx::Tween::EASE_IN_OUT);
  layer->SetOpacity(0.0f);

  // 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());
  layer->SetTransform(gfx::Transform());

  // A bit of a dirty trick: we need to catch the end of the animation we don't
  // control. So we use two facts we know: which animator will be used and the
  // target opacity to add "Do nothing" animation sequence.
  // Unfortunately, we can not just use empty LayerAnimationSequence, because
  // it does not call NotifyEnded().
  if (observer) {
    ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
        ui::LayerAnimationElement::CreateOpacityElement(
            0.0, base::TimeDelta()));
      sequence->AddObserver(observer);
    layer->GetAnimator()->ScheduleAnimation(sequence);
  }
}

// Animates |window| to identity transform and full opacity over |duration|.
void TransformWindowToBaseState(aura::Window* window,
                                base::TimeDelta duration,
                                ui::LayerAnimationObserver* observer) {
  ui::Layer* layer = window->layer();
  ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());

  // Animate to target values.
  settings.SetPreemptionStrategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  settings.SetTransitionDuration(duration);

  settings.SetTweenType(gfx::Tween::EASE_OUT);
  layer->SetTransform(gfx::Transform());

  settings.SetTweenType(gfx::Tween::EASE_IN_OUT);
  layer->SetOpacity(1.0f);

  // A bit of a dirty trick: we need to catch the end of the animation we don't
  // control. So we use two facts we know: which animator will be used and the
  // target opacity to add "Do nothing" animation sequence.
  // Unfortunately, we can not just use empty LayerAnimationSequence, because
  // it does not call NotifyEnded().
  if (observer) {
    ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
        ui::LayerAnimationElement::CreateOpacityElement(
            1.0, base::TimeDelta()));
    sequence->AddObserver(observer);
    layer->GetAnimator()->ScheduleAnimation(sequence);
  }
}

void ShowWindow(aura::Window* window,
                base::TimeDelta duration,
                bool above,
                ui::LayerAnimationObserver* observer) {
  ui::Layer* layer = window->layer();
  ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());

  // Set initial state of animation
  settings.SetPreemptionStrategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  settings.SetTransitionDuration(base::TimeDelta());
  SetTransformForScaleAnimation(layer,
      above ? LAYER_SCALE_ANIMATION_ABOVE : LAYER_SCALE_ANIMATION_BELOW);

  TransformWindowToBaseState(window, duration, observer);
}

// Starts grayscale/brightness animation for |window| over |duration|. Target
// value for both grayscale and brightness are specified by |target|.
void StartGrayscaleBrightnessAnimationForWindow(
    aura::Window* window,
    float target,
    base::TimeDelta duration,
    gfx::Tween::Type tween_type,
    ui::LayerAnimationObserver* observer) {
  ui::LayerAnimator* animator = window->layer()->GetAnimator();

  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(
          target, duration));
  brightness_element->set_tween_type(tween_type);
  brightness_sequence->AddElement(brightness_element.release());

  scoped_ptr<ui::LayerAnimationElement> grayscale_element(
      ui::LayerAnimationElement::CreateGrayscaleElement(
          target, duration));
  grayscale_element->set_tween_type(tween_type);
  grayscale_sequence->AddElement(grayscale_element.release());

  std::vector<ui::LayerAnimationSequence*> animations;
  animations.push_back(brightness_sequence.release());
  animations.push_back(grayscale_sequence.release());

  if (observer)
    animations[0]->AddObserver(observer);

  animator->set_preemption_strategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);

  animator->StartTogether(animations);
}

// Animation observer that will drop animated foreground once animation is
// finished. It is used in when undoing shutdown animation.
class CallbackAnimationObserver : public ui::LayerAnimationObserver {
 public:
  explicit CallbackAnimationObserver(base::Closure callback)
      : callback_(callback) {
  }
  virtual ~CallbackAnimationObserver() {
  }

 private:
  // Overridden from ui::LayerAnimationObserver:
  virtual void OnLayerAnimationEnded(ui::LayerAnimationSequence* seq)
      OVERRIDE {
    // Drop foreground once animation is over.
    callback_.Run();
    delete this;
  }

  virtual void OnLayerAnimationAborted(ui::LayerAnimationSequence* seq)
      OVERRIDE {
    // Drop foreground once animation is over.
    callback_.Run();
    delete this;
  }

  virtual void OnLayerAnimationScheduled(ui::LayerAnimationSequence* seq)
      OVERRIDE {}

  base::Closure callback_;

  DISALLOW_COPY_AND_ASSIGN(CallbackAnimationObserver);
};


bool IsLayerAnimated(ui::Layer* layer,
                     SessionStateAnimator::AnimationType type) {
  switch (type) {
    case SessionStateAnimator::ANIMATION_PARTIAL_CLOSE:
      if (layer->GetTargetTransform() != GetSlowCloseTransform())
        return false;
      break;
    case SessionStateAnimator::ANIMATION_UNDO_PARTIAL_CLOSE:
      if (layer->GetTargetTransform() != gfx::Transform())
        return false;
      break;
    case SessionStateAnimator::ANIMATION_FULL_CLOSE:
      if (layer->GetTargetTransform() != GetFastCloseTransform() ||
          layer->GetTargetOpacity() > 0.0001)
        return false;
      break;
    case SessionStateAnimator::ANIMATION_FADE_IN:
      if (layer->GetTargetOpacity() < 0.9999)
        return false;
      break;
    case SessionStateAnimator::ANIMATION_FADE_OUT:
      if (layer->GetTargetOpacity() > 0.0001)
        return false;
      break;
    case SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY:
      if (layer->GetTargetOpacity() > 0.0001)
        return false;
      break;
    case SessionStateAnimator::ANIMATION_RESTORE:
      if (layer->opacity() < 0.9999 || layer->transform() != gfx::Transform())
        return false;
      break;
    case SessionStateAnimator::ANIMATION_GRAYSCALE_BRIGHTNESS:
      if ((layer->GetTargetBrightness() < 0.9999) ||
          (layer->GetTargetGrayscale() < 0.9999))
        return false;
      break;
    case SessionStateAnimator::ANIMATION_UNDO_GRAYSCALE_BRIGHTNESS:
      if ((layer->GetTargetBrightness() > 0.0001) ||
          (layer->GetTargetGrayscale() > 0.0001))
        return false;
      break;
    case SessionStateAnimator::ANIMATION_DROP:
    case SessionStateAnimator::ANIMATION_UNDO_LIFT:
      //ToDo(antim) : check other effects
      if (layer->GetTargetOpacity() < 0.9999)
        return false;
      break;
      //ToDo(antim) : check other effects
    case SessionStateAnimator::ANIMATION_LIFT:
      if (layer->GetTargetOpacity() > 0.0001)
        return false;
      break;
    case SessionStateAnimator::ANIMATION_RAISE_TO_SCREEN:
      //ToDo(antim) : check other effects
      if (layer->GetTargetOpacity() < 0.9999)
        return false;
      break;
      //ToDo(antim) : check other effects
    case SessionStateAnimator::ANIMATION_LOWER_BELOW_SCREEN:
      if (layer->GetTargetOpacity() > 0.0001)
        return false;
      break;
    default:
      NOTREACHED() << "Unhandled animation type " << type;
      return false;
  }
  return true;
}

}  // namespace

// This observer is intended to use in cases when some action has to be taken
// once some animation successfully completes (i.e. it was not aborted).
// Observer will count a number of sequences it is attached to, and a number of
// finished sequences (either Ended or Aborted). Once these two numbers are
// equal, observer will delete itself, calling callback passed to constructor if
// there were no aborted animations.
// This way it can be either used to wait for some animation to be finished in
// multiple layers, to wait once a sequence of animations is finished in one
// layer or the mixture of both.
class SessionStateAnimatorImpl::AnimationSequence
  : public SessionStateAnimator::AnimationSequence,
    public ui::LayerAnimationObserver {
 public:
  explicit AnimationSequence(
      SessionStateAnimatorImpl* animator,
      base::Closure callback)
      : SessionStateAnimator::AnimationSequence(callback),
        animator_(animator),
        sequences_attached_(0),
        sequences_completed_(0) {
  }

  // SessionStateAnimator::AnimationSequence:
  virtual void StartAnimation(
      int container_mask,
      SessionStateAnimator::AnimationType type,
      SessionStateAnimator::AnimationSpeed speed) OVERRIDE {
    animator_->StartAnimationInSequence(container_mask, type, speed, this);
  }

 private:
  virtual ~AnimationSequence() {}

  // ui::LayerAnimationObserver:
  virtual void OnLayerAnimationEnded(
      ui::LayerAnimationSequence* sequence) OVERRIDE {
    sequences_completed_++;
    if (sequences_completed_ == sequences_attached_)
      OnAnimationCompleted();
  }

  virtual void OnLayerAnimationAborted(
      ui::LayerAnimationSequence* sequence) OVERRIDE {
    sequences_completed_++;
    if (sequences_completed_ == sequences_attached_)
      OnAnimationAborted();
  }

  virtual void OnLayerAnimationScheduled(
      ui::LayerAnimationSequence* sequence) OVERRIDE {}

  virtual void OnAttachedToSequence(
      ui::LayerAnimationSequence* sequence) OVERRIDE {
    LayerAnimationObserver::OnAttachedToSequence(sequence);
    sequences_attached_++;
  }

  SessionStateAnimatorImpl* animator_;  // not owned

  // Number of sequences this observer was attached to.
  int sequences_attached_;

  // Number of sequences either ended or aborted.
  int sequences_completed_;

  DISALLOW_COPY_AND_ASSIGN(AnimationSequence);
};

bool SessionStateAnimatorImpl::TestApi::ContainersAreAnimated(
    int container_mask, AnimationType type) const {
  aura::Window::Windows containers;
  animator_->GetContainers(container_mask, &containers);
  for (aura::Window::Windows::const_iterator it = containers.begin();
       it != containers.end(); ++it) {
    aura::Window* window = *it;
    ui::Layer* layer = window->layer();
    if (!IsLayerAnimated(layer, type))
      return false;
  }
  return true;
}

bool SessionStateAnimatorImpl::TestApi::RootWindowIsAnimated(AnimationType type)
    const {
  aura::Window* root_window = Shell::GetPrimaryRootWindow();
  ui::Layer* layer = root_window->layer();
  return IsLayerAnimated(layer, type);
}

SessionStateAnimatorImpl::SessionStateAnimatorImpl() {
}

SessionStateAnimatorImpl::~SessionStateAnimatorImpl() {
}

// Fills |containers| with the containers described by |container_mask|.
void SessionStateAnimatorImpl::GetContainers(int container_mask,
    aura::Window::Windows* containers) {
  aura::Window* root_window = Shell::GetPrimaryRootWindow();
  containers->clear();

  if (container_mask & ROOT_CONTAINER) {
    containers->push_back(Shell::GetPrimaryRootWindow());
  }

  if (container_mask & DESKTOP_BACKGROUND) {
    containers->push_back(Shell::GetContainer(
        root_window, kShellWindowId_DesktopBackgroundContainer));
  }
  if (container_mask & LAUNCHER) {
    containers->push_back(
        Shell::GetContainer(root_window, kShellWindowId_ShelfContainer));
  }
  if (container_mask & NON_LOCK_SCREEN_CONTAINERS) {
    // TODO(antrim): Figure out a way to eliminate a need to exclude launcher
    // in such way.
    aura::Window* non_lock_screen_containers = Shell::GetContainer(
        root_window, kShellWindowId_NonLockScreenContainersContainer);
    aura::Window::Windows children = non_lock_screen_containers->children();

    for (aura::Window::Windows::const_iterator it = children.begin();
         it != children.end(); ++it) {
      aura::Window* window = *it;
      if (window->id() == kShellWindowId_ShelfContainer)
        continue;
      containers->push_back(window);
    }
  }
  if (container_mask & LOCK_SCREEN_BACKGROUND) {
    containers->push_back(Shell::GetContainer(
        root_window, kShellWindowId_LockScreenBackgroundContainer));
  }
  if (container_mask & LOCK_SCREEN_CONTAINERS) {
    containers->push_back(Shell::GetContainer(
        root_window, kShellWindowId_LockScreenContainersContainer));
  }
  if (container_mask & LOCK_SCREEN_RELATED_CONTAINERS) {
    containers->push_back(Shell::GetContainer(
        root_window, kShellWindowId_LockScreenRelatedContainersContainer));
  }
}

void SessionStateAnimatorImpl::StartAnimation(int container_mask,
                                              AnimationType type,
                                              AnimationSpeed speed) {
  aura::Window::Windows containers;
  GetContainers(container_mask, &containers);
  for (aura::Window::Windows::const_iterator it = containers.begin();
       it != containers.end(); ++it) {
    RunAnimationForWindow(*it, type, speed, NULL);
  }
}

void SessionStateAnimatorImpl::StartAnimationWithCallback(
    int container_mask,
    AnimationType type,
    AnimationSpeed speed,
    base::Closure callback) {
  aura::Window::Windows containers;
  GetContainers(container_mask, &containers);
  for (aura::Window::Windows::const_iterator it = containers.begin();
       it != containers.end(); ++it) {
    ui::LayerAnimationObserver* observer =
        new CallbackAnimationObserver(callback);
    RunAnimationForWindow(*it, type, speed, observer);
  }
}

SessionStateAnimator::AnimationSequence*
    SessionStateAnimatorImpl::BeginAnimationSequence(base::Closure callback) {
  return new AnimationSequence(this, callback);
}

bool SessionStateAnimatorImpl::IsBackgroundHidden() const {
  return !GetBackground()->IsVisible();
}

void SessionStateAnimatorImpl::ShowBackground() {
  ui::ScopedLayerAnimationSettings settings(
      GetBackground()->layer()->GetAnimator());
  settings.SetTransitionDuration(base::TimeDelta());
  GetBackground()->Show();
}

void SessionStateAnimatorImpl::HideBackground() {
  ui::ScopedLayerAnimationSettings settings(
      GetBackground()->layer()->GetAnimator());
  settings.SetTransitionDuration(base::TimeDelta());
  GetBackground()->Hide();
}

void SessionStateAnimatorImpl::StartAnimationInSequence(
    int container_mask,
    AnimationType type,
    AnimationSpeed speed,
    AnimationSequence* observer) {
  aura::Window::Windows containers;
  GetContainers(container_mask, &containers);
  for (aura::Window::Windows::const_iterator it = containers.begin();
       it != containers.end(); ++it) {
    RunAnimationForWindow(*it, type, speed, observer);
  }
}

void SessionStateAnimatorImpl::RunAnimationForWindow(
    aura::Window* window,
    AnimationType type,
    AnimationSpeed speed,
    ui::LayerAnimationObserver* observer) {
  base::TimeDelta duration = GetDuration(speed);

  switch (type) {
    case ANIMATION_PARTIAL_CLOSE:
      StartSlowCloseAnimationForWindow(window, duration, observer);
      break;
    case ANIMATION_UNDO_PARTIAL_CLOSE:
      StartUndoSlowCloseAnimationForWindow(window, duration, observer);
      break;
    case ANIMATION_FULL_CLOSE:
      StartFastCloseAnimationForWindow(window, duration, observer);
      break;
    case ANIMATION_FADE_IN:
      StartOpacityAnimationForWindow(window, 1.0, duration, observer);
      break;
    case ANIMATION_FADE_OUT:
      StartOpacityAnimationForWindow(window, 0.0, duration, observer);
      break;
    case ANIMATION_HIDE_IMMEDIATELY:
      DCHECK_EQ(speed, ANIMATION_SPEED_IMMEDIATE);
      HideWindowImmediately(window, observer);
      break;
    case ANIMATION_RESTORE:
      DCHECK_EQ(speed, ANIMATION_SPEED_IMMEDIATE);
      RestoreWindow(window, observer);
      break;
    case ANIMATION_LIFT:
      HideWindow(window, duration, true, observer);
      break;
    case ANIMATION_DROP:
      ShowWindow(window, duration, true, observer);
      break;
    case ANIMATION_UNDO_LIFT:
      TransformWindowToBaseState(window, duration, observer);
      break;
    case ANIMATION_RAISE_TO_SCREEN:
      ShowWindow(window, duration, false, observer);
      break;
    case ANIMATION_LOWER_BELOW_SCREEN:
      HideWindow(window, duration, false, observer);
      break;
    case ANIMATION_PARTIAL_FADE_IN:
      StartPartialFadeAnimation(
          window, kPartialFadeRatio, duration, observer);
      break;
    case ANIMATION_UNDO_PARTIAL_FADE_IN:
      StartPartialFadeAnimation(window, 0.0, duration, observer);
      break;
    case ANIMATION_FULL_FADE_IN:
      StartPartialFadeAnimation(window, 1.0, duration, observer);
      break;
    case ANIMATION_GRAYSCALE_BRIGHTNESS:
      StartGrayscaleBrightnessAnimationForWindow(
          window, 1.0, duration, gfx::Tween::EASE_IN, observer);
      break;
    case ANIMATION_UNDO_GRAYSCALE_BRIGHTNESS:
      StartGrayscaleBrightnessAnimationForWindow(
          window, 0.0, duration, gfx::Tween::EASE_IN_OUT, observer);
      break;
  }
}

}  // namespace ash
