// Copyright (c) 2011 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 "chrome/browser/ui/panels/panel_overflow_strip.h"

#include "base/logging.h"
#include "chrome/browser/ui/panels/panel_manager.h"
#include "chrome/browser/ui/panels/panel_mouse_watcher.h"
#include "chrome/browser/ui/panels/panel_strip.h"
#include "chrome/common/chrome_notification_types.h"
#include "content/public/browser/notification_service.h"
#include "ui/base/animation/slide_animation.h"

namespace {
// The width of the overflow area that is expanded to show more info, i.e.
// titles, when the mouse hovers over the area.
static const int kOverflowAreaHoverWidth = 200;

// Maximium number of overflow panels allowed to be shown.
const size_t kMaxVisibleOverflowPanelsAllowed = 6;

// This value is experimental and subjective.
const int kOverflowHoverAnimationMs = 180;
}

PanelOverflowStrip::PanelOverflowStrip(PanelManager* panel_manager)
    : panel_manager_(panel_manager),
      current_display_width_(0),
      max_visible_panels_(kMaxVisibleOverflowPanelsAllowed),
      are_overflow_titles_shown_(false),
      overflow_hover_animator_start_width_(0),
      overflow_hover_animator_end_width_(0) {
}

PanelOverflowStrip::~PanelOverflowStrip() {
  DCHECK(panels_.empty());
}

void PanelOverflowStrip::SetDisplayArea(const gfx::Rect& display_area) {
  if (display_area_ == display_area)
    return;

  display_area_ = display_area;
  UpdateCurrentWidth();

  Refresh();
}

void PanelOverflowStrip::UpdateCurrentWidth() {
  current_display_width_ = are_overflow_titles_shown_ ? kOverflowAreaHoverWidth
                                                      : display_area_.width();
}

void PanelOverflowStrip::AddPanel(Panel* panel) {
  // TODO(jianli): consider using other container to improve the perf for
  // inserting to the front. http://crbug.com/106222
  DCHECK_EQ(Panel::IN_OVERFLOW, panel->expansion_state());
  // Newly created panels that were temporarily in the panel strip
  // are added to the back of the overflow, whereas panels that are
  // bumped from the panel strip by other panels go to the front
  // of overflow.
  if (panel->has_temporary_layout()) {
    panel->set_has_temporary_layout(false);
    panels_.push_back(panel);
    DoRefresh(panels_.size() - 1, panels_.size() - 1);
  } else {
    panels_.insert(panels_.begin(), panel);
    Refresh();
  }

  if (panels_.size() == 1)
    panel_manager_->mouse_watcher()->AddObserver(this);
}

bool PanelOverflowStrip::Remove(Panel* panel) {
  size_t index = 0;
  for (Panels::iterator iter = panels_.begin(); iter != panels_.end();
       ++iter, ++index) {
    if (*iter == panel) {
      panels_.erase(iter);
      DoRefresh(index, panels_.size() - 1);
      panel_manager_->OnPanelRemoved(panel);
      if (panels_.empty())
        panel_manager_->mouse_watcher()->RemoveObserver(this);
      return true;
    }
  }
  return false;
}

void PanelOverflowStrip::RemoveAll() {
  // Make a copy of the iterator as closing panels can modify the vector.
  Panels panels_copy = panels_;

  // Start from the bottom to avoid reshuffling.
  for (Panels::reverse_iterator iter = panels_copy.rbegin();
       iter != panels_copy.rend(); ++iter)
    (*iter)->Close();
}

void PanelOverflowStrip::OnPanelExpansionStateChanged(
    Panel* panel, Panel::ExpansionState old_state) {
  DCHECK(panel->expansion_state() == Panel::IN_OVERFLOW);
  panel_manager_->panel_strip()->Remove(panel);
  AddPanel(panel);
  panel->SetAppIconVisibility(false);
  panel->set_draggable(false);
}

void PanelOverflowStrip::Refresh() {
  if (panels_.empty())
    return;
  DoRefresh(0, panels_.size() - 1);
}

void PanelOverflowStrip::DoRefresh(size_t start_index, size_t end_index) {
  if (panels_.empty() || start_index == panels_.size())
    return;

  DCHECK(end_index < panels_.size());

  for (size_t index = start_index; index <= end_index; ++index) {
    Panel* panel = panels_[index];
    gfx::Rect new_bounds = ComputeLayout(index,
                                         panel->IconOnlySize());
    panel->SetPanelBounds(new_bounds);
  }
}

gfx::Rect PanelOverflowStrip::ComputeLayout(
    size_t index, const gfx::Size& iconified_size) const {
  DCHECK(index != kInvalidPanelIndex);

  gfx::Rect bounds;
  int bottom = (index == 0) ? display_area_.bottom() :
      panels_[index - 1]->GetBounds().y();
  bounds.set_x(display_area_.x());
  bounds.set_y(bottom - iconified_size.height());

  if (are_overflow_titles_shown_ ||
      static_cast<int>(index) < max_visible_panels_) {
    bounds.set_width(current_display_width_);
    bounds.set_height(iconified_size.height());
  } else {
    // Invisible for overflow-on-overflow.
    bounds.set_width(0);
    bounds.set_height(0);
  }

  return bounds;
}

void PanelOverflowStrip::OnMouseMove(const gfx::Point& mouse_position) {
  bool show_overflow_titles = ShouldShowOverflowTitles(mouse_position);
  ShowOverflowTitles(show_overflow_titles);
}

bool PanelOverflowStrip::ShouldShowOverflowTitles(
    const gfx::Point& mouse_position) const {
  if (panels_.empty())
    return false;

  Panel* top_visible_panel;
  if (are_overflow_titles_shown_) {
    top_visible_panel = panels_.back();
  } else {
    top_visible_panel = num_panels() >= max_visible_panels_ ?
        panels_[max_visible_panels_ - 1] : panels_.back();
  }
  return mouse_position.x() <= display_area_.x() + current_display_width_ &&
         top_visible_panel->GetBounds().y() <= mouse_position.y() &&
         mouse_position.y() <= display_area_.bottom();
}

void PanelOverflowStrip::ShowOverflowTitles(bool show_overflow_titles) {
  if (show_overflow_titles == are_overflow_titles_shown_)
    return;
  are_overflow_titles_shown_ = show_overflow_titles;

  UpdateCurrentWidth();

  if (panels_.empty())
    return;

  if (show_overflow_titles) {
    overflow_hover_animator_start_width_ = current_display_width_;
    overflow_hover_animator_end_width_ = kOverflowAreaHoverWidth;

    // We need to bring all overflow panels to the top of z-order since the
    // active panel might obscure the expanded overflow panels.
    for (Panels::iterator iter = panels_.begin();
         iter != panels_.end(); ++iter) {
      (*iter)->EnsureFullyVisible();
    }
  } else {
    overflow_hover_animator_start_width_ = current_display_width_;
    overflow_hover_animator_end_width_ = display_area_.width();
  }

  if (!overflow_hover_animator_.get())
    overflow_hover_animator_.reset(new ui::SlideAnimation(this));
  if (overflow_hover_animator_->IsShowing())
    overflow_hover_animator_->Reset();
  overflow_hover_animator_->SetSlideDuration(
    PanelManager::AdjustTimeInterval(kOverflowHoverAnimationMs));

  overflow_hover_animator_->Show();
}

void PanelOverflowStrip::AnimationEnded(const ui::Animation* animation) {
  content::NotificationService::current()->Notify(
      chrome::NOTIFICATION_PANEL_BOUNDS_ANIMATIONS_FINISHED,
      content::Source<PanelOverflowStrip>(this),
      content::NotificationService::NoDetails());
}

void PanelOverflowStrip::AnimationProgressed(const ui::Animation* animation) {
  int current_display_width = overflow_hover_animator_->CurrentValueBetween(
      overflow_hover_animator_start_width_, overflow_hover_animator_end_width_);
  bool end_of_shrinking = current_display_width == display_area_.width();

  // Update each overflow panel.
  for (size_t i = 0; i < panels_.size(); ++i) {
    Panel* overflow_panel = panels_[i];
    gfx::Rect bounds = overflow_panel->GetBounds();

    if (static_cast<int>(i) >= max_visible_panels_ && end_of_shrinking) {
      bounds.set_width(0);
      bounds.set_height(0);
    } else {
      bounds.set_width(current_display_width);
      bounds.set_height(overflow_panel->IconOnlySize().height());
    }

    overflow_panel->SetPanelBoundsInstantly(bounds);
  }
}

void PanelOverflowStrip::OnFullScreenModeChanged(bool is_full_screen) {
  for (size_t i = 0; i < panels_.size(); ++i)
    panels_[i]->FullScreenModeChanged(is_full_screen);
}