// Copyright 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 "cc/picture_layer_tiling.h"

#include "cc/math_util.h"
#include "ui/gfx/rect_conversions.h"
#include "ui/gfx/size_conversions.h"

namespace cc {

scoped_ptr<PictureLayerTiling> PictureLayerTiling::Create(
    float contents_scale,
    gfx::Size tile_size) {
  return make_scoped_ptr(new PictureLayerTiling(contents_scale, tile_size));
}

scoped_ptr<PictureLayerTiling> PictureLayerTiling::Clone() const {
  return make_scoped_ptr(new PictureLayerTiling(*this));
}

PictureLayerTiling::PictureLayerTiling(float contents_scale,
                                       gfx::Size tile_size)
    : client_(NULL),
      contents_scale_(contents_scale),
      tiling_data_(tile_size, gfx::Size(), true) {
}

PictureLayerTiling::~PictureLayerTiling() {
}

const PictureLayerTiling& PictureLayerTiling::operator=(
    const PictureLayerTiling& tiler) {
  tiling_data_ = tiler.tiling_data_;
  tiles_ = tiler.tiles_;
  return *this;
}

void PictureLayerTiling::SetClient(PictureLayerTilingClient* client) {
  client_ = client;
}

gfx::Rect PictureLayerTiling::ContentRect() const {
  gfx::Size content_bounds =
      gfx::ToCeiledSize(gfx::ScaleSize(layer_bounds_, contents_scale_));
  return gfx::Rect(gfx::Point(), content_bounds);
}

Tile* PictureLayerTiling::TileAt(int i, int j) const {
  TileMap::const_iterator iter = tiles_.find(TileMapKey(i, j));
  if (iter == tiles_.end())
    return NULL;
  return iter->second.get();
}

void PictureLayerTiling::CreateTile(int i, int j) {
  gfx::Rect tile_rect = tiling_data_.TileBoundsWithBorder(i, j);
  tile_rect.set_size(tiling_data_.max_texture_size());
  TileMapKey key(i, j);
  DCHECK(!tiles_[key]);
  tiles_[key] = client_->CreateTile(this, tile_rect);

  // TODO(enne): Remove this when we start setting priorities correctly.
  TilePriority priority;
  priority.resolution = HIGH_RESOLUTION;
  priority.time_to_visible_in_seconds = 1000;
  tiles_[key]->set_priority(ACTIVE_TREE, priority);
}

Region PictureLayerTiling::OpaqueRegionInContentRect(
    const gfx::Rect& content_rect) const {
  Region opaque_region;
  // TODO(enne): implement me
  return opaque_region;
}

void PictureLayerTiling::SetLayerBounds(gfx::Size layer_bounds) {
  if (layer_bounds_ == layer_bounds)
    return;

  layer_bounds_ = layer_bounds;
  gfx::Size content_bounds =
      gfx::ToCeiledSize(gfx::ScaleSize(layer_bounds_, contents_scale_));

  tiling_data_.SetTotalSize(content_bounds);
  if (layer_bounds_.IsEmpty()) {
    tiles_.clear();
    return;
  }

  int right = tiling_data_.TileXIndexFromSrcCoord(content_bounds.width() - 1);
  int bottom = tiling_data_.TileYIndexFromSrcCoord(content_bounds.height() - 1);

  // TODO(enne): Be more efficient about what tiles are created.
  for (int j = 0; j <= bottom; ++j) {
    for (int i = 0; i <= right; ++i) {
      if (tiles_.find(TileMapKey(i, j)) == tiles_.end())
        CreateTile(i, j);
    }
  }

  // Any tiles outside our new bounds are invalid and should be dropped.
  std::vector<TileMapKey> invalid_tile_keys;
  for (TileMap::const_iterator it = tiles_.begin(); it != tiles_.end(); ++it) {
    if (it->first.first > right || it->first.second > bottom)
      invalid_tile_keys.push_back(it->first);
  }
  for (size_t i = 0; i < invalid_tile_keys.size(); ++i)
    tiles_.erase(invalid_tile_keys[i]);
}

void PictureLayerTiling::Invalidate(const Region& layer_invalidation) {
  std::vector<TileMapKey> new_tiles;

  for (Region::Iterator region_iter(layer_invalidation);
       region_iter.has_rect();
       region_iter.next()) {

    gfx::Rect rect =
        gfx::ToEnclosingRect(ScaleRect(region_iter.rect(), contents_scale_));
    rect.Intersect(ContentRect());

    for (PictureLayerTiling::Iterator tile_iter(this, contents_scale_, rect);
         tile_iter;
         ++tile_iter) {
      TileMapKey key(tile_iter.tile_i_, tile_iter.tile_j_);
      if (!tiles_[key])
        continue;

      tiles_[key] = NULL;
      new_tiles.push_back(key);
    }
  }

  for (size_t i = 0; i < new_tiles.size(); ++i) {
    CreateTile(new_tiles[i].first, new_tiles[i].second);
  }
}

PictureLayerTiling::Iterator::Iterator()
    : tiling_(NULL),
      current_tile_(NULL),
      tile_i_(0),
      tile_j_(0),
      left_(0),
      top_(0),
      right_(0),
      bottom_(0) {
}

PictureLayerTiling::Iterator::Iterator(PictureLayerTiling* tiling,
                                       float dest_scale,
                                       gfx::Rect dest_rect)
    : tiling_(tiling),
      dest_rect_(dest_rect),
      dest_to_content_scale_(tiling_->contents_scale_ / dest_scale),
      current_tile_(NULL),
      tile_i_(0),
      tile_j_(0),
      left_(0),
      top_(0),
      right_(0),
      bottom_(0) {
  DCHECK(tiling_);
  if (dest_rect_.IsEmpty())
    return;

  gfx::Rect content_rect =
      gfx::ToEnclosingRect(gfx::ScaleRect(dest_rect_, dest_to_content_scale_));

  left_ = tiling_->tiling_data_.TileXIndexFromSrcCoord(content_rect.x());
  top_ = tiling_->tiling_data_.TileYIndexFromSrcCoord(content_rect.y());
  right_ = tiling_->tiling_data_.TileXIndexFromSrcCoord(
      content_rect.right() - 1);
  bottom_ = tiling_->tiling_data_.TileYIndexFromSrcCoord(
      content_rect.bottom() - 1);

  tile_i_ = left_ - 1;
  tile_j_ = top_;
  ++(*this);
}

PictureLayerTiling::Iterator::~Iterator() {
}

PictureLayerTiling::Iterator& PictureLayerTiling::Iterator::operator++() {
  if (tile_j_ > bottom_)
    return *this;

  bool first_time = tile_i_ < left_;
  bool new_row = false;
  tile_i_++;
  if (tile_i_ > right_) {
    tile_i_ = left_;
    tile_j_++;
    new_row = true;
    if (tile_j_ > bottom_) {
      current_tile_ = NULL;
      return *this;
    }
  }

  current_tile_ = tiling_->TileAt(tile_i_, tile_j_);

  // Calculate the current geometry rect.  Due to floating point rounding
  // and ToEnclosedRect, tiles might overlap in destination space on the
  // edges.
  gfx::Rect last_geometry_rect = current_geometry_rect_;

  gfx::Rect content_rect = tiling_->tiling_data_.TileBounds(tile_i_, tile_j_);
  current_geometry_rect_ = gfx::ToEnclosingRect(
      gfx::ScaleRect(content_rect, 1 / dest_to_content_scale_));
  current_geometry_rect_.Intersect(dest_rect_);

  if (first_time)
    return *this;

  // Iteration happens left->right, top->bottom.  Running off the bottom-right
  // edge is handled by the intersection above with dest_rect_.  Here we make
  // sure that the new current geometry rect doesn't overlap with the last.
  int min_left;
  int min_top;
  if (new_row) {
    min_left = dest_rect_.x();
    min_top = last_geometry_rect.bottom();
  } else {
    min_left = last_geometry_rect.right();
    min_top = last_geometry_rect.y();
  }

  int inset_left = std::max(0, min_left - current_geometry_rect_.x());
  int inset_top = std::max(0, min_top - current_geometry_rect_.y());
  current_geometry_rect_.Inset(inset_left, inset_top, 0, 0);

  if (!new_row) {
    DCHECK_EQ(last_geometry_rect.right(), current_geometry_rect_.x());
    DCHECK_EQ(last_geometry_rect.bottom(), current_geometry_rect_.bottom());
    DCHECK_EQ(last_geometry_rect.y(), current_geometry_rect_.y());
  }

  return *this;
}

gfx::Rect PictureLayerTiling::Iterator::geometry_rect() const {
  return current_geometry_rect_;
}

gfx::RectF PictureLayerTiling::Iterator::texture_rect() const {
  gfx::Rect full_bounds = tiling_->tiling_data_.TileBoundsWithBorder(tile_i_,
                                                                     tile_j_);
  full_bounds.set_size(texture_size());

  // Convert from dest space => content space => texture space.
  gfx::RectF texture_rect = gfx::ScaleRect(current_geometry_rect_,
                                           dest_to_content_scale_);
  texture_rect.Offset(-full_bounds.OffsetFromOrigin());

  DCHECK_GE(texture_rect.x(), 0);
  DCHECK_GE(texture_rect.y(), 0);
  DCHECK_LE(texture_rect.right(), texture_size().width());
  DCHECK_LE(texture_rect.bottom(), texture_size().height());

  return texture_rect;
}

gfx::Size PictureLayerTiling::Iterator::texture_size() const {
  return tiling_->tiling_data_.max_texture_size();
}

void PictureLayerTiling::UpdateTilePriorities(
    const gfx::Size& device_viewport,
    float layer_content_scale_x,
    float layer_content_scale_y,
    const gfx::Transform& last_screen_transform,
    const gfx::Transform& current_screen_transform,
    double time_delta) {
  gfx::Rect content_rect = ContentRect();
  if (content_rect.IsEmpty())
    return;

  gfx::Rect view_rect(gfx::Point(), device_viewport);
  int right = tiling_data_.TileXIndexFromSrcCoord(content_rect.width() - 1);
  int bottom = tiling_data_.TileYIndexFromSrcCoord(content_rect.height() - 1);
  for (TileMap::const_iterator it = tiles_.begin(); it != tiles_.end(); ++it) {
    TileMapKey key = it->first;
    TilePriority priority;
    if (key.first > right || key.second > bottom) {
      priority.distance_to_visible_in_pixels = std::numeric_limits<int>::max();
      priority.time_to_visible_in_seconds =
          TilePriority::kMaxTimeToVisibleInSeconds;
      // TODO(qinmin): pass the correct tree to this function.
      it->second->set_priority(ACTIVE_TREE, priority);
      continue;
    }

    gfx::Rect tile_bound = tiling_data_.TileBounds(key.first, key.second);
    gfx::RectF layer_content_rect = gfx::ScaleRect(
        tile_bound,
        layer_content_scale_x / contents_scale_,
        layer_content_scale_y / contents_scale_);
    gfx::RectF screen_rect = MathUtil::mapClippedRect(
        current_screen_transform, layer_content_rect);
    gfx::RectF previous_rect = MathUtil::mapClippedRect(
        last_screen_transform, layer_content_rect);

    priority.resolution = HIGH_RESOLUTION;
    priority.time_to_visible_in_seconds =
        TilePriority::TimeForBoundsToIntersect(
            previous_rect, screen_rect, time_delta, view_rect);

    priority.distance_to_visible_in_pixels =
        TilePriority::manhattanDistance(screen_rect, view_rect);
    // TODO(qinmin): pass the correct tree to this function.
    it->second->set_priority(ACTIVE_TREE, priority);
  }
}

}  // namespace cc