// 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 "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_overflow_indicator.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 visible overflow panels when the mouse does not hover over
// the overflow area.
const size_t kMaxVisibleOverflowPanels = 6;

// The ratio to determine the maximum number of visible overflow panels when the
// mouse hovers over the overflow area. These panels cannot occupy more than the
// fixed percentage of the display area height.
const double kHeightRatioForMaxVisibleOverflowPanelsOnHover = 0.67;

// 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_(kMaxVisibleOverflowPanels),
      max_visible_panels_on_hover_(0),
      are_overflow_titles_shown_(false),
      overflow_hover_animator_start_width_(0),
      overflow_hover_animator_end_width_(0) {
}

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

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

  UpdateCurrentWidth();

  max_visible_panels_on_hover_ = 0;  // reset this value for recomputation.
  UpdateMaxVisiblePanelsOnHover();

  if (overflow_indicator_.get())
    UpdateOverflowIndicatorCount();

  Refresh();
}

void PanelOverflowStrip::UpdateMaxVisiblePanelsOnHover() {
  // No need to recompute this value.
  if (max_visible_panels_on_hover_ || panels_.empty())
    return;

  max_visible_panels_on_hover_ =
      kHeightRatioForMaxVisibleOverflowPanelsOnHover *
      display_area_.height() /
      panels_.front()->IconOnlySize().height();
}

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 (num_panels() == 1) {
    panel_manager_->mouse_watcher()->AddObserver(this);
    UpdateMaxVisiblePanelsOnHover();
  }

  // Update the overflow indicator only when the number of overflow panels go
  // beyond the maximum visible limit.
  if (num_panels() > max_visible_panels_) {
    if (!overflow_indicator_.get()) {
      overflow_indicator_.reset(PanelOverflowIndicator::Create());
    }
    UpdateOverflowIndicatorCount();
  }
}

bool PanelOverflowStrip::Remove(Panel* panel) {
  size_t index = 0;
  Panels::iterator iter = panels_.begin();
  for (; iter != panels_.end(); ++iter, ++index)
    if (*iter == panel)
      break;
  if (iter == panels_.end())
    return false;

  panels_.erase(iter);
  DoRefresh(index, panels_.size() - 1);
  panel_manager_->OnPanelRemoved(panel);

  if (panels_.empty())
    panel_manager_->mouse_watcher()->RemoveObserver(this);

  // Update the overflow indicator. If the number of overflow panels fall below
  // the maximum visible limit, we do not need the overflow indicator any more.
  if (num_panels() < max_visible_panels_)
    overflow_indicator_.reset();
  else
    UpdateOverflowIndicatorCount();

  return true;
}

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) {
  // Only care about new state being overflow.
  if (panel->expansion_state() != Panel::IN_OVERFLOW)
    return;

  panel_manager_->panel_strip()->Remove(panel);
  AddPanel(panel);
  panel->SetAppIconVisibility(false);
  panel->set_draggable(false);
}

void PanelOverflowStrip::OnPanelAttentionStateChanged(Panel* panel) {
  DCHECK(panel->expansion_state() == Panel::IN_OVERFLOW);
  UpdateOverflowIndicatorAttention();
}

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);
  }
}

void PanelOverflowStrip::UpdateOverflowIndicatorCount() {
  if (!overflow_indicator_.get())
    return;

  int max_panels = max_visible_panels();

  // Setting the count to 0 will hide the indicator.
  if (num_panels() <= max_panels) {
    overflow_indicator_->SetCount(0);
    return;
  }

  // Update the bounds and count.
  int height = overflow_indicator_->GetHeight();
  overflow_indicator_->SetBounds(gfx::Rect(
      display_area_.x(),
      panels_[max_panels - 1]->GetBounds().y() - height,
      current_display_width_,
      height));
  overflow_indicator_->SetCount(num_panels() - max_panels);

  // The attention state might get changed when there is a change to count
  // value.
  UpdateOverflowIndicatorAttention();
}

void PanelOverflowStrip::UpdateOverflowIndicatorAttention() {
  if (!overflow_indicator_.get())
    return;

  int max_panels = max_visible_panels();

  // The overflow indicator is painted as drawing attention only when there is
  // at least one hidden panel that is drawing attention.
  bool is_drawing_attention = false;
  for (int index = max_panels; index < num_panels(); ++index) {
    if (panels_[index]->IsDrawingAttention()) {
      is_drawing_attention = true;
      break;
    }
  }
  if (is_drawing_attention)
    overflow_indicator_->DrawAttention();
  else
    overflow_indicator_->StopDrawingAttention();
}

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 (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 = 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;

  int start_width = current_display_width_;
  UpdateCurrentWidth();

  if (panels_.empty())
    return;

  if (show_overflow_titles) {
    overflow_hover_animator_start_width_ = start_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_ = start_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();

  // The overflow indicator count needs to be updated when the overflow area
  // gets changed.
  UpdateOverflowIndicatorCount();
}

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();
  int max_visible_panels = end_of_shrinking ?
      max_visible_panels_ : max_visible_panels_on_hover_;

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

    if (i >= max_visible_panels) {
      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);
  }

  // Update the indicator.
  if (overflow_indicator_.get()) {
    gfx::Rect bounds = overflow_indicator_->GetBounds();
    bounds.set_width(current_display_width);
    overflow_indicator_->SetBounds(bounds);
    overflow_indicator_->SetCount(num_panels() - max_visible_panels);
  }
}

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