// Copyright 2014 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/output/overlay_candidate.h"

#include <algorithm>
#include <limits>
#include "base/logging.h"
#include "cc/base/math_util.h"
#include "cc/quads/solid_color_draw_quad.h"
#include "cc/quads/stream_video_draw_quad.h"
#include "cc/quads/texture_draw_quad.h"
#include "cc/quads/tile_draw_quad.h"
#include "cc/resources/resource_provider.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/vector3d_f.h"

namespace cc {

namespace {
// Tolerance for considering axis vector elements to be zero.
const SkMScalar kEpsilon = std::numeric_limits<float>::epsilon();

enum Axis { NONE, AXIS_POS_X, AXIS_NEG_X, AXIS_POS_Y, AXIS_NEG_Y };

Axis VectorToAxis(const gfx::Vector3dF& vec) {
  if (std::abs(vec.z()) > kEpsilon)
    return NONE;
  const bool x_zero = (std::abs(vec.x()) <= kEpsilon);
  const bool y_zero = (std::abs(vec.y()) <= kEpsilon);
  if (x_zero && !y_zero)
    return (vec.y() > 0) ? AXIS_POS_Y : AXIS_NEG_Y;
  else if (y_zero && !x_zero)
    return (vec.x() > 0) ? AXIS_POS_X : AXIS_NEG_X;
  else
    return NONE;
}

gfx::OverlayTransform GetOverlayTransform(const gfx::Transform& quad_transform,
                                          bool y_flipped) {
  if (!quad_transform.Preserves2dAxisAlignment()) {
    return gfx::OVERLAY_TRANSFORM_INVALID;
  }

  gfx::Vector3dF x_axis = MathUtil::GetXAxis(quad_transform);
  gfx::Vector3dF y_axis = MathUtil::GetYAxis(quad_transform);
  if (y_flipped) {
    y_axis.Scale(-1);
  }

  Axis x_to = VectorToAxis(x_axis);
  Axis y_to = VectorToAxis(y_axis);

  if (x_to == AXIS_POS_X && y_to == AXIS_POS_Y)
    return gfx::OVERLAY_TRANSFORM_NONE;
  else if (x_to == AXIS_NEG_X && y_to == AXIS_POS_Y)
    return gfx::OVERLAY_TRANSFORM_FLIP_HORIZONTAL;
  else if (x_to == AXIS_POS_X && y_to == AXIS_NEG_Y)
    return gfx::OVERLAY_TRANSFORM_FLIP_VERTICAL;
  else if (x_to == AXIS_NEG_Y && y_to == AXIS_POS_X)
    return gfx::OVERLAY_TRANSFORM_ROTATE_270;
  else if (x_to == AXIS_NEG_X && y_to == AXIS_NEG_Y)
    return gfx::OVERLAY_TRANSFORM_ROTATE_180;
  else if (x_to == AXIS_POS_Y && y_to == AXIS_NEG_X)
    return gfx::OVERLAY_TRANSFORM_ROTATE_90;
  else
    return gfx::OVERLAY_TRANSFORM_INVALID;
}

// Apply transform |delta| to |in| and return the resulting transform,
// or OVERLAY_TRANSFORM_INVALID.
gfx::OverlayTransform ComposeTransforms(gfx::OverlayTransform delta,
                                        gfx::OverlayTransform in) {
  // There are 8 different possible transforms. We can characterize these
  // by looking at where the origin moves and the direction the horizontal goes.
  // (TL=top-left, BR=bottom-right, H=horizontal, V=vertical).
  // NONE: TL, H
  // FLIP_VERTICAL: BL, H
  // FLIP_HORIZONTAL: TR, H
  // ROTATE_90: TR, V
  // ROTATE_180: BR, H
  // ROTATE_270: BL, V
  // Missing transforms: TL, V & BR, V
  // Basic combinations:
  // Flip X & Y -> Rotate 180 (TL,H -> TR,H -> BR,H or TL,H -> BL,H -> BR,H)
  // Flip X or Y + Rotate 180 -> other flip (eg, TL,H -> TR,H -> BL,H)
  // Rotate + Rotate simply adds values.
  // Rotate 90/270 + flip is invalid because we can only have verticals with
  // the origin in TR or BL.
  if (delta == gfx::OVERLAY_TRANSFORM_NONE)
    return in;
  switch (in) {
    case gfx::OVERLAY_TRANSFORM_NONE:
      return delta;
    case gfx::OVERLAY_TRANSFORM_FLIP_VERTICAL:
      switch (delta) {
        case gfx::OVERLAY_TRANSFORM_FLIP_VERTICAL:
          return gfx::OVERLAY_TRANSFORM_NONE;
        case gfx::OVERLAY_TRANSFORM_FLIP_HORIZONTAL:
          return gfx::OVERLAY_TRANSFORM_ROTATE_180;
        case gfx::OVERLAY_TRANSFORM_ROTATE_180:
          return gfx::OVERLAY_TRANSFORM_FLIP_HORIZONTAL;
        default:
          return gfx::OVERLAY_TRANSFORM_INVALID;
      }
      break;
    case gfx::OVERLAY_TRANSFORM_FLIP_HORIZONTAL:
      switch (delta) {
        case gfx::OVERLAY_TRANSFORM_FLIP_HORIZONTAL:
          return gfx::OVERLAY_TRANSFORM_NONE;
        case gfx::OVERLAY_TRANSFORM_FLIP_VERTICAL:
          return gfx::OVERLAY_TRANSFORM_ROTATE_180;
        case gfx::OVERLAY_TRANSFORM_ROTATE_90:
        case gfx::OVERLAY_TRANSFORM_ROTATE_180:
          return gfx::OVERLAY_TRANSFORM_FLIP_VERTICAL;
        case gfx::OVERLAY_TRANSFORM_ROTATE_270:
        default:
          return gfx::OVERLAY_TRANSFORM_INVALID;
      }
      break;
    case gfx::OVERLAY_TRANSFORM_ROTATE_90:
      switch (delta) {
        case gfx::OVERLAY_TRANSFORM_ROTATE_90:
          return gfx::OVERLAY_TRANSFORM_ROTATE_180;
        case gfx::OVERLAY_TRANSFORM_ROTATE_180:
          return gfx::OVERLAY_TRANSFORM_ROTATE_270;
        case gfx::OVERLAY_TRANSFORM_ROTATE_270:
          return gfx::OVERLAY_TRANSFORM_NONE;
        default:
          return gfx::OVERLAY_TRANSFORM_INVALID;
      }
      break;
    case gfx::OVERLAY_TRANSFORM_ROTATE_180:
      switch (delta) {
        case gfx::OVERLAY_TRANSFORM_FLIP_HORIZONTAL:
          return gfx::OVERLAY_TRANSFORM_FLIP_VERTICAL;
        case gfx::OVERLAY_TRANSFORM_FLIP_VERTICAL:
          return gfx::OVERLAY_TRANSFORM_FLIP_HORIZONTAL;
        case gfx::OVERLAY_TRANSFORM_ROTATE_90:
          return gfx::OVERLAY_TRANSFORM_ROTATE_270;
        case gfx::OVERLAY_TRANSFORM_ROTATE_180:
          return gfx::OVERLAY_TRANSFORM_NONE;
        case gfx::OVERLAY_TRANSFORM_ROTATE_270:
          return gfx::OVERLAY_TRANSFORM_ROTATE_90;
        default:
          return gfx::OVERLAY_TRANSFORM_INVALID;
      }
      break;
    case gfx::OVERLAY_TRANSFORM_ROTATE_270:
      switch (delta) {
        case gfx::OVERLAY_TRANSFORM_ROTATE_90:
          return gfx::OVERLAY_TRANSFORM_NONE;
        case gfx::OVERLAY_TRANSFORM_ROTATE_180:
          return gfx::OVERLAY_TRANSFORM_ROTATE_90;
        case gfx::OVERLAY_TRANSFORM_ROTATE_270:
          return gfx::OVERLAY_TRANSFORM_ROTATE_180;
        default:
          return gfx::OVERLAY_TRANSFORM_INVALID;
      }
      break;
    default:
      return gfx::OVERLAY_TRANSFORM_INVALID;
  }
}

}  // namespace

OverlayCandidate::OverlayCandidate()
    : transform(gfx::OVERLAY_TRANSFORM_NONE),
      format(RGBA_8888),
      uv_rect(0.f, 0.f, 1.f, 1.f),
      is_clipped(false),
      use_output_surface_for_resource(false),
      resource_id(0),
      plane_z_order(0),
      is_unoccluded(false),
      overlay_handled(false) {}

OverlayCandidate::OverlayCandidate(const OverlayCandidate& other) = default;

OverlayCandidate::~OverlayCandidate() {}

// static
bool OverlayCandidate::FromDrawQuad(ResourceProvider* resource_provider,
                                    const DrawQuad* quad,
                                    OverlayCandidate* candidate) {
  if (quad->ShouldDrawWithBlending() ||
      quad->shared_quad_state->opacity != 1.f ||
      quad->shared_quad_state->blend_mode != SkXfermode::kSrcOver_Mode)
    return false;

  auto& transform = quad->shared_quad_state->quad_to_target_transform;
  candidate->display_rect = gfx::RectF(quad->rect);
  transform.TransformRect(&candidate->display_rect);
  candidate->quad_rect_in_target_space =
      MathUtil::MapEnclosingClippedRect(transform, quad->rect);

  candidate->format = RGBA_8888;
  candidate->clip_rect = quad->shared_quad_state->clip_rect;
  candidate->is_clipped = quad->shared_quad_state->is_clipped;

  switch (quad->material) {
    case DrawQuad::TEXTURE_CONTENT:
      return FromTextureQuad(resource_provider,
                             TextureDrawQuad::MaterialCast(quad), candidate);
    case DrawQuad::STREAM_VIDEO_CONTENT:
      return FromStreamVideoQuad(resource_provider,
                                 StreamVideoDrawQuad::MaterialCast(quad),
                                 candidate);
    default:
      break;
  }

  return false;
}

// static
bool OverlayCandidate::IsInvisibleQuad(const DrawQuad* quad) {
  float opacity = quad->shared_quad_state->opacity;
  if (opacity < std::numeric_limits<float>::epsilon())
    return true;
  if (quad->material == DrawQuad::SOLID_COLOR) {
    SkColor color = SolidColorDrawQuad::MaterialCast(quad)->color;
    float alpha = (SkColorGetA(color) * (1.0f / 255.0f)) * opacity;
    return quad->ShouldDrawWithBlending() &&
           alpha < std::numeric_limits<float>::epsilon();
  }
  return false;
}

// static
bool OverlayCandidate::IsOccluded(const OverlayCandidate& candidate,
                                  QuadList::ConstIterator quad_list_begin,
                                  QuadList::ConstIterator quad_list_end) {
  // Check that no visible quad overlaps the candidate.
  for (auto overlap_iter = quad_list_begin; overlap_iter != quad_list_end;
       ++overlap_iter) {
    gfx::RectF overlap_rect = MathUtil::MapClippedRect(
        overlap_iter->shared_quad_state->quad_to_target_transform,
        gfx::RectF(overlap_iter->rect));
    if (candidate.display_rect.Intersects(overlap_rect) &&
        !OverlayCandidate::IsInvisibleQuad(*overlap_iter))
      return true;
  }
  return false;
}

// static
bool OverlayCandidate::FromTextureQuad(ResourceProvider* resource_provider,
                                       const TextureDrawQuad* quad,
                                       OverlayCandidate* candidate) {
  if (!resource_provider->IsOverlayCandidate(quad->resource_id()))
    return false;
  gfx::OverlayTransform overlay_transform = GetOverlayTransform(
      quad->shared_quad_state->quad_to_target_transform, quad->y_flipped);
  if (quad->background_color != SK_ColorTRANSPARENT ||
      overlay_transform == gfx::OVERLAY_TRANSFORM_INVALID)
    return false;
  candidate->resource_id = quad->resource_id();
  candidate->resource_size_in_pixels = quad->resource_size_in_pixels();
  candidate->transform = overlay_transform;
  candidate->uv_rect = BoundingRect(quad->uv_top_left, quad->uv_bottom_right);
  return true;
}

// static
bool OverlayCandidate::FromStreamVideoQuad(ResourceProvider* resource_provider,
                                           const StreamVideoDrawQuad* quad,
                                           OverlayCandidate* candidate) {
  if (!resource_provider->IsOverlayCandidate(quad->resource_id()))
    return false;
  gfx::OverlayTransform overlay_transform = GetOverlayTransform(
      quad->shared_quad_state->quad_to_target_transform, false);
  if (overlay_transform == gfx::OVERLAY_TRANSFORM_INVALID)
    return false;
  if (!quad->matrix.IsScaleOrTranslation()) {
    // We cannot handle anything other than scaling & translation for texture
    // coordinates yet.
    return false;
  }
  candidate->resource_id = quad->resource_id();
  candidate->resource_size_in_pixels = quad->resource_size_in_pixels();
  candidate->transform = overlay_transform;

  gfx::Point3F uv0 = gfx::Point3F(0, 0, 0);
  gfx::Point3F uv1 = gfx::Point3F(1, 1, 0);
  quad->matrix.TransformPoint(&uv0);
  quad->matrix.TransformPoint(&uv1);
  gfx::Vector3dF delta = uv1 - uv0;
  if (delta.x() < 0) {
    candidate->transform = ComposeTransforms(
        gfx::OVERLAY_TRANSFORM_FLIP_HORIZONTAL, candidate->transform);
    float x0 = uv0.x();
    uv0.set_x(uv1.x());
    uv1.set_x(x0);
    delta.set_x(-delta.x());
  }

  if (delta.y() < 0) {
    // In this situation, uv0y < uv1y. Since we overlay inverted, a request
    // to invert the source texture means we can just output the texture
    // normally and it will be correct.
    candidate->uv_rect = gfx::RectF(uv0.x(), uv1.y(), delta.x(), -delta.y());
  } else {
    candidate->transform = ComposeTransforms(
        gfx::OVERLAY_TRANSFORM_FLIP_VERTICAL, candidate->transform);
    candidate->uv_rect = gfx::RectF(uv0.x(), uv0.y(), delta.x(), delta.y());
  }
  return true;
}

}  // namespace cc
