// 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 "pdf/thumbnail_control.h"

#include <algorithm>

#include "base/logging.h"
#include "base/strings/string_util.h"
#include "pdf/draw_utils.h"
#include "pdf/number_image_generator.h"

namespace chrome_pdf {

const int kLeftBorderSize = 52;
const int kBorderSize = 12;
const int kHighlightBorderSize = 2;

const uint32 kLeftColor = 0x003F537B;
const uint32 kRightColor = 0x990D1626;

const uint32 kTopHighlightColor = 0xFF426DC9;
const uint32 kBottomHighlightColor = 0xFF6391DE;
const uint32 kThumbnailBackgroundColor = 0xFF000000;

const uint32 kSlidingTimeoutMs = 50;
const int32 kSlidingShift = 50;

const double kNonSelectedThumbnailAlpha = 0.91;

ThumbnailControl::ThumbnailControl()
    : engine_(NULL), sliding_width_(0), sliding_shift_(kSlidingShift),
      sliding_timeout_(kSlidingTimeoutMs), sliding_timer_id_(0) {
}

ThumbnailControl::~ThumbnailControl() {
  ClearCache();
}

bool ThumbnailControl::CreateThumbnailControl(
    uint32 id, const pp::Rect& rc,
    bool visible, Owner* owner, PDFEngine* engine,
    NumberImageGenerator* number_image_generator) {
  engine_ = engine;
  number_image_generator_ = number_image_generator;
  sliding_width_ = rc.width();

  return Control::Create(id, rc, visible, owner);
}

void ThumbnailControl::SetPosition(int position, int total, bool invalidate) {
  visible_rect_ = pp::Rect();
  visible_pages_.clear();

  if (rect().width() < kLeftBorderSize + kBorderSize) {
    return;  // control is too narrow to show thumbnails.
  }

  int num_pages = engine_->GetNumberOfPages();

  int max_doc_width = 0, total_doc_height = 0;
  std::vector<pp::Rect> page_sizes(num_pages);
  for (int i = 0; i < num_pages; ++i) {
    page_sizes[i] = engine_->GetPageRect(i);
    max_doc_width = std::max(max_doc_width, page_sizes[i].width());
    total_doc_height += page_sizes[i].height();
  }

  if (!max_doc_width)
    return;

  int max_thumbnail_width = rect().width() - kLeftBorderSize - kBorderSize;
  double thumbnail_ratio =
      max_thumbnail_width / static_cast<double>(max_doc_width);

  int total_thumbnail_height = 0;
  for (int i = 0; i < num_pages; ++i) {
    total_thumbnail_height += kBorderSize;
    int thumbnail_width =
        static_cast<int>(page_sizes[i].width() * thumbnail_ratio);
    int thumbnail_height =
        static_cast<int>(page_sizes[i].height() * thumbnail_ratio);
    int x = (max_thumbnail_width - thumbnail_width) / 2;
    page_sizes[i] =
        pp::Rect(x, total_thumbnail_height, thumbnail_width, thumbnail_height);
    total_thumbnail_height += thumbnail_height;
  }
  total_thumbnail_height += kBorderSize;

  int visible_y = 0;
  if (total > 0) {
    double range = total_thumbnail_height - rect().height();
    if (range < 0)
      range = 0;
    visible_y = static_cast<int>(range * position / total);
  }
  visible_rect_ = pp::Rect(0, visible_y, max_thumbnail_width, rect().height());

  for (int i = 0; i < num_pages; ++i) {
    if (page_sizes[i].Intersects(visible_rect_)) {
      PageInfo page_info;
      page_info.index = i;
      page_info.rect = page_sizes[i];
      page_info.rect.Offset(kLeftBorderSize, -visible_rect_.y());
      visible_pages_.push_back(page_info);
    }
  }

  if (invalidate)
    owner()->Invalidate(id(), rect());
}

void ThumbnailControl::Show(bool visible, bool invalidate) {
  if (!visible || invalidate)
    ClearCache();
  sliding_width_ = rect().width();
  Control::Show(visible, invalidate);
}

void ThumbnailControl::SlideIn() {
  if (visible())
    return;

  Show(true, false);
  sliding_width_ = 0;
  sliding_shift_ = kSlidingShift;

  sliding_timer_id_ = owner()->ScheduleTimer(id(), sliding_timeout_);
  owner()->Invalidate(id(), rect());
}

void ThumbnailControl::SlideOut() {
  if (!visible())
    return;
  sliding_shift_ = -kSlidingShift;
  sliding_timer_id_ = owner()->ScheduleTimer(id(), sliding_timeout_);
}

void ThumbnailControl::Paint(pp::ImageData* image_data, const pp::Rect& rc) {
  if (!visible())
    return;

  pp::Rect control_rc(rect());
  control_rc.Offset(control_rc.width() - sliding_width_, 0);
  control_rc.set_width(sliding_width_);

  pp::Rect draw_rc = rc.Intersect(control_rc);
  if (draw_rc.IsEmpty())
    return;

  pp::Rect gradient_rc(control_rc.x(), draw_rc.y(),
                       control_rc.width(), draw_rc.height());
  GradientFill(owner()->GetInstance(),
               image_data,
               draw_rc,
               gradient_rc,
               kLeftColor,
               kRightColor,
               true,
               transparency());

  int selected_page = engine_->GetMostVisiblePage();
  for (size_t i = 0; i < visible_pages_.size(); ++i) {
    pp::Rect page_rc = visible_pages_[i].rect;
    page_rc.Offset(control_rc.point());

    if (visible_pages_[i].index == selected_page) {
      pp::Rect highlight_rc = page_rc;
      highlight_rc.Inset(-kHighlightBorderSize, -kHighlightBorderSize);
      GradientFill(owner()->GetInstance(),
                   image_data,
                   draw_rc,
                   highlight_rc,
                   kTopHighlightColor,
                   kBottomHighlightColor,
                   false,
                   transparency());
    }

    pp::Rect draw_page_rc = page_rc.Intersect(draw_rc);
    if (draw_page_rc.IsEmpty())
      continue;

    // First search page image in the cache.
    pp::ImageData* thumbnail = NULL;
    std::map<int, pp::ImageData*>::iterator it =
        image_cache_.find(visible_pages_[i].index);
    if (it != image_cache_.end()) {
      if (it->second->size() == page_rc.size())
        thumbnail = image_cache_[visible_pages_[i].index];
      else
        image_cache_.erase(it);
    }

    // If page is not found in the cache, create new one.
    if (thumbnail == NULL) {
      thumbnail = new pp::ImageData(owner()->GetInstance(),
                                    PP_IMAGEDATAFORMAT_BGRA_PREMUL,
                                    page_rc.size(),
                                    false);
      engine_->PaintThumbnail(thumbnail, visible_pages_[i].index);

      pp::ImageData page_number;
      number_image_generator_->GenerateImage(
          visible_pages_[i].index + 1, &page_number);
      pp::Point origin(
          (thumbnail->size().width() - page_number.size().width()) / 2,
          (thumbnail->size().height() - page_number.size().height()) / 2);

      if (origin.x() > 0 && origin.y() > 0) {
        AlphaBlend(page_number, pp::Rect(pp::Point(), page_number.size()),
                   thumbnail, origin, kOpaqueAlpha);
      }

      image_cache_[visible_pages_[i].index] = thumbnail;
    }

    uint8 alpha = transparency();
    if (visible_pages_[i].index != selected_page)
      alpha = static_cast<uint8>(alpha * kNonSelectedThumbnailAlpha);
    FillRect(image_data, draw_page_rc, kThumbnailBackgroundColor);
    draw_page_rc.Offset(-page_rc.x(), -page_rc.y());
    AlphaBlend(*thumbnail, draw_page_rc, image_data,
        draw_page_rc.point() + page_rc.point(), alpha);
  }
}

bool ThumbnailControl::HandleEvent(const pp::InputEvent& event) {
  if (!visible())
    return false;

  pp::MouseInputEvent mouse_event(event);
  if (mouse_event.is_null())
    return false;
  pp::Point pt = mouse_event.GetPosition();
  if (!rect().Contains(pt))
    return false;

  int over_page = -1;
  for (size_t i = 0; i < visible_pages_.size(); ++i) {
    pp::Rect page_rc = visible_pages_[i].rect;
    page_rc.Offset(rect().point());
    if (page_rc.Contains(pt)) {
      over_page = i;
      break;
    }
  }

  bool handled = false;
  switch (event.GetType()) {
    case PP_INPUTEVENT_TYPE_MOUSEMOVE:
      owner()->SetCursor(id(),
          over_page == -1 ? PP_CURSORTYPE_POINTER : PP_CURSORTYPE_HAND);
      break;
    case PP_INPUTEVENT_TYPE_MOUSEDOWN:
      if (over_page != -1) {
        owner()->Invalidate(id(), rect());
        owner()->OnEvent(id(), EVENT_ID_THUMBNAIL_SELECTED,
                         &visible_pages_[over_page].index);
      }
      handled = true;
      break;
    default:
      break;
  }

  return handled;
}

void ThumbnailControl::OnTimerFired(uint32 timer_id) {
  if (timer_id == sliding_timer_id_) {
    sliding_width_ += sliding_shift_;
    if (sliding_width_ <= 0) {
      // We completely slided out. Make control invisible now.
      Show(false, false);
    } else if (sliding_width_ >= rect().width()) {
      // We completely slided in. Make sliding width to full control width.
      sliding_width_ = rect().width();
    } else {
      // We have not completed sliding yet. Keep sliding.
      sliding_timer_id_ = owner()->ScheduleTimer(id(), sliding_timeout_);
    }
    owner()->Invalidate(id(), rect());
  }
}

void ThumbnailControl::ResetEngine(PDFEngine* engine) {
  engine_ = engine;
  ClearCache();
}

void ThumbnailControl::ClearCache() {
  std::map<int, pp::ImageData*>::iterator it;
  for (it = image_cache_.begin(); it != image_cache_.end(); ++it) {
    delete it->second;
  }
  image_cache_.clear();
}

}  // namespace chrome_pdf