// Copyright (c) 2006-2008 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/views/download_tab_view.h"

#include <time.h>

#include <algorithm>
#include <functional>

#include "base/file_util.h"
#include "base/string_util.h"
#include "base/task.h"
#include "base/time_format.h"
#include "base/timer.h"
#include "chrome/app/theme/theme_resources.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/user_metrics.h"
#include "chrome/common/gfx/chrome_canvas.h"
#include "chrome/common/gfx/chrome_font.h"
#include "chrome/common/l10n_util.h"
#include "chrome/common/resource_bundle.h"
#include "chrome/common/stl_util-inl.h"
#include "chrome/common/time_format.h"
#include "chrome/views/background.h"
#include "googleurl/src/gurl.h"
#include "generated_resources.h"

using base::Time;
using base::TimeDelta;

// Approximate spacing, in pixels, taken from initial UI mock up screens
static const int kVerticalPadding = 5;
static const int kHorizontalLinkPadding = 15;
static const int kHorizontalButtonPadding = 8;

// For vertical and horizontal element spacing
static const int kSpacer = 20;

// Horizontal space between the left edge of the entries and the
// left edge of the view.
static const int kLeftMargin = 38;

// x-position of the icon (massage this so it visually matches
// kDestinationSearchOffset in native_ui_contents.cc
static const int kDownloadIconOffset = 132;

// Padding between the progress icon and the title, url
static const int kInfoPadding = 16;

// Horizontal distance from the left window edge to the left icon edge
static const int kDateSize = 132;

// Maximum size of the text for the file name or URL
static const int kFilenameSize = 350;

// Maximum size of the progress text during download, which is taken
// out of kFilenameSize
static const int kProgressSize = 170;

// Status label color (grey)
static const SkColor kStatusColor = SkColorSetRGB(128, 128, 128);

// URL label color (green)
static const SkColor kUrlColor = SkColorSetRGB(0, 128, 0);

// Paused download indicator (red)
static const SkColor kPauseColor = SkColorSetRGB(128, 0, 0);

// Warning label color (blue)
static const SkColor kWarningColor = SkColorSetRGB(87, 108, 149);

// Selected item background color
static const SkColor kSelectedItemColor = SkColorSetRGB(215, 232, 255);

// State key used to identify search text.
static const wchar_t kSearchTextKey[] = L"st";

// The maximum number of characters we show in a file name when displaying the
// dangerous download message.
static const int kFileNameMaxLength = 20;

// Sorting functor for DownloadItem --------------------------------------------

// Sort DownloadItems into ascending order by their start time.
class DownloadItemSorter : public std::binary_function<DownloadItem*,
                                                       DownloadItem*,
                                                       bool> {
 public:
  bool operator()(const DownloadItem* lhs, const DownloadItem* rhs) {
    return lhs->start_time() < rhs->start_time();
  }
};


// DownloadItemTabView implementation ------------------------------------------
DownloadItemTabView::DownloadItemTabView()
    : model_(NULL),
      parent_(NULL),
      is_floating_view_renderer_(false) {
  // Create our element views using empty strings for now,
  // set them based on the model's state in Layout().
  since_ = new views::Label(L"");
  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
  ChromeFont font = rb.GetFont(ResourceBundle::WebFont);
  since_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
  since_->SetFont(font);
  AddChildView(since_);

  date_ = new views::Label(L"");
  date_->SetColor(kStatusColor);
  date_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
  date_->SetFont(font);
  AddChildView(date_);

  // file_name_ is enabled once the download has finished and we can open
  // it via ShellExecute.
  file_name_ = new views::Link(L"");
  file_name_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
  file_name_->SetController(this);
  file_name_->SetFont(font);
  AddChildView(file_name_);

  // dangerous_download_warning_ is enabled when a dangerous download has been
  // initiated.
  dangerous_download_warning_ = new views::Label();
  dangerous_download_warning_ ->SetMultiLine(true);
  dangerous_download_warning_->SetColor(kWarningColor);
  dangerous_download_warning_->SetHorizontalAlignment(
      views::Label::ALIGN_LEFT);
  dangerous_download_warning_->SetFont(font);
  AddChildView(dangerous_download_warning_);

  // The save and discard buttons are shown to prompt the user when a dangerous
  // download was started.
  save_button_ = new views::NativeButton(
      l10n_util::GetString(IDS_SAVE_DOWNLOAD));
  save_button_->set_enforce_dlu_min_size(false);
  save_button_->SetListener(this);
  discard_button_ = new views::NativeButton(
      l10n_util::GetString(IDS_DISCARD_DOWNLOAD));
  discard_button_->SetListener(this);
  discard_button_->set_enforce_dlu_min_size(false);
  AddChildView(save_button_);
  AddChildView(discard_button_);

  // Set our URL name
  download_url_ = new views::Label(L"");
  download_url_->SetColor(kUrlColor);
  download_url_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
  download_url_->SetFont(font);
  AddChildView(download_url_);

  // Set our time remaining
  time_remaining_ = new views::Label(L"");
  time_remaining_->SetColor(kStatusColor);
  time_remaining_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
  time_remaining_->SetFont(font);
  AddChildView(time_remaining_);

  // Set our download progress
  download_progress_ = new views::Label(L"");
  download_progress_->SetColor(kStatusColor);
  download_progress_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
  download_progress_->SetFont(font);
  AddChildView(download_progress_);

  // Set our 'Pause', 'Cancel' and 'Show in folder' links using
  // actual strings, since these are constant
  pause_ = new views::Link(l10n_util::GetString(IDS_DOWNLOAD_LINK_PAUSE));
  pause_->SetController(this);
  pause_->SetFont(font);
  AddChildView(pause_);

  cancel_ = new views::Link(l10n_util::GetString(IDS_DOWNLOAD_LINK_CANCEL));
  cancel_->SetController(this);
  cancel_->SetFont(font);
  AddChildView(cancel_);

  show_ = new views::Link(l10n_util::GetString(IDS_DOWNLOAD_LINK_SHOW));
  show_->SetController(this);
  show_->SetFont(font);
  AddChildView(show_);
}

DownloadItemTabView::~DownloadItemTabView() {
}

void DownloadItemTabView::SetModel(DownloadItem* model,
                                   DownloadTabView* parent) {
  DCHECK(model && parent);
  model_ = model;
  parent_ = parent;
  parent_->LookupIcon(model_);
}

gfx::Size DownloadItemTabView::GetPreferredSize() {
  gfx::Size pause_size = pause_->GetPreferredSize();
  gfx::Size cancel_size = cancel_->GetPreferredSize();
  gfx::Size show_size = show_->GetPreferredSize();
  return gfx::Size(parent_->big_icon_size() +
                   2 * kSpacer +
                   kHorizontalLinkPadding +
                   kFilenameSize +
                   std::max(pause_size.width() + cancel_size.width() +
                            kHorizontalLinkPadding, show_size.width()),
                   parent_->big_icon_size());
}

// Each DownloadItemTabView has reasonably complex layout requirements
// that are based on the state of its model. To make the code much simpler
// to read, Layout() is split into state specific code which will result
// in some redundant code.
void DownloadItemTabView::Layout() {
  DCHECK(model_);
  switch (model_->state()) {
    case DownloadItem::COMPLETE:
      if (model_->safety_state() == DownloadItem::DANGEROUS)
        LayoutPromptDangerousDownload();
      else
        LayoutComplete();
      break;
    case DownloadItem::CANCELLED:
      LayoutCancelled();
      break;
    case DownloadItem::IN_PROGRESS:
      if (model_->safety_state() == DownloadItem::DANGEROUS)
        LayoutPromptDangerousDownload();
      else
        LayoutInProgress();
      break;
    case DownloadItem::REMOVING:
      break;
    default:
      NOTREACHED();
  }
}

// Only display the date if the download is the last that occurred
// on a given day.
void DownloadItemTabView::LayoutDate() {
  if (!parent_->ShouldDrawDateForDownload(model_)) {
    since_->SetVisible(false);
    date_->SetVisible(false);
    return;
  }

  since_->SetText(TimeFormat::RelativeDate(model_->start_time(), NULL));
  gfx::Size since_size = since_->GetPreferredSize();
  since_->SetBounds(kLeftMargin, parent_->big_icon_offset(),
                    kDateSize, since_size.height());
  since_->SetVisible(true);

  date_->SetText(base::TimeFormatShortDate(model_->start_time()));
  gfx::Size date_size = date_->GetPreferredSize();
  date_->SetBounds(kLeftMargin, since_size.height() + kVerticalPadding +
                       parent_->big_icon_offset(),
                   kDateSize, date_size.height());
  date_->SetVisible(true);
}

// DownloadItem::COMPLETE state layout
void DownloadItemTabView::LayoutComplete() {
  // Hide unused UI elements
  pause_->SetVisible(false);
  pause_->SetEnabled(false);
  cancel_->SetVisible(false);
  cancel_->SetEnabled(false);
  time_remaining_->SetVisible(false);
  download_progress_->SetVisible(false);
  dangerous_download_warning_->SetVisible(false);
  save_button_->SetVisible(false);
  save_button_->SetEnabled(false);
  discard_button_->SetVisible(false);
  discard_button_->SetEnabled(false);

  LayoutDate();
  int dx = kDownloadIconOffset - parent_->big_icon_offset() +
           parent_->big_icon_size() + kInfoPadding;

  // File name and URL
  file_name_->SetText(model_->GetFileName());
  gfx::Size file_name_size = file_name_->GetPreferredSize();
  file_name_->SetBounds(dx, parent_->big_icon_offset(),
                        std::min(kFilenameSize,
                                 static_cast<int>(file_name_size.width())),
                        file_name_size.height());
  file_name_->SetVisible(true);
  file_name_->SetEnabled(true);

  GURL url(model_->url());
  download_url_->SetURL(url);
  gfx::Size url_size = download_url_->GetPreferredSize();
  download_url_->SetBounds(dx,
                           file_name_size.height() + kVerticalPadding +
                           parent_->big_icon_offset(),
                           std::min(kFilenameSize,
                                    static_cast<int>(width() - dx)),
                           url_size.height());
  download_url_->SetVisible(true);
  dx += kFilenameSize + kSpacer;

  // Action button (text is constant and set in constructor)
  gfx::Size show_size = show_->GetPreferredSize();
  show_->SetBounds(dx, ((file_name_size.height() + url_size.height()) / 2) +
                   parent_->big_icon_offset(),
                   show_size.width(), show_size.height());
  show_->SetVisible(true);
  show_->SetEnabled(true);
}

// DownloadItem::CANCELLED state layout
void DownloadItemTabView::LayoutCancelled() {
  // Hide unused UI elements
  show_->SetVisible(false);
  show_->SetEnabled(false);
  pause_->SetVisible(false);
  pause_->SetEnabled(false);
  cancel_->SetVisible(false);
  cancel_->SetEnabled(false);
  dangerous_download_warning_->SetVisible(false);
  save_button_->SetVisible(false);
  save_button_->SetEnabled(false);
  discard_button_->SetVisible(false);
  discard_button_->SetEnabled(false);

  LayoutDate();
  int dx = kDownloadIconOffset - parent_->big_icon_offset() +
           parent_->big_icon_size() + kInfoPadding;

  // File name and URL, truncated to show cancelled status
  file_name_->SetText(model_->GetFileName());
  gfx::Size file_name_size = file_name_->GetPreferredSize();
  file_name_->SetBounds(dx, parent_->big_icon_offset(),
                        kFilenameSize - kProgressSize - kSpacer,
                        file_name_size.height());
  file_name_->SetVisible(true);
  file_name_->SetEnabled(false);

  GURL url(model_->url());
  download_url_->SetURL(url);
  gfx::Size url_size = download_url_->GetPreferredSize();
  download_url_->SetBounds(dx,
                           file_name_size.height() + kVerticalPadding +
                           parent_->big_icon_offset(),
                           std::min(kFilenameSize - kProgressSize - kSpacer,
                                    static_cast<int>(width() - dx)),
                           url_size.height());
  download_url_->SetVisible(true);

  dx += kFilenameSize - kProgressSize;

  // Display cancelled status
  time_remaining_->SetColor(kStatusColor);
  time_remaining_->SetText(l10n_util::GetString(IDS_DOWNLOAD_TAB_CANCELLED));
  gfx::Size cancel_size = time_remaining_->GetPreferredSize();
  time_remaining_->SetBounds(dx, parent_->big_icon_offset(),
                             kProgressSize, cancel_size.height());
  time_remaining_->SetVisible(true);

  // Display received size, we may not know the total size if the server didn't
  // provide a content-length.
  int64 total = model_->total_bytes();
  int64 size = model_->received_bytes();
  DataUnits amount_units = GetByteDisplayUnits(size);
  std::wstring received_size = FormatBytes(size, amount_units, true);
  std::wstring amount = received_size;

  // We don't know which string we'll end up using for constructing the final
  // progress string so we need to adjust both strings for the locale
  // direction.
  std::wstring amount_localized;
  if (l10n_util::AdjustStringForLocaleDirection(amount, &amount_localized)) {
    amount.assign(amount_localized);
    received_size.assign(amount_localized);
  }

  if (total > 0) {
    amount_units = GetByteDisplayUnits(total);
    std::wstring total_text = FormatBytes(total, amount_units, true);
    std::wstring total_text_localized;
    if (l10n_util::AdjustStringForLocaleDirection(total_text,
                                                  &total_text_localized))
      total_text.assign(total_text_localized);

    // Note that there is no need to adjust the new amount string for the
    // locale direction as views::Label does that for us.
    amount = l10n_util::GetStringF(IDS_DOWNLOAD_TAB_PROGRESS_SIZE,
                                   received_size,
                                   total_text);
  }

  download_progress_->SetText(amount);
  gfx::Size byte_size = download_progress_->GetPreferredSize();
  download_progress_->SetBounds(dx,
                                file_name_size.height() + kVerticalPadding +
                                parent_->big_icon_offset(),
                                kProgressSize,
                                byte_size.height());
  download_progress_->SetVisible(true);
}

// DownloadItem::IN_PROGRESS state layout
void DownloadItemTabView::LayoutInProgress() {
  // Hide unused UI elements
  show_->SetVisible(false);
  show_->SetEnabled(false);
  dangerous_download_warning_->SetVisible(false);
  save_button_->SetVisible(false);
  save_button_->SetEnabled(false);
  discard_button_->SetVisible(false);
  discard_button_->SetEnabled(false);

  LayoutDate();
  int dx = kDownloadIconOffset - parent_->big_icon_offset() +
           parent_->big_icon_size() + kInfoPadding;

  // File name and URL, truncated to show progress status
  file_name_->SetText(model_->GetFileName());
  gfx::Size file_name_size = file_name_->GetPreferredSize();
  file_name_->SetBounds(dx, parent_->big_icon_offset(),
                        kFilenameSize - kProgressSize - kSpacer,
                        file_name_size.height());
  file_name_->SetVisible(true);
  file_name_->SetEnabled(false);

  GURL url(model_->url());
  download_url_->SetURL(url);
  gfx::Size url_size = download_url_->GetPreferredSize();
  download_url_->SetBounds(dx, file_name_size.height() + kVerticalPadding +
                           parent_->big_icon_offset(),
                           std::min(kFilenameSize - kProgressSize - kSpacer,
                                    static_cast<int>(width() - dx)),
                           url_size.height());
  download_url_->SetVisible(true);

  dx += kFilenameSize - kProgressSize;

  // Set the time remaining and progress display strings. This can
  // be complicated by not having received the total download size
  // In that case, we can't calculate time remaining so we just
  // display speed and received size.

  // Size
  int64 total = model_->total_bytes();
  int64 size = model_->received_bytes();
  DataUnits amount_units = GetByteDisplayUnits(size);
  std::wstring received_size = FormatBytes(size, amount_units, true);
  std::wstring amount = received_size;

  // Adjust both strings for the locale direction since we don't yet know which
  // string we'll end up using for constructing the final progress string.
  std::wstring amount_localized;
  if (l10n_util::AdjustStringForLocaleDirection(amount, &amount_localized)) {
    amount.assign(amount_localized);
    received_size.assign(amount_localized);
  }

  if (total > 0) {
    amount_units = GetByteDisplayUnits(total);
    std::wstring total_text = FormatBytes(total, amount_units, true);
    std::wstring total_text_localized;
    if (l10n_util::AdjustStringForLocaleDirection(total_text,
                                                  &total_text_localized))
      total_text.assign(total_text_localized);

    amount = l10n_util::GetStringF(IDS_DOWNLOAD_TAB_PROGRESS_SIZE,
                                   received_size,
                                   total_text);

    // We adjust the 'amount' string in case we use it as part of the progress
    // text.
    if (l10n_util::AdjustStringForLocaleDirection(amount, &amount_localized))
      amount.assign(amount_localized);
  }

  // Speed
  int64 speed = model_->CurrentSpeed();
  std::wstring progress = amount;
  if (!model_->is_paused() && speed > 0) {
    amount_units = GetByteDisplayUnits(speed);
    std::wstring speed_text = FormatSpeed(speed, amount_units, true);
    std::wstring speed_text_localized;
    if (l10n_util::AdjustStringForLocaleDirection(speed_text,
                                                  &speed_text_localized))
      speed_text.assign(speed_text_localized);

    progress = l10n_util::GetStringF(IDS_DOWNLOAD_TAB_PROGRESS_SPEED,
                                     speed_text,
                                     amount);

    // For some reason, the appearance of the dash character ('-') in a string
    // causes Windows to ignore the 'LRE'/'RLE'/'PDF' Unicode formatting
    // characters within the string and this causes the string to be displayed
    // incorrectly on RTL UIs. Therefore, we add the Unicode right-to-left
    // override character (U+202E) if the locale is RTL in order to fix this
    // problem.
    if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT)
      progress.insert(0, L"\x202E");
  }

  // Time remaining
  int y_pos = file_name_size.height() + kVerticalPadding +
              parent_->big_icon_offset();
  gfx::Size time_size;
  time_remaining_->SetColor(kStatusColor);
  if (model_->is_paused()) {
    time_remaining_->SetColor(kPauseColor);
    time_remaining_->SetText(
        l10n_util::GetString(IDS_DOWNLOAD_PROGRESS_PAUSED));
    time_size = time_remaining_->GetPreferredSize();
    time_remaining_->SetBounds(dx, parent_->big_icon_offset(),
                               kProgressSize, time_size.height());
    time_remaining_->SetVisible(true);
  } else if (total > 0)  {
    TimeDelta remaining;
    if (model_->TimeRemaining(&remaining))
      time_remaining_->SetText(TimeFormat::TimeRemaining(remaining));
    time_size = time_remaining_->GetPreferredSize();
    time_remaining_->SetBounds(dx, parent_->big_icon_offset(),
                               kProgressSize, time_size.height());
    time_remaining_->SetVisible(true);
  } else {
    time_remaining_->SetText(L"");
    y_pos = ((file_name_size.height() + url_size.height()) / 2) +
            parent_->big_icon_offset();
  }

  download_progress_->SetText(progress);
  gfx::Size byte_size = download_progress_->GetPreferredSize();
  download_progress_->SetBounds(dx, y_pos,
                                kProgressSize, byte_size.height());
  download_progress_->SetVisible(true);

  dx += kProgressSize + kSpacer;
  y_pos = ((file_name_size.height() + url_size.height()) / 2) +
          parent_->big_icon_offset();

  // Pause (or Resume) / Cancel buttons.
  if (model_->is_paused())
    pause_->SetText(l10n_util::GetString(IDS_DOWNLOAD_LINK_RESUME));
  else
    pause_->SetText(l10n_util::GetString(IDS_DOWNLOAD_LINK_PAUSE));

  pause_->SetVisible(true);
  pause_->SetEnabled(true);
  gfx::Size pause_size = pause_->GetPreferredSize();
  pause_->SetBounds(dx, y_pos, pause_size.width(), pause_size.height());

  dx += pause_size.width() + kHorizontalLinkPadding;

  gfx::Size cancel_size = cancel_->GetPreferredSize();
  cancel_->SetBounds(dx, y_pos, cancel_size.width(), cancel_size.height());
  cancel_->SetVisible(true);
  cancel_->SetEnabled(true);
}

void DownloadItemTabView::LayoutPromptDangerousDownload() {
  // Hide unused UI elements
  show_->SetVisible(false);
  show_->SetEnabled(false);
  file_name_->SetVisible(false);
  file_name_->SetEnabled(false);
  pause_->SetVisible(false);
  pause_->SetEnabled(false);
  cancel_->SetVisible(false);
  cancel_->SetEnabled(false);
  time_remaining_->SetVisible(false);
  download_progress_->SetVisible(false);

  LayoutDate();
  int dx = kDownloadIconOffset - parent_->big_icon_offset() +
           parent_->big_icon_size() + kInfoPadding;

  // Warning message and URL.
  std::wstring file_name;
  ElideString(model_->original_name(), kFileNameMaxLength, &file_name);
  dangerous_download_warning_->SetText(
      l10n_util::GetStringF(IDS_PROMPT_DANGEROUS_DOWNLOAD, file_name));
  gfx::Size warning_size = dangerous_download_warning_->GetPreferredSize();
  dangerous_download_warning_->SetBounds(dx, 0,
                                         kFilenameSize, warning_size.height());
  dangerous_download_warning_->SetVisible(true);

  GURL url(model_->url());
  download_url_->SetURL(url);
  gfx::Size url_size = download_url_->GetPreferredSize();
  download_url_->SetBounds(dx, height() - url_size.height(),
                           std::min(kFilenameSize - kSpacer,
                                    static_cast<int>(width() - dx)),
                           url_size.height());
  download_url_->SetVisible(true);

  dx += kFilenameSize + kSpacer;

  // Save/Discard buttons.
  gfx::Size button_size = save_button_->GetPreferredSize();
  save_button_->SetBounds(dx, (height() - button_size.height()) / 2,
                          button_size.width(), button_size.height());
  save_button_->SetVisible(true);
  save_button_->SetEnabled(true);

  dx += button_size.width() + kHorizontalButtonPadding;

  button_size = discard_button_->GetPreferredSize();
  discard_button_->SetBounds(dx, (height() - button_size.height()) / 2,
                             button_size.width(), button_size.height());
  discard_button_->SetVisible(true);
  discard_button_->SetEnabled(true);
}

void DownloadItemTabView::Paint(ChromeCanvas* canvas) {
  PaintBackground(canvas);

  if (model_->state() == DownloadItem::IN_PROGRESS  &&
      model_->safety_state() != DownloadItem::DANGEROUS) {
    // For most languages, 'offset' will be 0. For languages where the dangerous
    // download warning is longer than usual, the download view will be slightly
    // larger and 'offset' will be positive value that lines up the progress
    // halo and the file's icon in order to accomodate the larger view.
    int offset = (parent_->big_icon_size() -
                  download_util::kBigProgressIconSize) / 2;
    download_util::PaintDownloadProgress(
        canvas,
        this,
        offset + kDownloadIconOffset -
        parent_->big_icon_offset(),
        offset,
        parent_->start_angle(),
        model_->PercentComplete(),
        download_util::BIG);
  }

  // Most of the UI elements in the DownloadItemTabView are represented as
  // child Views and therefore they get mirrored automatically in
  // right-to-left UIs. The download item icon is not contained within a child
  // View so we need to mirror it manually if the locale is RTL.
  SkBitmap* icon = parent_->LookupIcon(model_);
  if (icon) {
    gfx::Rect icon_bounds(kDownloadIconOffset,
                          parent_->big_icon_offset(),
                          icon->width(), icon->height());
    icon_bounds.set_x(MirroredLeftPointForRect(icon_bounds));
    canvas->DrawBitmapInt(*icon, icon_bounds.x(), icon_bounds.y());
  }
}

void DownloadItemTabView::PaintBackground(ChromeCanvas* canvas) {
  if (parent_->ItemIsSelected(model_)) {
    // Before we paint the border and the focus rect, we need to mirror the
    // highlighted area if the View is using a right-to-left UI layout. We need
    // to explicitly mirror the position because the highlighted area is
    // directly painted on the canvas (as opposed to being represented as a
    // child View like the rest of the UI elements in DownloadItemTabView).
    gfx::Rect highlighted_bounds(kDownloadIconOffset -
                                 parent_->big_icon_offset(),
                                 0,
                                 parent_->big_icon_size() +
                                 kInfoPadding + kFilenameSize,
                                 parent_->big_icon_size());
    highlighted_bounds.set_x(MirroredLeftPointForRect(highlighted_bounds));

    canvas->FillRectInt(kSelectedItemColor,
                        highlighted_bounds.x(),
                        highlighted_bounds.y(),
                        highlighted_bounds.width(),
                        highlighted_bounds.height());

    canvas->DrawFocusRect(highlighted_bounds.x(),
                          highlighted_bounds.y(),
                          highlighted_bounds.width(),
                          highlighted_bounds.height());
  }
}

bool DownloadItemTabView::OnMousePressed(const views::MouseEvent& event) {
  gfx::Point point(event.location());

  // If the click is in the highlight region, then highlight this download.
  // Otherwise, remove the highlighting from any download.
  gfx::Rect select_rect(
      kDownloadIconOffset - parent_->big_icon_offset(),
      0,
      kDownloadIconOffset - parent_->big_icon_offset() +
      parent_->big_icon_size() + kInfoPadding + kFilenameSize,
      parent_->big_icon_size());

  // The position of the highlighted region does not take into account the
  // View's UI layout so we have to manually mirror the position if the View is
  // using a right-to-left UI layout.
  gfx::Rect mirrored_rect(select_rect);
  select_rect.set_x(MirroredLeftPointForRect(mirrored_rect));
  if (select_rect.Contains(point)) {
    parent_->ItemBecameSelected(model_);

    // Don't show the right-click menu if we are prompting the user for a
    // dangerous download.
    if (event.IsRightMouseButton() &&
        model_->safety_state() != DownloadItem::DANGEROUS) {
      views::View::ConvertPointToScreen(this, &point);

      download_util::DownloadDestinationContextMenu menu(
          model_, GetContainer()->GetHWND(), point.ToPOINT());
    }
  } else {
    parent_->ItemBecameSelected(NULL);
  }

  return true;
}

// Handle drag (file copy) operations.
bool DownloadItemTabView::OnMouseDragged(const views::MouseEvent& event) {
  if (model_->state() != DownloadItem::COMPLETE ||
      model_->safety_state() == DownloadItem::DANGEROUS)
    return false;

  CPoint point(event.x(), event.y());

  // In order to make sure drag and drop works as expected when the UI is
  // mirrored, we can either flip the mouse X coordinate or flip the X position
  // of the drag rectangle. Flipping the mouse X coordinate is easier.
  point.x = MirroredXCoordinateInsideView(point.x);
  CRect drag_rect(kDownloadIconOffset -
                  parent_->big_icon_offset(),
                  0,
                  kDownloadIconOffset -
                  parent_->big_icon_offset() +
                  parent_->big_icon_size() + kInfoPadding +
                  kFilenameSize,
                  parent_->big_icon_size());

  if (drag_rect.PtInRect(point)) {
    SkBitmap* icon = parent_->LookupIcon(model_);
    if (icon)
      download_util::DragDownload(model_, icon);
  }

  return true;
}

void DownloadItemTabView::LinkActivated(views::Link* source, int event_flags) {
  // There are several links in our view that could have been clicked:
  if (source == file_name_) {
    views::Container* container = this->GetContainer();
    HWND parent_window = container ? container->GetHWND() : NULL;
    model_->manager()->OpenDownloadInShell(model_, parent_window);
  } else if (source == pause_) {
    model_->TogglePause();
  } else if (source == cancel_) {
    model_->Cancel(true /* update history service */);
  } else if (source == show_) {
    model_->manager()->ShowDownloadInShell(model_);
  } else {
    NOTREACHED();
  }

  parent_->ItemBecameSelected(model_);
}

void DownloadItemTabView::ButtonPressed(views::NativeButton* sender) {
  if (sender == save_button_) {
    parent_->model()->DangerousDownloadValidated(model_);
    // Relayout and repaint to display the right mode (complete or in progress).
    Layout();
    SchedulePaint();
  } else if (sender == discard_button_) {
    model_->Remove(true);
  } else  {
    NOTREACHED();
  }
}

// DownloadTabView implementation ----------------------------------------------

DownloadTabView::DownloadTabView(DownloadManager* model)
    : model_(model),
      big_icon_size_(download_util::GetBigProgressIconSize()),
      big_icon_offset_(download_util::GetBigProgressIconOffset()),
      start_angle_(download_util::kStartAngleDegrees),
      scroll_helper_(kSpacer, big_icon_size_ + kSpacer),
      selected_index_(-1) {
  DCHECK(model_);
}

DownloadTabView::~DownloadTabView() {
  StopDownloadProgress();
  model_->RemoveObserver(this);

  // DownloadManager owns the contents.
  for (OrderedDownloads::iterator it = downloads_.begin();
       it != downloads_.end(); ++it) {
    (*it)->RemoveObserver(this);
  }
  downloads_.clear();
  ClearDownloadInProgress();
  ClearDangerousDownloads();


  icon_consumer_.CancelAllRequests();
}

void DownloadTabView::Initialize() {
  model_->AddObserver(this);
}

// Start progress animation timers when we get our first (in-progress) download.
void DownloadTabView::StartDownloadProgress() {
  if (progress_timer_.IsRunning())
    return;
  progress_timer_.Start(
      TimeDelta::FromMilliseconds(download_util::kProgressRateMs), this,
      &DownloadTabView::UpdateDownloadProgress);
}

// Stop progress animation when there are no more in-progress downloads.
void DownloadTabView::StopDownloadProgress() {
  progress_timer_.Stop();
}

// Update our animations.
void DownloadTabView::UpdateDownloadProgress() {
  start_angle_ = (start_angle_ + download_util::kUnknownIncrementDegrees) %
                 download_util::kMaxDegrees;
  SchedulePaint();
}

void DownloadTabView::Layout() {
  DetachAllFloatingViews();
  // Dangerous downloads items use NativeButtons, so they need to be attached
  // as NativeControls are not supported yet in floating views.
  gfx::Rect visible_bounds = GetVisibleBounds();
  int row_start = (visible_bounds.y() - kSpacer) /
                  (big_icon_size_ + kSpacer);
  int row_stop = (visible_bounds.y() - kSpacer + visible_bounds.height()) /
                 (big_icon_size_ + kSpacer);
  row_stop = std::min(row_stop, static_cast<int>(downloads_.size()) - 1);
  for (int i = row_start; i <= row_stop; ++i) {
    // The DownloadManager stores downloads earliest first, but this view
    // displays latest first, so adjust the index:
    int index = static_cast<int>(downloads_.size()) - 1 - i;
    if (downloads_[index]->safety_state() == DownloadItem::DANGEROUS)
      ValidateFloatingViewForID(index);
  }
  View* v = GetParent();
  if (v) {
    int h = static_cast<int>(downloads_.size()) *
            (big_icon_size_ + kSpacer) + kSpacer;
    SetBounds(x(), y(), v->width(), h);
  }
}

// Paint our scrolled region
void DownloadTabView::Paint(ChromeCanvas* canvas) {
  views::View::Paint(canvas);

  if (download_util::kBigIconSize == 0 || downloads_.size() == 0)
    return;

  SkRect clip;
  if (canvas->getClipBounds(&clip)) {
    int row_start = (SkScalarRound(clip.fTop) - kSpacer) /
                    (big_icon_size_ + kSpacer);
    int row_stop = SkScalarRound(clip.fBottom) / (big_icon_size_ + kSpacer);
    SkRect download_rect;
    for (int i = row_start; i <= row_stop; ++i) {
      int y = i * (big_icon_size_ + kSpacer) + kSpacer;
      if (HasFloatingViewForPoint(0, y))
        continue;
      download_rect.set(SkIntToScalar(0),
                        SkIntToScalar(y),
                        SkIntToScalar(width()),
                        SkIntToScalar(y + big_icon_size_));
      if (SkRect::Intersects(clip, download_rect)) {
        // The DownloadManager stores downloads earliest first, but this
        // view displays latest first, so adjust the index:
        int index = static_cast<int>(downloads_.size()) - 1 - i;
        download_renderer_.SetModel(downloads_[index], this);
        PaintFloatingView(canvas, &download_renderer_,
                          0, y, width(), big_icon_size_);
      }
    }
  }
}

// Draw the DownloadItemTabView for the current position.
bool DownloadTabView::GetFloatingViewIDForPoint(int x, int y, int* id) {
  if (y < kSpacer ||
      y > (kSpacer + big_icon_size_) * static_cast<int>(downloads_.size()))
    return false;

  // Are we hovering over a download or the spacer? If we're over the
  // download, create a floating view for it.
  if ((y - kSpacer) % (big_icon_size_ + kSpacer) < big_icon_size_) {
    int row = y / (big_icon_size_ + kSpacer);
    *id = static_cast<int>(downloads_.size()) - 1 - row;
    return true;
  }
  return false;
}

views::View* DownloadTabView::CreateFloatingViewForIndex(int index) {
  if (index >= static_cast<int>(downloads_.size())) {
    // It's possible that the downloads have been cleared via the "Clear
    // Browsing Data" command, so this index is gone.
    return NULL;
  }

  DownloadItemTabView* dl = new DownloadItemTabView();
  // We attach the view before layout as the Save/Discard buttons are native
  // and need to be in the tree hierarchy to compute their preferred size
  // correctly.
  AttachFloatingView(dl, index);
  dl->SetModel(downloads_[index], this);
  int row = static_cast<int>(downloads_.size()) - 1 - index;
  int y_pos = row * (big_icon_size_ + kSpacer) + kSpacer;
  dl->SetBounds(0, y_pos, width(), big_icon_size_);
  dl->Layout();
  return dl;
}

bool DownloadTabView::EnumerateFloatingViews(
      views::View::FloatingViewPosition position,
      int starting_id, int* id) {
  DCHECK(id);
  return View::EnumerateFloatingViewsForInterval(
      0, static_cast<int>(downloads_.size()), false, position, starting_id, id);
}

views::View* DownloadTabView::ValidateFloatingViewForID(int id) {
  return CreateFloatingViewForIndex(id);
}

void DownloadTabView::OnDownloadUpdated(DownloadItem* download) {
  switch (download->state()) {
    case DownloadItem::COMPLETE:
    case DownloadItem::CANCELLED: {
      base::hash_set<DownloadItem*>::iterator d = in_progress_.find(download);
      if (d != in_progress_.end()) {
        // If this is a dangerous download not yet validated by the user, we
        // still need to be notified when the validation happens.
        if (download->safety_state() != DownloadItem::DANGEROUS)
          (*d)->RemoveObserver(this);
        in_progress_.erase(d);
      }
      if (in_progress_.empty())
        StopDownloadProgress();
      LoadIcon(download);
      break;
    }
    case DownloadItem::IN_PROGRESS: {
      // If all IN_PROGRESS downloads are paused, don't waste CPU issuing any
      // further progress updates until at least one download is active again.
      if (download->is_paused()) {
        bool continue_update = false;
        base::hash_set<DownloadItem*>::iterator it = in_progress_.begin();
        for (; it != in_progress_.end(); ++it) {
          if (!(*it)->is_paused()) {
            continue_update = true;
            break;
          }
        }
        if (!continue_update)
          StopDownloadProgress();
      } else {
        StartDownloadProgress();
      }
      break;
    }
    case DownloadItem::REMOVING:
      // Handled below.
      break;
    default:
      NOTREACHED();
      break;
  }

  OrderedDownloads::iterator it = find(downloads_.begin(),
                                       downloads_.end(),
                                       download);
  if (it == downloads_.end())
    return;

  const int index = static_cast<int>(it - downloads_.begin());
  DownloadItemTabView* view =
      static_cast<DownloadItemTabView*>(RetrieveFloatingViewForID(index));
  if (view) {
    if (download->state() != DownloadItem::REMOVING) {
      view->Layout();
      SchedulePaintForViewAtIndex(index);
    } else if (selected_index_ == index) {
      selected_index_ = -1;
    }
  }
}

// A download has started or been deleted. Query our DownloadManager for the
// current set of downloads, which will call us back in SetDownloads once it
// has retrieved them.
void DownloadTabView::ModelChanged() {
  downloads_.clear();
  ClearDownloadInProgress();
  ClearDangerousDownloads();
  DetachAllFloatingViews();

  // Issue the query.
  model_->GetDownloads(this, search_text_);
}

void DownloadTabView::SetDownloads(std::vector<DownloadItem*>& downloads) {
  // Stop progress timers.
  StopDownloadProgress();

  // Clear out old state and remove self as observer for each download.
  downloads_.clear();
  ClearDownloadInProgress();
  ClearDangerousDownloads();

  // Swap new downloads in.
  downloads_.swap(downloads);
  sort(downloads_.begin(), downloads_.end(), DownloadItemSorter());

  // Scan for any in progress downloads and add ourself to them as an observer.
  for (OrderedDownloads::iterator it = downloads_.begin();
       it != downloads_.end(); ++it) {
    DownloadItem* download = *it;
    if (download->state() == DownloadItem::IN_PROGRESS) {
      download->AddObserver(this);
      in_progress_.insert(download);
    } else if (download->safety_state() == DownloadItem::DANGEROUS) {
      // We need to be notified when the user validates the dangerous download.
      download->AddObserver(this);
      dangerous_downloads_.insert(download);
    }
  }

  // Start any progress timers if required.
  if (!in_progress_.empty())
    StartDownloadProgress();

  // Update the UI.
  selected_index_ = -1;
  GetParent()->GetParent()->Layout();
  SchedulePaint();
}


// If we have the icon in our cache, then return it. If not, look it up via the
// IconManager. Ignore in progress requests (duplicates).
SkBitmap* DownloadTabView::LookupIcon(DownloadItem* download) {
  IconManager* im = g_browser_process->icon_manager();
  // Fast look up.
  SkBitmap* icon = im->LookupIcon(download->full_path(), IconLoader::NORMAL);

  // Expensive look up.
  if (!icon)
    LoadIcon(download);

  return icon;
}

// Bypass the caches and perform the Icon extraction directly. This is useful in
// the case where the download has completed and we want to re-check the file
// to see if it has an embedded icon (which we couldn't do at download start).
void DownloadTabView::LoadIcon(DownloadItem* download) {
  IconManager* im = g_browser_process->icon_manager();
  IconManager::Handle h =
      im->LoadIcon(download->full_path(), IconLoader::NORMAL,
                   &icon_consumer_,
                   NewCallback(this, &DownloadTabView::OnExtractIconComplete));
  icon_consumer_.SetClientData(im, h, download);
}

void DownloadTabView::ClearDownloadInProgress() {
  for (base::hash_set<DownloadItem*>::iterator it = in_progress_.begin();
       it != in_progress_.end(); ++it)
    (*it)->RemoveObserver(this);
  in_progress_.clear();
}

void DownloadTabView::ClearDangerousDownloads() {
  base::hash_set<DownloadItem*>::const_iterator it;
  for (it = dangerous_downloads_.begin();
       it != dangerous_downloads_.end(); ++it)
    (*it)->RemoveObserver(this);
  dangerous_downloads_.clear();
}

// Check to see if the download is the latest download on a given day.
// We use this to determine when to draw the date next to a particular
// download view: if the DownloadItem is the latest download on a given
// day, the date gets drawn.
bool DownloadTabView::ShouldDrawDateForDownload(DownloadItem* download) {
  DCHECK(download);
  OrderedDownloads::iterator it = find(downloads_.begin(),
                                       downloads_.end(),
                                       download);
  DCHECK(it != downloads_.end());
  const int index = static_cast<int>(it - downloads_.begin());

  // If download is the last or only download, it draws the date.
  if (downloads_.size() - 1 == index)
    return true;

  const DownloadItem* next = downloads_[index + 1];

  Time next_midnight = next->start_time().LocalMidnight();
  Time curr_midnight = download->start_time().LocalMidnight();
  if (next_midnight == curr_midnight) {
    // 'next' happened today: let it draw the date so we don't have to.
    return false;
  }
  return true;
}

int DownloadTabView::GetPageScrollIncrement(
    views::ScrollView* scroll_view, bool is_horizontal,
    bool is_positive) {
  return scroll_helper_.GetPageScrollIncrement(scroll_view, is_horizontal,
                                               is_positive);
}

int DownloadTabView::GetLineScrollIncrement(
    views::ScrollView* scroll_view, bool is_horizontal,
    bool is_positive) {
  return scroll_helper_.GetLineScrollIncrement(scroll_view, is_horizontal,
                                               is_positive);
}

void DownloadTabView::ItemBecameSelected(const DownloadItem* download) {
  int index = -1;
  if (download != NULL) {
    OrderedDownloads::const_iterator it = find(downloads_.begin(),
                                               downloads_.end(),
                                               download);
    DCHECK(it != downloads_.end());
    index = static_cast<int>(it - downloads_.begin());
    if (index == selected_index_)
      return;  // Avoid unnecessary paint.
  }

  if (selected_index_ >= 0)
    SchedulePaintForViewAtIndex(selected_index_);
  if (index >= 0)
    SchedulePaintForViewAtIndex(index);
  selected_index_ = index;
}

bool DownloadTabView::ItemIsSelected(DownloadItem* download) {
  OrderedDownloads::iterator it = find(downloads_.begin(),
                                       downloads_.end(),
                                       download);
  if (it != downloads_.end())
    return selected_index_ == static_cast<int>(it - downloads_.begin());
  return false;
}

void DownloadTabView::SchedulePaintForViewAtIndex(int index) {
  int y = GetYPositionForIndex(index);
  SchedulePaint(0, y, width(), big_icon_size_);
}

int DownloadTabView::GetYPositionForIndex(int index) {
  int row = static_cast<int>(downloads_.size()) - 1 - index;
  return row * (big_icon_size_ + kSpacer) + kSpacer;
}

void DownloadTabView::SetSearchText(const std::wstring& search_text) {
  search_text_ = search_text;
  model_->GetDownloads(this, search_text_);
}

// The 'icon_bitmap' is ignored here, since it is cached by the IconManager.
// When the paint message runs, we'll use the fast IconManager lookup API to
// retrieve it.
void DownloadTabView::OnExtractIconComplete(IconManager::Handle handle,
                                            SkBitmap* icon_bitmap) {
  IconManager* im = g_browser_process->icon_manager();
  DownloadItem* download = icon_consumer_.GetClientData(im, handle);
  OrderedDownloads::iterator it = find(downloads_.begin(),
                                       downloads_.end(),
                                       download);
  if (it != downloads_.end()) {
    const int index = static_cast<int>(it - downloads_.begin());
    SchedulePaintForViewAtIndex(index);
  }
}

// DownloadTabUIFactory ------------------------------------------------------

class DownloadTabUIFactory : public NativeUIFactory {
 public:
  DownloadTabUIFactory() {}
  virtual ~DownloadTabUIFactory() {}

  virtual NativeUI* CreateNativeUIForURL(const GURL& url,
                                         NativeUIContents* contents) {
    return new DownloadTabUI(contents);
  }

 private:
  DISALLOW_EVIL_CONSTRUCTORS(DownloadTabUIFactory);
};

// DownloadTabUI -------------------------------------------------------------

DownloadTabUI::DownloadTabUI(NativeUIContents* contents)
#pragma warning(suppress: 4355)  // Okay to pass "this" here.
    : searchable_container_(this),
      download_tab_view_(NULL),
      contents_(contents) {
  DownloadManager* dlm = contents_->profile()->GetDownloadManager();
  download_tab_view_ = new DownloadTabView(dlm);
  searchable_container_.SetContents(download_tab_view_);
  download_tab_view_->Initialize();

  NotificationService* ns = NotificationService::current();
  ns->AddObserver(this, NOTIFY_DOWNLOAD_START,
                  NotificationService::AllSources());
  ns->AddObserver(this, NOTIFY_DOWNLOAD_STOP,
                  NotificationService::AllSources());

  // Spin the throbber if there are active downloads, since we may have been
  // created after the NOTIFY_DOWNLOAD_START was sent. If the download manager
  // has not been created, don't bother since it will negatively impact start
  // up time with history requests.
  Profile* profile = contents_->profile();
  if (profile &&
      profile->HasCreatedDownloadManager() &&
      profile->GetDownloadManager()->in_progress_count() > 0)
    contents_->SetIsLoading(true, NULL);
}

DownloadTabUI::~DownloadTabUI() {
  NotificationService* ns = NotificationService::current();
  ns->RemoveObserver(this, NOTIFY_DOWNLOAD_START,
                     NotificationService::AllSources());
  ns->RemoveObserver(this, NOTIFY_DOWNLOAD_STOP,
                     NotificationService::AllSources());
}

const std::wstring DownloadTabUI::GetTitle() const {
  return l10n_util::GetString(IDS_DOWNLOAD_TITLE);
}

const int DownloadTabUI::GetFavIconID() const {
  return IDR_DOWNLOADS_FAVICON;
}

const int DownloadTabUI::GetSectionIconID() const {
  return IDR_DOWNLOADS_SECTION;
}

const std::wstring DownloadTabUI::GetSearchButtonText() const {
  return l10n_util::GetString(IDS_DOWNLOAD_SEARCH_BUTTON);
}

views::View* DownloadTabUI::GetView() {
  return &searchable_container_;
}

void DownloadTabUI::WillBecomeVisible(NativeUIContents* parent) {
  UserMetrics::RecordAction(L"Destination_Downloads", parent->profile());
}

void DownloadTabUI::WillBecomeInvisible(NativeUIContents* parent) {
}

void DownloadTabUI::Navigate(const PageState& state) {
  std::wstring search_text;
  state.GetProperty(kSearchTextKey, &search_text);
  download_tab_view_->SetSearchText(search_text);
  searchable_container_.GetSearchField()->SetText(search_text);
}

bool DownloadTabUI::SetInitialFocus() {
  searchable_container_.GetSearchField()->RequestFocus();
  return true;
}

// static
GURL DownloadTabUI::GetURL() {
  std::string spec(NativeUIContents::GetScheme());
  spec.append("://downloads");
  return GURL(spec);
}

// static
NativeUIFactory* DownloadTabUI::GetNativeUIFactory() {
  return new DownloadTabUIFactory();
}

void DownloadTabUI::DoSearch(const std::wstring& new_text) {
  download_tab_view_->SetSearchText(new_text);
  PageState* page_state = contents_->page_state().Copy();
  page_state->SetProperty(kSearchTextKey, new_text);
  contents_->SetPageState(page_state);
}

void DownloadTabUI::Observe(NotificationType type,
                            const NotificationSource& source,
                            const NotificationDetails& details) {
  switch (type) {
    case NOTIFY_DOWNLOAD_START:
    case NOTIFY_DOWNLOAD_STOP:
      DCHECK(profile()->HasCreatedDownloadManager());
      contents_->SetIsLoading(
          profile()->GetDownloadManager()->in_progress_count() > 0,
          NULL);
      break;
    default:
      break;
  }
}