using base::TimeDelta;
using base::TimeTicks;
using std::max;
using std::min;

namespace {

// How long should the bubble be shown onscreen whenever the setting changes?
const int kBubbleShowTimeoutMs = 1000;

// How long should the level initially take to move up or down when it changes?
// (The rate adapts to handle keyboard autorepeat.)
const int64 kInitialAnimationDurationMs = 200;

// Horizontal position of the center of the bubble on the screen: 0 is left
// edge, 0.5 is center, 1 is right edge.
const double kBubbleXRatio = 0.5;

// Vertical gap from the bottom of the screen in pixels.
const int kBubbleBottomGap = 30;

// Duration between animation frames.
// Chosen to match ui::SlideAnimation's kDefaultFramerateHz.
const int kAnimationIntervalMs = 1000 / 50;

double LimitPercent(double percent) {
  return min(max(percent, 0.0), 100.0);
}

}  // namespace

namespace chromeos {

// SettingLevelBubbleDelegateView ----------------------------------------------
class SettingLevelBubbleDelegateView : public views::BubbleDelegateView {
 public:
  // BubbleDelegate overrides:
  virtual gfx::Rect GetAnchorRect() OVERRIDE;

  // Create the bubble delegate view.
  SettingLevelBubbleDelegateView();
  virtual ~SettingLevelBubbleDelegateView();

  SettingLevelBubbleView* view() { return view_; }

 protected:
  // BubbleDelegate overrides:
  virtual void Init() OVERRIDE;

 private:
  SettingLevelBubbleView* view_;

  DISALLOW_COPY_AND_ASSIGN(SettingLevelBubbleDelegateView);
};

gfx::Rect SettingLevelBubbleDelegateView::GetAnchorRect() {
  gfx::Size view_size = GetPreferredSize();
  // Calculate the position in screen coordinates that the bubble should
  // "point" at (since we use BubbleBorder::FLOAT, this position actually
  // specifies the center of the bubble).
  gfx::Rect monitor_area = gfx::Screen::GetMonitorAreaNearestWindow(NULL);
  return (gfx::Rect(
      monitor_area.x() + kBubbleXRatio * monitor_area.width(),
      monitor_area.bottom() - view_size.height() / 2 - kBubbleBottomGap, 0, 0));
}

SettingLevelBubbleDelegateView::SettingLevelBubbleDelegateView()
    : BubbleDelegateView(NULL, views::BubbleBorder::FLOAT),
      view_(NULL) {
  set_close_on_esc(false);
  set_use_focusless(true);
}

SettingLevelBubbleDelegateView::~SettingLevelBubbleDelegateView() {
  view_ = NULL;
}

void SettingLevelBubbleDelegateView::Init() {
  SetLayoutManager(new views::FillLayout());
  view_ = new SettingLevelBubbleView();
  AddChildView(view_);
}

// SettingLevelBubble ----------------------------------------------------------
void SettingLevelBubble::ShowBubble(double percent, bool enabled) {
  hide_timer_.Stop();

  // Set up target percent and icon.
  const double old_target_percent = target_percent_;
  UpdateTargetPercent(percent);
  SkBitmap* current_icon = increase_icon_;
  if (!enabled || target_percent_ == 0)
    current_icon = disabled_icon_;
  else if (old_target_percent >= 0 && target_percent_ < old_target_percent)
    current_icon = decrease_icon_;

  if (!view_) {
    view_ = CreateView();
    view_->Init(current_icon, percent, enabled);
  } else {
    // Reset fade sequence, if the bubble is already fading.
    SettingLevelBubbleDelegateView* delegate =
        static_cast<SettingLevelBubbleDelegateView*>
        (view_->GetWidget()->widget_delegate());
    delegate->ResetFade();
    view_->SetIcon(current_icon);
    view_->SetEnabled(enabled);
  }
  view_->GetWidget()->Show();
  // When the timer runs out, start the fade sequence.
  hide_timer_.Start(FROM_HERE,
                    base::TimeDelta::FromMilliseconds(kBubbleShowTimeoutMs),
                    this, &SettingLevelBubble::OnHideTimeout);
}

void SettingLevelBubble::HideBubble() {
  hide_timer_.Stop();
  if (view_) {
    view_->GetWidget()->Close();
    view_ = NULL;
  }
}

void SettingLevelBubble::UpdateWithoutShowingBubble(double percent,
                                                    bool enabled) {
  UpdateTargetPercent(percent);
  if (view_)
    view_->SetEnabled(enabled);
}

SettingLevelBubble::SettingLevelBubble(SkBitmap* increase_icon,
                                       SkBitmap* decrease_icon,
                                       SkBitmap* disabled_icon)
    :  current_percent_(-1.0),
       target_percent_(-1.0),
       increase_icon_(increase_icon),
       decrease_icon_(decrease_icon),
       disabled_icon_(disabled_icon),
       view_(NULL),
       is_animating_(false) {
}

SettingLevelBubble::~SettingLevelBubble() {
  view_ = NULL;
}

void SettingLevelBubble::OnWidgetClosing(views::Widget* widget) {
  if (view_ && view_->GetWidget() == widget) {
    view_->GetWidget()->RemoveObserver(this);
    view_ = NULL;
  }
  // Update states.
  current_percent_ = target_percent_;
  target_time_ = TimeTicks();
  last_animation_update_time_ = TimeTicks();
  last_target_update_time_ = TimeTicks();
  hide_timer_.Stop();
  StopAnimation();
}

SettingLevelBubbleView* SettingLevelBubble::CreateView() {
  SettingLevelBubbleDelegateView* delegate = new SettingLevelBubbleDelegateView;
  views::Widget* widget = browser::CreateViewsBubbleAboveLockScreen(delegate);
  widget->AddObserver(this);
  // Hold on to the content view.
  return delegate->view();
}

void SettingLevelBubble::OnHideTimeout() {
  // Start fading away.
  if (view_) {
    SettingLevelBubbleDelegateView* delegate =
        static_cast<SettingLevelBubbleDelegateView*>
        (view_->GetWidget()->widget_delegate());
    delegate->StartFade(false);
  }
}

void SettingLevelBubble::OnAnimationTimeout() {
  const TimeTicks now = TimeTicks::Now();
  const int64 remaining_ms = (target_time_ - now).InMilliseconds();

  if (remaining_ms <= 0) {
    current_percent_ = target_percent_;
    StopAnimation();
  } else {
    // Figure out what fraction of the total time until we want to reach the
    // target has elapsed since the last update.
    const double remaining_percent = target_percent_ - current_percent_;
    const int64 elapsed_ms =
        (now - last_animation_update_time_).InMilliseconds();
    current_percent_ +=
        remaining_percent *
        (static_cast<double>(elapsed_ms) / (elapsed_ms + remaining_ms));
  }
  last_animation_update_time_ = now;

  if (view_)
    view_->SetLevel(current_percent_);
}

void SettingLevelBubble::UpdateTargetPercent(double percent) {
  target_percent_ = LimitPercent(percent);
  const TimeTicks now = TimeTicks::Now();

  if (current_percent_ < 0.0) {
    // If we're setting the level for the first time, no need to animate.
    current_percent_ = target_percent_;
    if (view_)
      view_->SetLevel(current_percent_);
  } else {
    // Use the time since the last request as a hint for the duration of the
    // animation.  This makes us automatically adapt to the repeat rate if a key
    // is being held down to change a setting (which prevents us from lagging
    // behind when the key is finally released).
    int64 duration_ms = kInitialAnimationDurationMs;
    if (!last_target_update_time_.is_null())
      duration_ms = min(kInitialAnimationDurationMs,
                        (now - last_target_update_time_).InMilliseconds());
    target_time_ = now + TimeDelta::FromMilliseconds(duration_ms);

    if (!is_animating_) {
      animation_timer_.Start(FROM_HERE,
                             TimeDelta::FromMilliseconds(kAnimationIntervalMs),
                             this,
                             &SettingLevelBubble::OnAnimationTimeout);
      is_animating_ = true;
      last_animation_update_time_ = now;
    }
  }

  last_target_update_time_ = now;
}

void SettingLevelBubble::StopAnimation() {
  animation_timer_.Stop();
  is_animating_ = false;
}

}  // namespace chromeos
