// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/gfx/overlay_transform_utils.h"

#include "base/notreached.h"
#include "ui/gfx/geometry/rect_conversions.h"

namespace gfx {

Transform OverlayTransformToTransform(OverlayTransform overlay_transform,
                                      const SizeF& viewport_bounds) {
  switch (overlay_transform) {
    case OVERLAY_TRANSFORM_INVALID:
      NOTREACHED();
    case OVERLAY_TRANSFORM_NONE:
      return Transform();
    case OVERLAY_TRANSFORM_FLIP_HORIZONTAL:
      return Transform::Affine(-1, 0, 0, 1, viewport_bounds.width(), 0);
    case OVERLAY_TRANSFORM_FLIP_VERTICAL:
      return Transform::Affine(1, 0, 0, -1, 0, viewport_bounds.height());
    case OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90:
      return Transform::Affine(0, 1, -1, 0, viewport_bounds.height(), 0);
    case OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_180:
      return Transform::Affine(-1, 0, 0, -1, viewport_bounds.width(),
                               viewport_bounds.height());
    case OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270:
      return Transform::Affine(0, -1, 1, 0, 0, viewport_bounds.width());
    case OVERLAY_TRANSFORM_FLIP_VERTICAL_CLOCKWISE_90:
      return Transform::Affine(0, 1, 1, 0, 0, 0);
    case OVERLAY_TRANSFORM_FLIP_VERTICAL_CLOCKWISE_270:
      return Transform::Affine(0, -1, -1, 0, viewport_bounds.height(),
                               viewport_bounds.width());
  }

  NOTREACHED();
}

OverlayTransform InvertOverlayTransform(OverlayTransform transform) {
  switch (transform) {
    case OVERLAY_TRANSFORM_INVALID:
      NOTREACHED();
    case OVERLAY_TRANSFORM_NONE:
      return OVERLAY_TRANSFORM_NONE;
    case OVERLAY_TRANSFORM_FLIP_HORIZONTAL:
      return OVERLAY_TRANSFORM_FLIP_HORIZONTAL;
    case OVERLAY_TRANSFORM_FLIP_VERTICAL:
      return OVERLAY_TRANSFORM_FLIP_VERTICAL;
    case OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90:
      return OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270;
    case OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_180:
      return OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_180;
    case OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270:
      return OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90;
    case OVERLAY_TRANSFORM_FLIP_VERTICAL_CLOCKWISE_90:
      return OVERLAY_TRANSFORM_FLIP_VERTICAL_CLOCKWISE_90;
    case OVERLAY_TRANSFORM_FLIP_VERTICAL_CLOCKWISE_270:
      return OVERLAY_TRANSFORM_FLIP_VERTICAL_CLOCKWISE_270;
  }
  NOTREACHED();
}

OverlayTransform OverlayTransformsConcat(OverlayTransform t1,
                                         OverlayTransform t2) {
  if (t1 == OVERLAY_TRANSFORM_INVALID || t2 == OVERLAY_TRANSFORM_INVALID) {
    return OVERLAY_TRANSFORM_INVALID;
  }

  enum VFlip {
    // Written so they behave similar to bools.
    kNo = 0,
    kYes,
  };
  enum Rotation {
    // Enums are written so that it's valid to do modular arithematic.
    // Eg k90 + k270 mod 4 is k0.
    k0 = 0,
    k90,
    k180,
    k270,
  };

  // Step 1: Decompose arguments into vertical flip and clock-wise rotation.
  struct DecomposedOverlayTransform {
    VFlip vflip;
    Rotation rotation;
  };
  auto decompose = [](OverlayTransform t) -> DecomposedOverlayTransform {
    switch (t) {
      case OVERLAY_TRANSFORM_NONE:
        return {VFlip::kNo, Rotation::k0};
      case OVERLAY_TRANSFORM_FLIP_HORIZONTAL:
        return {VFlip::kYes, Rotation::k180};
      case OVERLAY_TRANSFORM_FLIP_VERTICAL:
        return {VFlip::kYes, Rotation::k0};
      case OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90:
        return {VFlip::kNo, Rotation::k90};
      case OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_180:
        return {VFlip::kNo, Rotation::k180};
      case OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270:
        return {VFlip::kNo, Rotation::k270};
      case OVERLAY_TRANSFORM_FLIP_VERTICAL_CLOCKWISE_90:
        return {VFlip::kYes, Rotation::k90};
      case OVERLAY_TRANSFORM_FLIP_VERTICAL_CLOCKWISE_270:
        return {VFlip::kYes, Rotation::k270};
      case OVERLAY_TRANSFORM_INVALID:
        break;
    }
    NOTREACHED();
  };

  DecomposedOverlayTransform decomposed1 = decompose(t1);
  DecomposedOverlayTransform decomposed2 = decompose(t2);

  // Step 2: Compute decomposed result.
  // Result flip is effectively an XOR of two arguments.
  VFlip result_vflip =
      static_cast<VFlip>(decomposed1.vflip != decomposed2.vflip);
  // Add rotation, except that rotation of `t1` needs to be subtracted if `t2`
  // contains a flip.
  Rotation result_rotation;
  if (decomposed2.vflip == VFlip::kYes) {
    result_rotation = static_cast<Rotation>(
        (decomposed2.rotation + 4 - decomposed1.rotation) % 4);
  } else {
    result_rotation = static_cast<Rotation>(
        (decomposed2.rotation + decomposed1.rotation) % 4);
  }

  // Step 3: Reconstruct result.
  switch (result_rotation) {
    case Rotation::k0:
      return result_vflip == VFlip::kYes ? OVERLAY_TRANSFORM_FLIP_VERTICAL
                                         : OVERLAY_TRANSFORM_NONE;
    case Rotation::k90:
      return result_vflip == VFlip::kYes
                 ? OVERLAY_TRANSFORM_FLIP_VERTICAL_CLOCKWISE_90
                 : OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90;
    case Rotation::k180:
      return result_vflip == VFlip::kYes
                 ? OVERLAY_TRANSFORM_FLIP_HORIZONTAL
                 : OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_180;
    case Rotation::k270:
      return result_vflip == VFlip::kYes
                 ? OVERLAY_TRANSFORM_FLIP_VERTICAL_CLOCKWISE_270
                 : OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270;
  }

  NOTREACHED();
}

}  // namespace gfx
