// Copyright 2018 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/geometry/rrect_f.h"

#include <array>
#include <iomanip>
#include <iostream>
#include <sstream>

#include "base/check_op.h"
#include "base/containers/span.h"
#include "base/values.h"
#include "third_party/skia/include/core/SkMatrix.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"

namespace gfx {

// Sets all x radii to x_rad, and all y radii to y_rad. If one of x_rad or
// y_rad are zero, sets ALL radii to zero.
RRectF::RRectF(float x,
               float y,
               float width,
               float height,
               float x_rad,
               float y_rad)
    : skrrect_(SkRRect::MakeRectXY(SkRect::MakeXYWH(x, y, width, height),
                                   x_rad,
                                   y_rad)) {
  if (IsEmpty()) {
    // Make sure that empty rects are created fully empty, not with some
    // non-zero dimensions.
    skrrect_ = SkRRect::MakeEmpty();
  }
}

// Directly sets all four corners.
RRectF::RRectF(float x,
               float y,
               float width,
               float height,
               float upper_left_x,
               float upper_left_y,
               float upper_right_x,
               float upper_right_y,
               float lower_right_x,
               float lower_right_y,
               float lower_left_x,
               float lower_left_y) {
  SkVector radii[4] = {
      {upper_left_x, upper_left_y},
      {upper_right_x, upper_right_y},
      {lower_right_x, lower_right_y},
      {lower_left_x, lower_left_y},
  };
  skrrect_.setRectRadii(SkRect::MakeXYWH(x, y, width, height), radii);
  if (IsEmpty()) {
    // Make sure that empty rects are created fully empty, not with some
    // non-zero dimensions.
    skrrect_ = SkRRect::MakeEmpty();
  }
}

gfx::Vector2dF RRectF::GetSimpleRadii() const {
  DCHECK(GetType() <= Type::kOval);
  SkPoint result = skrrect_.getSimpleRadii();
  return gfx::Vector2dF(result.x(), result.y());
}

float RRectF::GetSimpleRadius() const {
  DCHECK(GetType() <= Type::kOval);
  SkPoint result = skrrect_.getSimpleRadii();
  DCHECK_EQ(result.x(), result.y());
  return result.x();
}

RRectF::Type RRectF::GetType() const {
  SkPoint rad;
  switch (skrrect_.getType()) {
    case SkRRect::kEmpty_Type:
      return Type::kEmpty;
    case SkRRect::kRect_Type:
      return Type::kRect;
    case SkRRect::kSimple_Type:
      rad = skrrect_.getSimpleRadii();
      if (rad.x() == rad.y()) {
        return Type::kSingle;
      }
      return Type::kSimple;
    case SkRRect::kOval_Type:
      rad = skrrect_.getSimpleRadii();
      if (rad.x() == rad.y()) {
        return Type::kSingle;
      }
      return Type::kOval;
    case SkRRect::kNinePatch_Type:
    case SkRRect::kComplex_Type:
    default:
      return Type::kComplex;
  }
}

gfx::Vector2dF RRectF::GetCornerRadii(Corner corner) const {
  SkPoint result = skrrect_.radii(SkRRect::Corner(corner));
  return gfx::Vector2dF(result.x(), result.y());
}

void RRectF::GetAllRadii(base::span<SkVector, 4> radii) const {
  // Unfortunately, the only way to get all radii is one at a time.
  radii[SkRRect::kUpperLeft_Corner] =
      skrrect_.radii(SkRRect::kUpperLeft_Corner);
  radii[SkRRect::kUpperRight_Corner] =
      skrrect_.radii(SkRRect::kUpperRight_Corner);
  radii[SkRRect::kLowerRight_Corner] =
      skrrect_.radii(SkRRect::kLowerRight_Corner);
  radii[SkRRect::kLowerLeft_Corner] =
      skrrect_.radii(SkRRect::kLowerLeft_Corner);
}

void RRectF::SetCornerRadii(Corner corner, float x_rad, float y_rad) {
  // Unfortunately, the only way to set this is to create a new SkRRect.
  std::array<SkVector, 4> radii;
  GetAllRadii(radii);
  radii[SkRRect::Corner(corner)] = SkPoint::Make(x_rad, y_rad);
  skrrect_.setRectRadii(skrrect_.rect(), radii.data());
}

gfx::RectF RRectF::CornerBoundingRect(Corner corner) const {
  auto radii = GetCornerRadii(corner);
  gfx::RectF bounding_box(radii.x(), radii.y());
  switch (corner) {
    case Corner::kUpperLeft:
      bounding_box.Offset(rect().x(), rect().y());
      break;
    case Corner::kUpperRight:
      bounding_box.Offset(rect().right() - radii.x(), rect().y());
      break;
    case Corner::kLowerRight:
      bounding_box.Offset(rect().right() - radii.x(),
                          rect().bottom() - radii.y());
      break;
    case Corner::kLowerLeft:
      bounding_box.Offset(rect().x(), rect().bottom() - radii.y());
      break;
  }
  return bounding_box;
}

void RRectF::Scale(float x_scale, float y_scale) {
  if (IsEmpty()) {
    // SkRRect doesn't support scaling of empty rects.
    return;
  }
  if (!x_scale || !y_scale) {
    // SkRRect doesn't support scaling TO an empty rect.
    skrrect_ = SkRRect::MakeEmpty();
    return;
  }
  SkMatrix scale = SkMatrix::Scale(x_scale, y_scale);
  SkRRect result;
  if (!skrrect_.transform(scale, &result)) {
    skrrect_ = SkRRect::MakeEmpty();
    return;
  }
  skrrect_ = result;
}

void RRectF::Offset(float horizontal, float vertical) {
  skrrect_.offset(horizontal, vertical);
}

const RRectF& RRectF::operator+=(const gfx::Vector2dF& offset) {
  Offset(offset.x(), offset.y());
  return *this;
}

const RRectF& RRectF::operator-=(const gfx::Vector2dF& offset) {
  Offset(-offset.x(), -offset.y());
  return *this;
}

std::string RRectF::ToString() const {
  std::stringstream ss;
  ss << std::fixed << std::setprecision(3);
  ss << rect().origin().x() << "," << rect().origin().y() << " "
     << rect().size().width() << "x" << rect().size().height();
  Type type = this->GetType();
  if (type <= Type::kRect) {
    ss << ", rectangular";
  } else if (type <= Type::kSingle) {
    ss << ", radius " << GetSimpleRadius();
  } else if (type <= Type::kSimple) {
    gfx::Vector2dF radii = GetSimpleRadii();
    ss << ", x_rad " << radii.x() << ", y_rad " << radii.y();
  } else {
    ss << ",";
    const Corner corners[] = {Corner::kUpperLeft, Corner::kUpperRight,
                              Corner::kLowerRight, Corner::kLowerLeft};
    for (const auto& c : corners) {
      auto this_corner = GetCornerRadii(c);
      ss << " [" << this_corner.x() << " " << this_corner.y() << "]";
    }
  }
  return ss.str();
}

namespace {
inline bool AboveTol(float val1, float val2, float tolerance) {
  return (std::abs(val1 - val2) > tolerance);
}
}  // namespace

bool RRectF::ApproximatelyEqual(const RRectF& rect, float tolerance) const {
  if (AboveTol(skrrect_.rect().x(), rect.skrrect_.rect().x(), tolerance) ||
      AboveTol(skrrect_.rect().y(), rect.skrrect_.rect().y(), tolerance) ||
      AboveTol(skrrect_.width(), rect.skrrect_.width(), tolerance) ||
      AboveTol(skrrect_.height(), rect.skrrect_.height(), tolerance))
    return false;
  for (int i = 0; i < 4; i++) {
    SkVector r1 = skrrect_.radii(SkRRect::Corner(i));
    SkVector r2 = rect.skrrect_.radii(SkRRect::Corner(i));
    if (std::abs(r1.x() - r2.x()) > tolerance ||
        std::abs(r1.y() - r2.y()) > tolerance) {
      return false;
    }
  }
  return true;
}

// static
RRectF RRectF::ToEnclosingRRectF(const RRectF& rrect_f) {
  return RRectF(gfx::RectF(ToEnclosingRect(rrect_f.rect())),
                rrect_f.GetRoundedCorners());
}

// static
RRectF RRectF::ToEnclosingRRectFIgnoringError(const RRectF& rrect_f,
                                              float error) {
  return RRectF(gfx::RectF(ToEnclosingRectIgnoringError(rrect_f.rect(), error)),
                rrect_f.GetRoundedCorners());
}

gfx::RoundedCornersF RRectF::GetRoundedCorners() const {
  auto upper_left = GetCornerRadii(Corner::kUpperLeft);
  auto upper_right = GetCornerRadii(Corner::kUpperRight);
  auto lower_right = GetCornerRadii(Corner::kLowerRight);
  auto lower_left = GetCornerRadii(Corner::kLowerLeft);
  return {upper_left.x(), upper_right.x(), lower_right.x(), lower_left.x()};
}

}  // namespace gfx
