// 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 "components/infobars/core/infobar.h"

#include <cmath>
#include <utility>

#include "base/logging.h"
#include "build/build_config.h"
#include "components/infobars/core/infobar_container.h"
#include "components/infobars/core/infobar_manager.h"
#include "ui/base/material_design/material_design_controller.h"
#include "ui/gfx/animation/slide_animation.h"

namespace infobars {

InfoBar::InfoBar(std::unique_ptr<InfoBarDelegate> delegate)
    : owner_(NULL),
      delegate_(std::move(delegate)),
      container_(NULL),
      animation_(this),
      arrow_height_(0),
      arrow_target_height_(0),
      arrow_half_width_(0),
      bar_height_(0),
      bar_target_height_(-1) {
  DCHECK(delegate_ != NULL);
  animation_.SetTweenType(gfx::Tween::LINEAR);
  delegate_->set_infobar(this);
}

InfoBar::~InfoBar() {
  DCHECK(!owner_);
}

// static
SkColor InfoBar::GetTopColor(InfoBarDelegate::Type infobar_type) {
  if (ui::MaterialDesignController::IsModeMaterial()) {
    static const SkColor kWarningBackgroundColorMd =
        SkColorSetRGB(0xFF, 0xEC, 0xB3);  // Yellow
    static const SkColor kPageActionBackgroundColorMd = SK_ColorWHITE;

    return infobar_type == InfoBarDelegate::WARNING_TYPE ?
        kWarningBackgroundColorMd : kPageActionBackgroundColorMd;
  }

  static const SkColor kWarningBackgroundColorTop =
      SkColorSetRGB(255, 242, 183);  // Yellow
  static const SkColor kPageActionBackgroundColorTop =
      SkColorSetRGB(237, 237, 237);  // Gray
  return (infobar_type == InfoBarDelegate::WARNING_TYPE) ?
      kWarningBackgroundColorTop : kPageActionBackgroundColorTop;
}

// static
SkColor InfoBar::GetBottomColor(InfoBarDelegate::Type infobar_type) {
  // No gradient in MD.
  if (ui::MaterialDesignController::IsModeMaterial())
    return GetTopColor(infobar_type);

  static const SkColor kWarningBackgroundColorBottom =
      SkColorSetRGB(250, 230, 145);  // Yellow
  static const SkColor kPageActionBackgroundColorBottom =
      SkColorSetRGB(217, 217, 217);  // Gray
  return (infobar_type == InfoBarDelegate::WARNING_TYPE) ?
      kWarningBackgroundColorBottom : kPageActionBackgroundColorBottom;
}

void InfoBar::SetOwner(InfoBarManager* owner) {
  DCHECK(!owner_);
  owner_ = owner;
  delegate_->set_nav_entry_id(owner->GetActiveEntryID());
  PlatformSpecificSetOwner();
}

void InfoBar::Show(bool animate) {
  PlatformSpecificShow(animate);
  if (animate) {
    animation_.Show();
  } else {
    animation_.Reset(1.0);
    RecalculateHeights(true);
  }
}

void InfoBar::Hide(bool animate) {
  PlatformSpecificHide(animate);
  if (animate) {
    animation_.Hide();
  } else {
    animation_.Reset(0.0);
    // We want to remove ourselves from the container immediately even if we
    // still have an owner, which MaybeDelete() won't do.
    DCHECK(container_);
    container_->RemoveInfoBar(this);
    MaybeDelete();  // Necessary if the infobar was already closing.
  }
}

void InfoBar::SetArrowTargetHeight(int height) {
  // Once the closing animation starts, we ignore further requests to change the
  // target height.
  if ((arrow_target_height_ != height) && !animation_.IsClosing()) {
    arrow_target_height_ = height;
    RecalculateHeights(false);
  }
}

void InfoBar::CloseSoon() {
  owner_ = NULL;
  PlatformSpecificOnCloseSoon();
  MaybeDelete();
}

void InfoBar::RemoveSelf() {
  if (owner_)
    owner_->RemoveInfoBar(this);
}

void InfoBar::SetBarTargetHeight(int height) {
  if (bar_target_height_ != height) {
    bar_target_height_ = height;
    RecalculateHeights(false);
  }
}

void InfoBar::AnimationProgressed(const gfx::Animation* animation) {
  RecalculateHeights(false);
}

void InfoBar::AnimationEnded(const gfx::Animation* animation) {
  // When the animation ends, we must ensure the container is notified even if
  // the heights haven't changed, lest it never get an "animation finished"
  // notification.  (If the browser doesn't get this notification, it will not
  // bother to re-layout the content area for the new infobar size.)
  RecalculateHeights(true);
  MaybeDelete();
}

void InfoBar::RecalculateHeights(bool force_notify) {
  // If there's no container delegate, there's no way to compute new element
  // sizes, so return immediately.  We don't need to worry that this might leave
  // us with bogus sizes, because if we're ever re-added to a container, it will
  // call Show(false) while re-adding us, which will compute a correct set of
  // sizes.
  if (!container_ || !container_->delegate())
    return;

  int old_arrow_height = arrow_height_;
  int old_bar_height = bar_height_;

  container_->delegate()->ComputeInfoBarElementSizes(
      animation_, arrow_target_height_, bar_target_height_, &arrow_height_,
      &arrow_half_width_, &bar_height_);

  // Don't re-layout if nothing has changed, e.g. because the animation step was
  // not large enough to actually change the heights by at least a pixel.
  bool heights_differ =
      (old_arrow_height != arrow_height_) || (old_bar_height != bar_height_);
  if (heights_differ)
    PlatformSpecificOnHeightsRecalculated();

  if (heights_differ || force_notify)
    container_->OnInfoBarStateChanged(animation_.is_animating());
}

void InfoBar::MaybeDelete() {
  if (!owner_ && (animation_.GetCurrentValue() == 0.0)) {
    if (container_)
      container_->RemoveInfoBar(this);
    delete this;
  }
}

}  // namespace infobars
