// Copyright (c) 2011 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/gfx/compositor/layer.h"

#include <algorithm>

#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "ui/base/animation/animation.h"
#include "ui/gfx/compositor/layer_animator.h"
#include "ui/gfx/canvas_skia.h"
#include "ui/gfx/interpolated_transform.h"
#include "ui/gfx/point3.h"

namespace {

const float EPSILON = 1e-3f;

bool IsApproximateMultilpleOf(float value, float base) {
  float remainder = fmod(fabs(value), base);
  return remainder < EPSILON || base - remainder < EPSILON;
}

}  // namespace

namespace ui {

Layer::Layer(Compositor* compositor)
    : type_(LAYER_HAS_TEXTURE),
      compositor_(compositor),
      parent_(NULL),
      visible_(true),
      fills_bounds_opaquely_(true),
      layer_updated_externally_(false),
      opacity_(1.0f),
      delegate_(NULL) {
}

Layer::Layer(Compositor* compositor, LayerType type)
    : type_(type),
      compositor_(compositor),
      parent_(NULL),
      visible_(true),
      fills_bounds_opaquely_(true),
      layer_updated_externally_(false),
      opacity_(1.0f),
      delegate_(NULL) {
}

Layer::~Layer() {
  if (parent_)
    parent_->Remove(this);
  for (size_t i = 0; i < children_.size(); ++i)
    children_[i]->parent_ = NULL;
}

Compositor* Layer::GetCompositor() {
  return compositor_ ? compositor_ : parent_ ? parent_->GetCompositor() : NULL;
}

void Layer::SetCompositor(Compositor* compositor) {
  // This function must only be called once, with a valid compositor, and only
  // for the compositor's root layer.
  DCHECK(!compositor_);
  DCHECK(compositor);
  DCHECK_EQ(compositor->root_layer(), this);
  compositor_ = compositor;
}

void Layer::Add(Layer* child) {
  if (child->parent_)
    child->parent_->Remove(child);
  child->parent_ = this;
  children_.push_back(child);

  RecomputeHole();
}

void Layer::Remove(Layer* child) {
  std::vector<Layer*>::iterator i =
      std::find(children_.begin(), children_.end(), child);
  DCHECK(i != children_.end());
  children_.erase(i);
  child->parent_ = NULL;

  RecomputeHole();

  child->DropTextures();
}

void Layer::MoveToFront(Layer* child) {
  std::vector<Layer*>::iterator i =
      std::find(children_.begin(), children_.end(), child);
  DCHECK(i != children_.end());
  children_.erase(i);
  children_.push_back(child);
}

bool Layer::Contains(const Layer* other) const {
  for (const Layer* parent = other; parent; parent = parent->parent()) {
    if (parent == this)
      return true;
  }
  return false;
}

void Layer::SetAnimation(Animation* animation) {
  if (animation) {
    if (!animator_.get())
      animator_.reset(new LayerAnimator(this));
    animation->Start();
    animator_->SetAnimation(animation);
  } else {
    animator_.reset();
  }
}

void Layer::SetTransform(const ui::Transform& transform) {
  StopAnimatingIfNecessary(LayerAnimator::TRANSFORM);
  if (animator_.get() && animator_->IsRunning()) {
    animator_->AnimateTransform(transform);
    return;
  }
  SetTransformImmediately(transform);
}

void Layer::SetBounds(const gfx::Rect& bounds) {
  StopAnimatingIfNecessary(LayerAnimator::LOCATION);
  if (animator_.get() && animator_->IsRunning() &&
      bounds.size() == bounds_.size()) {
    animator_->AnimateToPoint(bounds.origin());
    return;
  }
  SetBoundsImmediately(bounds);
}

void Layer::SetOpacity(float opacity) {
  StopAnimatingIfNecessary(LayerAnimator::OPACITY);
  if (animator_.get() && animator_->IsRunning()) {
    animator_->AnimateOpacity(opacity);
    return;
  }
  SetOpacityImmediately(opacity);
}

void Layer::SetVisible(bool visible) {
  if (visible_ == visible)
    return;

  bool was_drawn = IsDrawn();
  visible_ = visible;
  bool is_drawn = IsDrawn();
  if (was_drawn == is_drawn)
    return;

  if (!is_drawn)
    DropTextures();
  if (parent_)
    parent_->RecomputeHole();
}

bool Layer::IsDrawn() const {
  const Layer* layer = this;
  while (layer && layer->visible_)
    layer = layer->parent_;
  return layer == NULL;
}

bool Layer::ShouldDraw() {
  return type_ == LAYER_HAS_TEXTURE && GetCombinedOpacity() > 0.0f &&
      !hole_rect_.Contains(gfx::Rect(gfx::Point(0, 0), bounds_.size()));
}

// static
void Layer::ConvertPointToLayer(const Layer* source,
                                const Layer* target,
                                gfx::Point* point) {
  const Layer* inner = NULL;
  const Layer* outer = NULL;
  if (source->Contains(target)) {
    inner = target;
    outer = source;
    inner->ConvertPointFromAncestor(outer, point);
  } else if (target->Contains(source)) {
    inner = source;
    outer = target;
    inner->ConvertPointForAncestor(outer, point);
  } else {
    NOTREACHED(); // |source| and |target| are in unrelated hierarchies.
  }
}

void Layer::SetFillsBoundsOpaquely(bool fills_bounds_opaquely) {
  if (fills_bounds_opaquely_ == fills_bounds_opaquely)
    return;

  fills_bounds_opaquely_ = fills_bounds_opaquely;

  if (parent())
    parent()->RecomputeHole();
}

void Layer::SetExternalTexture(ui::Texture* texture) {
  DCHECK(texture);
  layer_updated_externally_ = true;
  texture_ = texture;
}

void Layer::SetCanvas(const SkCanvas& canvas, const gfx::Point& origin) {
  DCHECK_EQ(type_, LAYER_HAS_TEXTURE);

  if (!texture_.get())
    texture_ = GetCompositor()->CreateTexture();

  texture_->SetCanvas(canvas, origin, bounds_.size());
  invalid_rect_ = gfx::Rect();
}

void Layer::SchedulePaint(const gfx::Rect& invalid_rect) {
  invalid_rect_ = invalid_rect_.Union(invalid_rect);
  ScheduleDraw();
}

void Layer::ScheduleDraw() {
  if (GetCompositor())
    GetCompositor()->ScheduleDraw();
}

void Layer::Draw() {
  DCHECK(GetCompositor());
  if (!ShouldDraw())
    return;

  UpdateLayerCanvas();

  // Layer drew nothing, no texture was created.
  if (!texture_.get())
    return;

  ui::TextureDrawParams texture_draw_params;
  for (Layer* layer = this; layer; layer = layer->parent_) {
    texture_draw_params.transform.ConcatTransform(layer->transform_);
    texture_draw_params.transform.ConcatTranslate(
        static_cast<float>(layer->bounds_.x()),
        static_cast<float>(layer->bounds_.y()));
  }

  const float combined_opacity = GetCombinedOpacity();

  // Only blend for transparent child layers (and when we're forcing
  // transparency). The root layer will clobber the cleared bg.
  const bool is_root = parent_ == NULL;
  const bool forcing_transparency = combined_opacity < 1.0f;
  const bool is_opaque = fills_bounds_opaquely_ || !has_valid_alpha_channel();
  texture_draw_params.blend = !is_root && (forcing_transparency || !is_opaque);

  texture_draw_params.compositor_size = GetCompositor()->size();
  texture_draw_params.opacity = combined_opacity;
  texture_draw_params.has_valid_alpha_channel = has_valid_alpha_channel();

  std::vector<gfx::Rect> regions_to_draw;
  PunchHole(gfx::Rect(gfx::Point(), bounds().size()), hole_rect_,
            &regions_to_draw);

  for (size_t i = 0; i < regions_to_draw.size(); ++i) {
    if (!regions_to_draw[i].IsEmpty())
      texture_->Draw(texture_draw_params, regions_to_draw[i]);
  }
}

void Layer::DrawTree() {
  if (!visible_)
    return;

  Draw();
  for (size_t i = 0; i < children_.size(); ++i)
    children_.at(i)->DrawTree();
}

float Layer::GetCombinedOpacity() const {
  float opacity = opacity_;
  Layer* current = this->parent_;
  while (current) {
    opacity *= current->opacity_;
    current = current->parent_;
  }
  return opacity;
}

void Layer::UpdateLayerCanvas() {
  // If we have no delegate, that means that whoever constructed the Layer is
  // setting its canvas directly with SetCanvas().
  if (!delegate_ || layer_updated_externally_)
    return;
  gfx::Rect local_bounds = gfx::Rect(gfx::Point(), bounds_.size());
  gfx::Rect draw_rect = texture_.get() ? invalid_rect_.Intersect(local_bounds) :
      local_bounds;
  if (draw_rect.IsEmpty()) {
    invalid_rect_ = gfx::Rect();
    return;
  }
  scoped_ptr<gfx::Canvas> canvas(gfx::Canvas::CreateCanvas(
      draw_rect.width(), draw_rect.height(), false));
  canvas->TranslateInt(-draw_rect.x(), -draw_rect.y());
  delegate_->OnPaintLayer(canvas.get());
  SetCanvas(*canvas->GetSkCanvas(), draw_rect.origin());
}

void Layer::RecomputeHole() {
  if (type_ == LAYER_HAS_NO_TEXTURE)
    return;

  // Reset to default.
  hole_rect_ = gfx::Rect();

  // Find the largest hole
  for (size_t i = 0; i < children_.size(); ++i) {
    // Ignore non-opaque and hidden children.
    if (!children_[i]->IsCompletelyOpaque() || !children_[i]->visible_)
      continue;

    // Ignore children that aren't rotated by multiples of 90 degrees.
    float degrees;
    if (!InterpolatedTransform::FactorTRS(children_[i]->transform(),
                                          NULL, &degrees, NULL) ||
        !IsApproximateMultilpleOf(degrees, 90.0f))
      continue;

    // The reason why we don't just take the bounds and apply the transform is
    // that the bounds encodes a position, too, so the effective transformation
    // matrix is actually different that the one reported. As well, the bounds
    // will not necessarily be at the origin.
    gfx::Rect candidate_hole(children_[i]->bounds_.size());
    ui::Transform transform = children_[i]->transform();
    transform.ConcatTranslate(static_cast<float>(children_[i]->bounds_.x()),
                              static_cast<float>(children_[i]->bounds_.y()));
    transform.TransformRect(&candidate_hole);

    // This layer might not contain the child (e.g., a portion of the child may
    // be offscreen). Only the portion of the child that overlaps this layer is
    // of any importance, so take the intersection.
    candidate_hole = bounds().Intersect(candidate_hole);

    // Ensure we have the largest hole.
    if (candidate_hole.size().GetArea() > hole_rect_.size().GetArea())
      hole_rect_ = candidate_hole;
  }

  // Free up texture memory if the hole fills bounds of layer.
  if (!ShouldDraw() && !layer_updated_externally_)
    texture_ = NULL;
}

bool Layer::IsCompletelyOpaque() const {
  return fills_bounds_opaquely() && GetCombinedOpacity() == 1.0f;
}

// static
void Layer::PunchHole(const gfx::Rect& rect,
                      const gfx::Rect& region_to_punch_out,
                      std::vector<gfx::Rect>* sides) {
  gfx::Rect trimmed_rect = rect.Intersect(region_to_punch_out);

  if (trimmed_rect.IsEmpty()) {
    sides->push_back(rect);
    return;
  }

  // Top (above the hole).
  sides->push_back(gfx::Rect(rect.x(),
                             rect.y(),
                             rect.width(),
                             trimmed_rect.y() - rect.y()));

  // Left (of the hole).
  sides->push_back(gfx::Rect(rect.x(),
                             trimmed_rect.y(),
                             trimmed_rect.x() - rect.x(),
                             trimmed_rect.height()));

  // Right (of the hole).
  sides->push_back(gfx::Rect(trimmed_rect.right(),
                             trimmed_rect.y(),
                             rect.right() - trimmed_rect.right(),
                             trimmed_rect.height()));

  // Bottom (below the hole).
  sides->push_back(gfx::Rect(rect.x(),
                             trimmed_rect.bottom(),
                             rect.width(),
                             rect.bottom() - trimmed_rect.bottom()));
}

void Layer::DropTextures() {
  if (!layer_updated_externally_)
    texture_ = NULL;
  for (size_t i = 0; i < children_.size(); ++i)
    children_[i]->DropTextures();
}

bool Layer::ConvertPointForAncestor(const Layer* ancestor,
                                    gfx::Point* point) const {
  ui::Transform transform;
  bool result = GetTransformRelativeTo(ancestor, &transform);
  gfx::Point3f p(*point);
  transform.TransformPoint(p);
  *point = p.AsPoint();
  return result;
}

bool Layer::ConvertPointFromAncestor(const Layer* ancestor,
                                     gfx::Point* point) const {
  ui::Transform transform;
  bool result = GetTransformRelativeTo(ancestor, &transform);
  gfx::Point3f p(*point);
  transform.TransformPointReverse(p);
  *point = p.AsPoint();
  return result;
}

bool Layer::GetTransformRelativeTo(const Layer* ancestor,
                                   ui::Transform* transform) const {
  const Layer* p = this;
  for (; p && p != ancestor; p = p->parent()) {
    if (p->transform().HasChange())
      transform->ConcatTransform(p->transform());
    transform->ConcatTranslate(static_cast<float>(p->bounds().x()),
                               static_cast<float>(p->bounds().y()));
  }
  return p == ancestor;
}

void Layer::StopAnimatingIfNecessary(
    LayerAnimator::AnimationProperty property) {
  if (!animator_.get() || !animator_->IsRunning() ||
      !animator_->got_initial_tick()) {
    return;
  }

  if (property != LayerAnimator::LOCATION &&
      animator_->IsAnimating(LayerAnimator::LOCATION)) {
    SetBoundsImmediately(
        gfx::Rect(animator_->GetTargetPoint(), bounds_.size()));
  }
  if (property != LayerAnimator::OPACITY &&
      animator_->IsAnimating(LayerAnimator::OPACITY)) {
    SetOpacityImmediately(animator_->GetTargetOpacity());
  }
  if (property != LayerAnimator::TRANSFORM &&
      animator_->IsAnimating(LayerAnimator::TRANSFORM)) {
    SetTransformImmediately(animator_->GetTargetTransform());
  }
  animator_.reset();
}

void Layer::SetBoundsImmediately(const gfx::Rect& bounds) {
  bounds_ = bounds;

  if (parent())
    parent()->RecomputeHole();
}

void Layer::SetTransformImmediately(const ui::Transform& transform) {
  transform_ = transform;

  if (parent())
    parent()->RecomputeHole();
}

void Layer::SetOpacityImmediately(float opacity) {
  bool was_opaque = GetCombinedOpacity() == 1.0f;
  opacity_ = opacity;
  bool is_opaque = GetCombinedOpacity() == 1.0f;

  // If our opacity has changed we need to recompute our hole, our parent's hole
  // and the holes of all our descendants.
  if (was_opaque != is_opaque) {
    if (parent_)
      parent_->RecomputeHole();
    std::queue<Layer*> to_process;
    to_process.push(this);
    while (!to_process.empty()) {
      Layer* current = to_process.front();
      to_process.pop();
      current->RecomputeHole();
      for (size_t i = 0; i < current->children_.size(); ++i)
        to_process.push(current->children_.at(i));
    }
  }
}

void Layer::SetBoundsFromAnimator(const gfx::Rect& bounds) {
  SetBoundsImmediately(bounds);
}

void Layer::SetTransformFromAnimator(const Transform& transform) {
  SetTransformImmediately(transform);
}

void Layer::SetOpacityFromAnimator(float opacity) {
  SetOpacityImmediately(opacity);
}

}  // namespace ui