// Copyright 2013 The Flutter 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 "flutter/impeller/geometry/round_rect.h"

namespace impeller {

RoundRect RoundRect::MakeRectRadii(const Rect& in_bounds,
                                   const RoundingRadii& in_radii) {
  if (!in_bounds.IsFinite()) {
    return {};
  }
  Rect bounds = in_bounds.GetPositive();
  // RoundingRadii::Scaled might return an empty radii if bounds or in_radii is
  // empty, which is expected. Pass along the bounds even if the radii is empty
  // as it would still have a valid location and/or 1-dimensional size which
  // might appear when stroked
  return RoundRect(bounds, in_radii.Scaled(bounds));
}

// Determine if p is inside the elliptical corner curve defined by the
// indicated corner point and the indicated radii.
// p - is the test point in absolute coordinates
// corner - is the location of the associated corner in absolute coordinates
// direction - is the sign of (corner - center), or the sign of coordinates
//             as they move in the direction of the corner from inside the
//             rect ((-1,-1) for the upper left corner for instance)
// radii - the non-negative X and Y size of the corner's radii.
static bool CornerContains(const Point& p,
                           const Point& corner,
                           const Point& direction,
                           const Size& radii) {
  FML_DCHECK(radii.width >= 0.0f && radii.height >= 0.0f);
  if (radii.IsEmpty()) {
    // This corner is not curved, therefore the containment is the same as
    // the previously checked bounds containment.
    return true;
  }

  // The positive X,Y distance between the corner and the point.
  Point corner_relative = (corner - p) * direction;

  // The distance from the "center" of the corner's elliptical curve.
  // If both numbers are positive then we need to do an elliptical distance
  // check to determine if it is inside the curve.
  // If either number is negative, then the point is outside this quadrant
  // and is governed by inclusion in the bounds and inclusion within other
  // corners of this round rect. In that case, we return true here to allow
  // further evaluation within other quadrants.
  Point quadrant_relative = radii - corner_relative;
  if (quadrant_relative.x <= 0.0f || quadrant_relative.y <= 0.0f) {
    // Not within the curved quadrant of this corner, therefore "inside"
    // relative to this one corner.
    return true;
  }

  // Dividing the quadrant_relative point by the radii gives a corresponding
  // location within a unit circle which can be more easily tested for
  // containment. We can use x^2 + y^2 and compare it against the radius
  // squared (1.0) to avoid the sqrt.
  Point quadrant_unit_circle_point = quadrant_relative / radii;
  return quadrant_unit_circle_point.GetLengthSquared() <= 1.0;
}

// The sign of the direction that points move as they approach the indicated
// corner from within the rectangle.
static constexpr Point kUpperLeftDirection(-1.0f, -1.0f);
static constexpr Point kUpperRightDirection(1.0f, -1.0f);
static constexpr Point kLowerLeftDirection(-1.0f, 1.0f);
static constexpr Point kLowerRightDirection(1.0f, 1.0f);

[[nodiscard]] bool RoundRect::Contains(const Point& p) const {
  if (!bounds_.Contains(p)) {
    return false;
  }
  if (!CornerContains(p, bounds_.GetLeftTop(), kUpperLeftDirection,
                      radii_.top_left) ||
      !CornerContains(p, bounds_.GetRightTop(), kUpperRightDirection,
                      radii_.top_right) ||
      !CornerContains(p, bounds_.GetLeftBottom(), kLowerLeftDirection,
                      radii_.bottom_left) ||
      !CornerContains(p, bounds_.GetRightBottom(), kLowerRightDirection,
                      radii_.bottom_right)) {
    return false;
  }
  return true;
}

void RoundRect::Dispatch(PathReceiver& receiver) const {
  Scalar left = bounds_.GetLeft();
  Scalar top = bounds_.GetTop();
  Scalar right = bounds_.GetRight();
  Scalar bottom = bounds_.GetBottom();

  receiver.MoveTo(Point(left + radii_.top_left.width, top), true);
  receiver.LineTo(Point(right - radii_.top_right.width, top));

  receiver.ConicTo(Point(right, top),
                   Point(right, top + radii_.top_right.height),  //
                   kSqrt2Over2);

  receiver.LineTo(Point(right, bottom - radii_.bottom_right.height));

  receiver.ConicTo(Point(right, bottom),
                   Point(right - radii_.bottom_right.width, bottom),  //
                   kSqrt2Over2);

  receiver.LineTo(Point(left + radii_.bottom_left.width, bottom));

  receiver.ConicTo(Point(left, bottom),
                   Point(left, bottom - radii_.bottom_left.height),  //
                   kSqrt2Over2);

  receiver.LineTo(Point(left, top + radii_.top_left.height));

  receiver.ConicTo(Point(left, top),
                   Point(left + radii_.top_left.width, top),  //
                   kSqrt2Over2);

  receiver.Close();
}

RoundRectPathSource::RoundRectPathSource(const RoundRect& round_rect)
    : round_rect_(round_rect) {}

RoundRectPathSource::~RoundRectPathSource() = default;

FillType RoundRectPathSource::GetFillType() const {
  return FillType::kNonZero;
}

Rect RoundRectPathSource::GetBounds() const {
  return round_rect_.GetBounds();
}

bool RoundRectPathSource::IsConvex() const {
  return true;
}

void RoundRectPathSource::Dispatch(PathReceiver& receiver) const {
  round_rect_.Dispatch(receiver);
}

DiffRoundRectPathSource::DiffRoundRectPathSource(const RoundRect& outer,
                                                 const RoundRect& inner)
    : outer_(outer), inner_(inner) {}

DiffRoundRectPathSource::~DiffRoundRectPathSource() = default;

FillType DiffRoundRectPathSource::GetFillType() const {
  return FillType::kOdd;
}

Rect DiffRoundRectPathSource::GetBounds() const {
  return outer_.GetBounds();
}

bool DiffRoundRectPathSource::IsConvex() const {
  return false;
}

void DiffRoundRectPathSource::Dispatch(PathReceiver& receiver) const {
  outer_.Dispatch(receiver);
  inner_.Dispatch(receiver);
}

}  // namespace impeller
