// 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 "ui/views/controls/progress_bar.h"

#include <algorithm>
#include <string>

#include "base/logging.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkXfermode.h"
#include "third_party/skia/include/effects/SkGradientShader.h"
#include "ui/accessibility/ax_view_state.h"
#include "ui/gfx/canvas.h"

namespace {

// Progress bar's border width.
const int kBorderWidth = 1;

// Corner radius for the progress bar's border.
const int kCornerRadius = 2;

// The width of the highlight at the right of the progress bar.
const int kHighlightWidth = 18;

const SkColor kBackgroundColor = SkColorSetRGB(230, 230, 230);
const SkColor kBackgroundBorderColor = SkColorSetRGB(208, 208, 208);
const SkColor kBarBorderColor = SkColorSetRGB(65, 137, 237);
const SkColor kBarTopColor = SkColorSetRGB(110, 188, 249);
const SkColor kBarColorStart = SkColorSetRGB(86, 167, 247);
const SkColor kBarColorEnd = SkColorSetRGB(76, 148, 245);
const SkColor kBarHighlightEnd = SkColorSetRGB(114, 206, 251);
const SkColor kDisabledBarBorderColor = SkColorSetRGB(191, 191, 191);
const SkColor kDisabledBarColorStart = SkColorSetRGB(224, 224, 224);
const SkColor kDisabledBarColorEnd = SkColorSetRGB(212, 212, 212);

void AddRoundRectPathWithPadding(int x, int y,
                                 int w, int h,
                                 int corner_radius,
                                 SkScalar padding,
                                 SkPath* path) {
  DCHECK(path);
  SkRect rect;
  rect.set(
      SkIntToScalar(x) + padding, SkIntToScalar(y) + padding,
      SkIntToScalar(x + w) - padding, SkIntToScalar(y + h) - padding);
  path->addRoundRect(
      rect,
      SkIntToScalar(corner_radius) - padding,
      SkIntToScalar(corner_radius) - padding);
}

void AddRoundRectPath(int x, int y,
                      int w, int h,
                      int corner_radius,
                      SkPath* path) {
  AddRoundRectPathWithPadding(x, y, w, h, corner_radius, SK_ScalarHalf, path);
}

void FillRoundRect(gfx::Canvas* canvas,
                   int x, int y,
                   int w, int h,
                   int corner_radius,
                   const SkColor colors[],
                   const SkScalar points[],
                   int count,
                   bool gradient_horizontal) {
  SkPath path;
  AddRoundRectPath(x, y, w, h, corner_radius, &path);
  SkPaint paint;
  paint.setStyle(SkPaint::kFill_Style);
  paint.setFlags(SkPaint::kAntiAlias_Flag);

  SkPoint p[2];
  p[0].iset(x, y);
  if (gradient_horizontal) {
    p[1].iset(x + w, y);
  } else {
    p[1].iset(x, y + h);
  }
  skia::RefPtr<SkShader> s = skia::AdoptRef(SkGradientShader::CreateLinear(
      p, colors, points, count, SkShader::kClamp_TileMode));
  paint.setShader(s.get());

  canvas->DrawPath(path, paint);
}

void FillRoundRect(gfx::Canvas* canvas,
                   int x, int y,
                   int w, int h,
                   int corner_radius,
                   SkColor gradient_start_color,
                   SkColor gradient_end_color,
                   bool gradient_horizontal) {
  if (gradient_start_color != gradient_end_color) {
    SkColor colors[2] = { gradient_start_color, gradient_end_color };
    FillRoundRect(canvas, x, y, w, h, corner_radius,
                  colors, NULL, 2, gradient_horizontal);
  } else {
    SkPath path;
    AddRoundRectPath(x, y, w, h, corner_radius, &path);
    SkPaint paint;
    paint.setStyle(SkPaint::kFill_Style);
    paint.setFlags(SkPaint::kAntiAlias_Flag);
    paint.setColor(gradient_start_color);
    canvas->DrawPath(path, paint);
  }
}

void StrokeRoundRect(gfx::Canvas* canvas,
                     int x, int y,
                     int w, int h,
                     int corner_radius,
                     SkColor stroke_color,
                     int stroke_width) {
  SkPath path;
  AddRoundRectPath(x, y, w, h, corner_radius, &path);
  SkPaint paint;
  paint.setShader(NULL);
  paint.setColor(stroke_color);
  paint.setStyle(SkPaint::kStroke_Style);
  paint.setFlags(SkPaint::kAntiAlias_Flag);
  paint.setStrokeWidth(SkIntToScalar(stroke_width));
  canvas->DrawPath(path, paint);
}

}  // namespace

namespace views {

// static
const char ProgressBar::kViewClassName[] = "ProgressBar";

ProgressBar::ProgressBar()
    : min_display_value_(0.0),
      max_display_value_(1.0),
      current_value_(0.0) {
}

ProgressBar::~ProgressBar() {
}

double ProgressBar::GetNormalizedValue() const {
  const double capped_value = std::min(
      std::max(current_value_, min_display_value_), max_display_value_);
  return (capped_value - min_display_value_) /
      (max_display_value_ - min_display_value_);
}

void ProgressBar::SetDisplayRange(double min_display_value,
                                  double max_display_value) {
  if (min_display_value != min_display_value_ ||
      max_display_value != max_display_value_) {
    DCHECK(min_display_value < max_display_value);
    min_display_value_ = min_display_value;
    max_display_value_ = max_display_value;
    SchedulePaint();
  }
}

void ProgressBar::SetValue(double value) {
  if (value != current_value_) {
    current_value_ = value;
    SchedulePaint();
  }
}

void ProgressBar::SetTooltipText(const base::string16& tooltip_text) {
  tooltip_text_ = tooltip_text;
}

bool ProgressBar::GetTooltipText(const gfx::Point& p,
                                 base::string16* tooltip) const {
  DCHECK(tooltip);
  *tooltip = tooltip_text_;
  return !tooltip_text_.empty();
}

void ProgressBar::GetAccessibleState(ui::AXViewState* state) {
  state->role = ui::AX_ROLE_PROGRESS_INDICATOR;
  state->AddStateFlag(ui::AX_STATE_READ_ONLY);
}

gfx::Size ProgressBar::GetPreferredSize() const {
  gfx::Size pref_size(100, 11);
  gfx::Insets insets = GetInsets();
  pref_size.Enlarge(insets.width(), insets.height());
  return pref_size;
}

const char* ProgressBar::GetClassName() const {
  return kViewClassName;
}

void ProgressBar::OnPaint(gfx::Canvas* canvas) {
  gfx::Rect content_bounds = GetContentsBounds();
  int bar_left = content_bounds.x();
  int bar_top = content_bounds.y();
  int bar_width = content_bounds.width();
  int bar_height = content_bounds.height();

  const int progress_width =
      static_cast<int>(bar_width * GetNormalizedValue() + 0.5);

  // Draw background.
  FillRoundRect(canvas,
                bar_left, bar_top, bar_width, bar_height,
                kCornerRadius,
                kBackgroundColor, kBackgroundColor,
                false);
  StrokeRoundRect(canvas,
                  bar_left, bar_top,
                  bar_width, bar_height,
                  kCornerRadius,
                  kBackgroundBorderColor,
                  kBorderWidth);

  if (progress_width > 1) {
    // Draw inner if wide enough.
    if (progress_width > kBorderWidth * 2) {
      canvas->Save();

      SkPath inner_path;
      AddRoundRectPathWithPadding(
          bar_left, bar_top, progress_width, bar_height,
          kCornerRadius,
          0,
          &inner_path);
      canvas->ClipPath(inner_path, false);

      const SkColor bar_colors[] = {
        kBarTopColor,
        kBarTopColor,
        kBarColorStart,
        kBarColorEnd,
        kBarColorEnd,
      };
      // We want a thin 1-pixel line for kBarTopColor.
      SkScalar scalar_height = SkIntToScalar(bar_height);
      SkScalar highlight_width = SkScalarDiv(SK_Scalar1, scalar_height);
      SkScalar border_width = SkScalarDiv(SkIntToScalar(kBorderWidth),
                                          scalar_height);
      const SkScalar bar_points[] = {
        0,
        border_width,
        border_width + highlight_width,
        SK_Scalar1 - border_width,
        SK_Scalar1,
      };

      const SkColor disabled_bar_colors[] = {
        kDisabledBarColorStart,
        kDisabledBarColorStart,
        kDisabledBarColorEnd,
        kDisabledBarColorEnd,
      };

      const SkScalar disabled_bar_points[] = {
        0,
        border_width,
        SK_Scalar1 - border_width,
        SK_Scalar1
      };

      // Do not start from (kBorderWidth, kBorderWidth) because it makes gaps
      // between the inner and the border.
      FillRoundRect(canvas,
                    bar_left, bar_top,
                    progress_width, bar_height,
                    kCornerRadius,
                    enabled() ? bar_colors : disabled_bar_colors,
                    enabled() ? bar_points : disabled_bar_points,
                    enabled() ? arraysize(bar_colors) :
                        arraysize(disabled_bar_colors),
                    false);

      if (enabled()) {
        // Draw the highlight to the right.
        const SkColor highlight_colors[] = {
          SkColorSetA(kBarHighlightEnd, 0),
          kBarHighlightEnd,
          kBarHighlightEnd,
        };
        const SkScalar highlight_points[] = {
          0,
          SK_Scalar1 - SkScalarDiv(SkIntToScalar(kBorderWidth), scalar_height),
          SK_Scalar1,
        };
        SkPaint paint;
        paint.setStyle(SkPaint::kFill_Style);
        paint.setFlags(SkPaint::kAntiAlias_Flag);

        SkPoint p[2];
        int highlight_left =
            std::max(0, progress_width - kHighlightWidth - kBorderWidth);
        p[0].iset(highlight_left, 0);
        p[1].iset(progress_width, 0);
        skia::RefPtr<SkShader> s =
            skia::AdoptRef(SkGradientShader::CreateLinear(
                p, highlight_colors, highlight_points,
                arraysize(highlight_colors), SkShader::kClamp_TileMode));
        paint.setShader(s.get());
        paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
        canvas->DrawRect(gfx::Rect(highlight_left, 0,
                                   kHighlightWidth + kBorderWidth, bar_height),
                         paint);
      }

      canvas->Restore();
    }

    // Draw bar stroke
    StrokeRoundRect(canvas,
                    bar_left, bar_top, progress_width, bar_height,
                    kCornerRadius,
                    enabled() ? kBarBorderColor : kDisabledBarBorderColor,
                    kBorderWidth);
  }
}

}  // namespace views