// 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/display/output_configurator_animation.h"

#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "base/bind.h"
#include "base/stl_util.h"
#include "base/time/time.h"
#include "ui/aura/root_window.h"
#include "ui/aura/window.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"

namespace ash {
namespace internal {
namespace {

const int kFadingAnimationDurationInMS = 200;
const int kFadingTimeoutDurationInSeconds = 10;

// CallbackRunningObserver accepts multiple layer animations and
// runs the specified |callback| when all of the animations have finished.
class CallbackRunningObserver {
 public:
  CallbackRunningObserver(base::Closure callback)
      : completed_counter_(0),
        animation_aborted_(false),
        callback_(callback) {}

  void AddNewAnimator(ui::LayerAnimator* animator) {
    Observer* observer = new Observer(animator, this);
    animator->AddObserver(observer);
    observer_list_.push_back(observer);
  }

 private:
  void OnSingleTaskCompleted() {
    completed_counter_++;
    if (completed_counter_ >= observer_list_.size()) {
      base::MessageLoopForUI::current()->DeleteSoon(FROM_HERE, this);
      if (!animation_aborted_)
        base::MessageLoopForUI::current()->PostTask(FROM_HERE, callback_);
    }
  }

  void OnSingleTaskAborted() {
    animation_aborted_ = true;
    OnSingleTaskCompleted();
  }

  // The actual observer to listen each animation completion.
  class Observer : public ui::LayerAnimationObserver {
   public:
    Observer(ui::LayerAnimator* animator,
             CallbackRunningObserver* observer)
        : animator_(animator),
          observer_(observer) {}

   protected:
    // ui::LayerAnimationObserver overrides:
    virtual void OnLayerAnimationEnded(
        ui::LayerAnimationSequence* sequence) OVERRIDE {
      animator_->RemoveObserver(this);
      observer_->OnSingleTaskCompleted();
    }
    virtual void OnLayerAnimationAborted(
        ui::LayerAnimationSequence* sequence) OVERRIDE {
      animator_->RemoveObserver(this);
      observer_->OnSingleTaskAborted();
    }
    virtual void OnLayerAnimationScheduled(
        ui::LayerAnimationSequence* sequence) OVERRIDE {
    }
    virtual bool RequiresNotificationWhenAnimatorDestroyed() const OVERRIDE {
      return true;
    }

   private:
    ui::LayerAnimator* animator_;
    CallbackRunningObserver* observer_;

    DISALLOW_COPY_AND_ASSIGN(Observer);
  };

  size_t completed_counter_;
  bool animation_aborted_;
  ScopedVector<Observer> observer_list_;
  base::Closure callback_;

  DISALLOW_COPY_AND_ASSIGN(CallbackRunningObserver);
};

}  // namespace

OutputConfiguratorAnimation::OutputConfiguratorAnimation() {
}

OutputConfiguratorAnimation::~OutputConfiguratorAnimation() {
  ClearHidingLayers();
}

void OutputConfiguratorAnimation::StartFadeOutAnimation(
    base::Closure callback) {
  CallbackRunningObserver* observer = new CallbackRunningObserver(callback);
  ClearHidingLayers();

  // Make the fade-out animation for all root windows.  Instead of actually
  // hiding the root windows, we put a black layer over a root window for
  // safety.  These layers remain to hide root windows and will be deleted
  // after the animation of OnDisplayModeChanged().
  Shell::RootWindowList root_windows =
      Shell::GetInstance()->GetAllRootWindows();
  for (Shell::RootWindowList::const_iterator it = root_windows.begin();
       it != root_windows.end(); ++it) {
    aura::RootWindow* root_window = *it;
    ui::Layer* hiding_layer = new ui::Layer(ui::LAYER_SOLID_COLOR);
    hiding_layer->SetColor(SK_ColorBLACK);
    hiding_layer->SetBounds(root_window->bounds());
    ui::Layer* parent = ash::Shell::GetContainer(
        root_window,
        ash::internal::kShellWindowId_OverlayContainer)->layer();
    parent->Add(hiding_layer);

    hiding_layer->SetOpacity(0.0);

    ui::ScopedLayerAnimationSettings settings(hiding_layer->GetAnimator());
    settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
        kFadingAnimationDurationInMS));
    observer->AddNewAnimator(hiding_layer->GetAnimator());
    hiding_layer->SetOpacity(1.0f);
    hiding_layer->SetVisible(true);
    hiding_layers_[root_window] = hiding_layer;
  }

  // In case that OnDisplayModeChanged() isn't called or its animator is
  // canceled due to some unknown errors, we set a timer to clear these
  // hiding layers.
  timer_.reset(new base::OneShotTimer<OutputConfiguratorAnimation>());
  timer_->Start(FROM_HERE,
                base::TimeDelta::FromSeconds(kFadingTimeoutDurationInSeconds),
                this,
                &OutputConfiguratorAnimation::ClearHidingLayers);
}

void OutputConfiguratorAnimation::StartFadeInAnimation() {
  // We want to make sure clearing all of hiding layers after the animation
  // finished.  Note that this callback can be canceled, but the cancel only
  // happens when the next animation is scheduled.  Thus the hiding layers
  // should be deleted eventually.
  CallbackRunningObserver* observer = new CallbackRunningObserver(
      base::Bind(&OutputConfiguratorAnimation::ClearHidingLayers,
                 base::Unretained(this)));

  // Ensure that layers are not animating.
  for (std::map<aura::RootWindow*, ui::Layer*>::iterator it =
           hiding_layers_.begin(); it != hiding_layers_.end(); ++it) {
    ui::LayerAnimator* animator = it->second->GetAnimator();
    if (animator->is_animating())
      animator->StopAnimating();
  }

  // Schedules the fade-in effect for all root windows.  Because we put the
  // black layers for fade-out, here we actually turn those black layers
  // invisible.
  Shell::RootWindowList root_windows =
      Shell::GetInstance()->GetAllRootWindows();
  for (Shell::RootWindowList::const_iterator it = root_windows.begin();
       it != root_windows.end(); ++it) {
    aura::RootWindow* root_window = *it;
    ui::Layer* hiding_layer = NULL;
    if (hiding_layers_.find(root_window) == hiding_layers_.end()) {
      // In case of the transition from mirroring->non-mirroring, new root
      // windows appear and we do not have the black layers for them.  Thus
      // we need to create the layer and make it visible.
      hiding_layer = new ui::Layer(ui::LAYER_SOLID_COLOR);
      hiding_layer->SetColor(SK_ColorBLACK);
      hiding_layer->SetBounds(root_window->bounds());
      ui::Layer* parent = ash::Shell::GetContainer(
          root_window,
          ash::internal::kShellWindowId_OverlayContainer)->layer();
      parent->Add(hiding_layer);
      hiding_layer->SetOpacity(1.0f);
      hiding_layer->SetVisible(true);
      hiding_layers_[root_window] = hiding_layer;
    } else {
      hiding_layer = hiding_layers_[root_window];
      if (hiding_layer->bounds() != root_window->bounds())
        hiding_layer->SetBounds(root_window->bounds());
    }

    ui::ScopedLayerAnimationSettings settings(hiding_layer->GetAnimator());
    settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
        kFadingAnimationDurationInMS));
    observer->AddNewAnimator(hiding_layer->GetAnimator());
    hiding_layer->SetOpacity(0.0f);
    hiding_layer->SetVisible(false);
  }
}

void OutputConfiguratorAnimation::OnDisplayModeChanged(
    const std::vector<chromeos::OutputConfigurator::OutputSnapshot>& outputs) {
  if (!hiding_layers_.empty())
    StartFadeInAnimation();
}

void OutputConfiguratorAnimation::OnDisplayModeChangeFailed(
    chromeos::OutputState failed_new_state) {
  if (!hiding_layers_.empty())
    StartFadeInAnimation();
}

void OutputConfiguratorAnimation::ClearHidingLayers() {
  if (timer_) {
    timer_->Stop();
    timer_.reset();
  }
  STLDeleteContainerPairSecondPointers(
      hiding_layers_.begin(), hiding_layers_.end());
  hiding_layers_.clear();
}

}  // namespace internal
}  // namespace ash
