// Copyright 2014 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 "athena/wm/window_overview_mode.h"

#include <algorithm>
#include <functional>
#include <vector>

#include "base/macros.h"
#include "ui/aura/scoped_window_targeter.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/aura/window_property.h"
#include "ui/aura/window_targeter.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/events/event_handler.h"
#include "ui/gfx/transform.h"
#include "ui/wm/core/shadow.h"

namespace {

struct WindowOverviewState {
  // The transform for when the window is at the topmost position.
  gfx::Transform top;

  // The transform for when the window is at the bottom-most position.
  gfx::Transform bottom;

  // The current overview state of the window. 0.f means the window is at the
  // topmost position. 1.f means the window is at the bottom-most position.
  float progress;

  scoped_ptr<wm::Shadow> shadow;
};

}  // namespace

DECLARE_WINDOW_PROPERTY_TYPE(WindowOverviewState*)
DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowOverviewState,
                                 kWindowOverviewState,
                                 NULL)
namespace athena {

namespace {

// Sets the progress-state for the window in the overview mode.
void SetWindowProgress(aura::Window* window, float progress) {
  WindowOverviewState* state = window->GetProperty(kWindowOverviewState);
  gfx::Transform transform =
      gfx::Tween::TransformValueBetween(progress, state->top, state->bottom);
  window->SetTransform(transform);
  state->progress = progress;
}

// Resets the overview-related state for |window|.
void RestoreWindowState(aura::Window* window) {
  window->ClearProperty(kWindowOverviewState);

  ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
  settings.SetPreemptionStrategy(
      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(250));
  window->SetTransform(gfx::Transform());
}

// Always returns the same target.
class StaticWindowTargeter : public aura::WindowTargeter {
 public:
  explicit StaticWindowTargeter(aura::Window* target) : target_(target) {}
  virtual ~StaticWindowTargeter() {}

 private:
  // aura::WindowTargeter:
  virtual ui::EventTarget* FindTargetForEvent(ui::EventTarget* root,
                                              ui::Event* event) OVERRIDE {
    return target_;
  }

  virtual ui::EventTarget* FindTargetForLocatedEvent(
      ui::EventTarget* root,
      ui::LocatedEvent* event) OVERRIDE {
    return target_;
  }

  aura::Window* target_;
  DISALLOW_COPY_AND_ASSIGN(StaticWindowTargeter);
};

class WindowOverviewModeImpl : public WindowOverviewMode,
                               public ui::EventHandler {
 public:
  WindowOverviewModeImpl(aura::Window* container,
                         WindowOverviewModeDelegate* delegate)
      : container_(container),
        delegate_(delegate),
        scoped_targeter_(new aura::ScopedWindowTargeter(
            container,
            scoped_ptr<ui::EventTargeter>(
                new StaticWindowTargeter(container)))) {
    container_->set_target_handler(this);

    // Prepare the desired transforms for all the windows, and set the initial
    // state on the windows.
    ComputeTerminalStatesForAllWindows();
    SetInitialWindowStates();
  }

  virtual ~WindowOverviewModeImpl() {
    container_->set_target_handler(container_->delegate());

    aura::Window::Windows windows = container_->children();
    if (windows.empty())
      return;
    std::for_each(windows.begin(), windows.end(), &RestoreWindowState);
  }

 private:
  // Computes the transforms for all windows in both the topmost and bottom-most
  // positions. The transforms are set in the |kWindowOverviewState| property of
  // the windows.
  void ComputeTerminalStatesForAllWindows() {
    aura::Window::Windows windows = container_->children();
    size_t window_count = windows.size();
    size_t index = 0;
    const gfx::Size container_size = container_->bounds().size();

    const int kGapBetweenWindowsBottom = 10;
    const int kGapBetweenWindowsTop = 5;
    const float kMinScale = 0.6f;
    const float kMaxScale = 0.95f;

    for (aura::Window::Windows::reverse_iterator iter = windows.rbegin();
         iter != windows.rend();
         ++iter, ++index) {
      aura::Window* window = (*iter);

      gfx::Transform top_transform;
      int top = (window_count - index - 1) * kGapBetweenWindowsTop;
      float x_translate = container_size.width() * (1 - kMinScale) / 2.;
      top_transform.Translate(x_translate, top);
      top_transform.Scale(kMinScale, kMinScale);

      gfx::Transform bottom_transform;
      int bottom = GetScrollableHeight() - (index * kGapBetweenWindowsBottom);
      x_translate = container_size.width() * (1 - kMaxScale) / 2.;
      bottom_transform.Translate(x_translate, bottom - window->bounds().y());
      bottom_transform.Scale(kMaxScale, kMaxScale);

      WindowOverviewState* state = new WindowOverviewState;
      state->top = top_transform;
      state->bottom = bottom_transform;
      state->progress = 0.f;
      state->shadow = CreateShadowForWindow(window);
      window->SetProperty(kWindowOverviewState, state);
    }
  }

  // Sets the initial position for the windows for the overview mode.
  void SetInitialWindowStates() {
    aura::Window::Windows windows = container_->children();
    size_t window_count = windows.size();
    // The initial overview state of the topmost three windows.
    const float kInitialProgress[] = { 0.5f, 0.05f, 0.01f };
    for (size_t i = 0; i < window_count; ++i) {
      float progress = 0.f;
      aura::Window* window = windows[window_count - 1 - i];
      if (i < arraysize(kInitialProgress))
        progress = kInitialProgress[i];

      scoped_refptr<ui::LayerAnimator> animator =
          window->layer()->GetAnimator();

      // Unset any in-progress animation.
      {
        ui::ScopedLayerAnimationSettings settings(animator);
        settings.SetPreemptionStrategy(
            ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET);
        window->Show();
        window->SetTransform(gfx::Transform());
      }
      // Setup the animation.
      {
        ui::ScopedLayerAnimationSettings settings(animator);
        settings.SetPreemptionStrategy(
            ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
        settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(250));
        SetWindowProgress(window, progress);
      }
    }
  }

  scoped_ptr<wm::Shadow> CreateShadowForWindow(aura::Window* window) {
    scoped_ptr<wm::Shadow> shadow(new wm::Shadow());
    shadow->Init(wm::Shadow::STYLE_ACTIVE);
    shadow->SetContentBounds(gfx::Rect(window->bounds().size()));
    shadow->layer()->SetVisible(true);
    window->layer()->Add(shadow->layer());
    return shadow.Pass();
  }

  aura::Window* SelectWindowAt(ui::LocatedEvent* event) {
    CHECK_EQ(container_, event->target());
    // Find the old targeter to find the target of the event.
    ui::EventTarget* window = container_;
    ui::EventTargeter* targeter = scoped_targeter_->old_targeter();
    while (!targeter && window->GetParentTarget()) {
      window = window->GetParentTarget();
      targeter = window->GetEventTargeter();
    }
    if (!targeter)
      return NULL;
    aura::Window* target = static_cast<aura::Window*>(
        targeter->FindTargetForLocatedEvent(container_, event));
    while (target && target->parent() != container_)
      target = target->parent();
    return target;
  }

  // Scroll the window list by |delta_y| amount. |delta_y| is negative when
  // scrolling up; and positive when scrolling down.
  void DoScroll(float delta_y) {
    const float kEpsilon = 1e-3f;
    aura::Window::Windows windows = container_->children();
    float delta_y_p = std::abs(delta_y) / GetScrollableHeight();
    if (delta_y < 0) {
      // Scroll up. Start with the top-most (i.e. behind-most in terms of
      // z-index) window, and try to scroll them up.
      for (aura::Window::Windows::iterator iter = windows.begin();
           delta_y_p > kEpsilon && iter != windows.end();
           ++iter) {
        aura::Window* window = (*iter);
        WindowOverviewState* state = window->GetProperty(kWindowOverviewState);
        if (state->progress > kEpsilon) {
          // It is possible to scroll |window| up. Scroll it up, and update
          // |delta_y_p| for the next window.
          float apply = delta_y_p * state->progress;
          SetWindowProgress(window, std::max(0.f, state->progress - apply * 3));
          delta_y_p -= apply;
        }
      }
    } else {
      // Scroll down. Start with the bottom-most (i.e. front-most in terms of
      // z-index) window, and try to scroll them down.
      for (aura::Window::Windows::reverse_iterator iter = windows.rbegin();
           delta_y_p > kEpsilon && iter != windows.rend();
           ++iter) {
        aura::Window* window = (*iter);
        WindowOverviewState* state = window->GetProperty(kWindowOverviewState);
        if (1.f - state->progress > kEpsilon) {
          // It is possible to scroll |window| down. Scroll it down, and update
          // |delta_y_p| for the next window.
          SetWindowProgress(window, std::min(1.f, state->progress + delta_y_p));
          delta_y_p /= 2.f;
        }
      }
    }
  }

  int GetScrollableHeight() const {
    const float kScrollableFraction = 0.65f;
    return container_->bounds().height() * kScrollableFraction;
  }

  // ui::EventHandler:
  virtual void OnMouseEvent(ui::MouseEvent* mouse) OVERRIDE {
    if (mouse->type() == ui::ET_MOUSE_PRESSED) {
      aura::Window* select = SelectWindowAt(mouse);
      if (select) {
        mouse->SetHandled();
        delegate_->OnSelectWindow(select);
      }
    } else if (mouse->type() == ui::ET_MOUSEWHEEL) {
      DoScroll(static_cast<ui::MouseWheelEvent*>(mouse)->y_offset());
    }
  }

  virtual void OnScrollEvent(ui::ScrollEvent* scroll) OVERRIDE {
    if (scroll->type() == ui::ET_SCROLL)
      DoScroll(scroll->y_offset());
  }

  virtual void OnGestureEvent(ui::GestureEvent* gesture) OVERRIDE {
    if (gesture->type() == ui::ET_GESTURE_TAP) {
      aura::Window* select = SelectWindowAt(gesture);
      if (select) {
        gesture->SetHandled();
        delegate_->OnSelectWindow(select);
      }
    } else if (gesture->type() == ui::ET_GESTURE_SCROLL_UPDATE) {
      DoScroll(gesture->details().scroll_y());
    }
  }

  aura::Window* container_;
  WindowOverviewModeDelegate* delegate_;
  scoped_ptr<aura::ScopedWindowTargeter> scoped_targeter_;

  DISALLOW_COPY_AND_ASSIGN(WindowOverviewModeImpl);
};

}  // namespace

scoped_ptr<WindowOverviewMode> WindowOverviewMode::Create(
    aura::Window* window,
    WindowOverviewModeDelegate* delegate) {
  return scoped_ptr<WindowOverviewMode>(
      new WindowOverviewModeImpl(window, delegate));
}

}  // namespace athena