// 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.

// See https://github.com/flutter/engine/blob/main/lib/ui/geometry.dart for
// documentation of APIs.
// ignore_for_file: public_member_api_docs
part of dart.ui;

abstract class OffsetBase {
  const OffsetBase(this._dx, this._dy)
      : assert(_dx != null), // ignore: unnecessary_null_comparison
        assert(_dy != null); // ignore: unnecessary_null_comparison

  final double _dx;
  final double _dy;
  bool get isInfinite => _dx >= double.infinity || _dy >= double.infinity;
  bool get isFinite => _dx.isFinite && _dy.isFinite;
  bool operator <(OffsetBase other) => _dx < other._dx && _dy < other._dy;
  bool operator <=(OffsetBase other) => _dx <= other._dx && _dy <= other._dy;
  bool operator >(OffsetBase other) => _dx > other._dx && _dy > other._dy;
  bool operator >=(OffsetBase other) => _dx >= other._dx && _dy >= other._dy;
  @override
  bool operator ==(Object other) {
    return other is OffsetBase
        && other._dx == _dx
        && other._dy == _dy;
  }

  @override
  int get hashCode => hashValues(_dx, _dy);

  @override
  String toString() => 'OffsetBase(${_dx.toStringAsFixed(1)}, ${_dy.toStringAsFixed(1)})';
}

class Offset extends OffsetBase {
  const Offset(double dx, double dy) : super(dx, dy);
  factory Offset.fromDirection(double direction, [ double distance = 1.0 ]) {
    return Offset(distance * math.cos(direction), distance * math.sin(direction));
  }
  double get dx => _dx;
  double get dy => _dy;
  double get distance => math.sqrt(dx * dx + dy * dy);
  double get distanceSquared => dx * dx + dy * dy;
  double get direction => math.atan2(dy, dx);
  static const Offset zero = Offset(0.0, 0.0);
  // This is included for completeness, because [Size.infinite] exists.
  static const Offset infinite = Offset(double.infinity, double.infinity);
  Offset scale(double scaleX, double scaleY) => Offset(dx * scaleX, dy * scaleY);
  Offset translate(double translateX, double translateY) => Offset(dx + translateX, dy + translateY);
  Offset operator -() => Offset(-dx, -dy);
  Offset operator -(Offset other) => Offset(dx - other.dx, dy - other.dy);
  Offset operator +(Offset other) => Offset(dx + other.dx, dy + other.dy);
  Offset operator *(double operand) => Offset(dx * operand, dy * operand);
  Offset operator /(double operand) => Offset(dx / operand, dy / operand);
  Offset operator ~/(double operand) => Offset((dx ~/ operand).toDouble(), (dy ~/ operand).toDouble());
  Offset operator %(double operand) => Offset(dx % operand, dy % operand);
  Rect operator &(Size other) => Rect.fromLTWH(dx, dy, other.width, other.height);
  static Offset? lerp(Offset? a, Offset? b, double t) {
    assert(t != null); // ignore: unnecessary_null_comparison
    if (b == null) {
      if (a == null) {
        return null;
      } else {
        return a * (1.0 - t);
      }
    } else {
      if (a == null) {
        return b * t;
      } else {
        return Offset(_lerpDouble(a.dx, b.dx, t), _lerpDouble(a.dy, b.dy, t));
      }
    }
  }

  @override
  bool operator ==(Object other) {
    return other is Offset
        && other.dx == dx
        && other.dy == dy;
  }

  @override
  int get hashCode => hashValues(dx, dy);

  @override
  String toString() => 'Offset(${dx.toStringAsFixed(1)}, ${dy.toStringAsFixed(1)})';
}

class Size extends OffsetBase {
  const Size(double width, double height) : super(width, height);
  // Used by the rendering library's _DebugSize hack.
  Size.copy(Size source) : super(source.width, source.height);
  const Size.square(double dimension) : super(dimension, dimension);
  const Size.fromWidth(double width) : super(width, double.infinity);
  const Size.fromHeight(double height) : super(double.infinity, height);
  const Size.fromRadius(double radius) : super(radius * 2.0, radius * 2.0);
  double get width => _dx;
  double get height => _dy;
  double get aspectRatio {
    if (height != 0.0)
      return width / height;
    if (width > 0.0)
      return double.infinity;
    if (width < 0.0)
      return double.negativeInfinity;
    return 0.0;
  }

  static const Size zero = Size(0.0, 0.0);
  static const Size infinite = Size(double.infinity, double.infinity);
  bool get isEmpty => width <= 0.0 || height <= 0.0;
  OffsetBase operator -(OffsetBase other) {
    if (other is Size)
      return Offset(width - other.width, height - other.height);
    if (other is Offset)
      return Size(width - other.dx, height - other.dy);
    throw ArgumentError(other);
  }

  Size operator +(Offset other) => Size(width + other.dx, height + other.dy);
  Size operator *(double operand) => Size(width * operand, height * operand);
  Size operator /(double operand) => Size(width / operand, height / operand);
  Size operator ~/(double operand) => Size((width ~/ operand).toDouble(), (height ~/ operand).toDouble());
  Size operator %(double operand) => Size(width % operand, height % operand);
  double get shortestSide => math.min(width.abs(), height.abs());
  double get longestSide => math.max(width.abs(), height.abs());

  // Convenience methods that do the equivalent of calling the similarly named
  // methods on a Rect constructed from the given origin and this size.
  Offset topLeft(Offset origin) => origin;
  Offset topCenter(Offset origin) => Offset(origin.dx + width / 2.0, origin.dy);
  Offset topRight(Offset origin) => Offset(origin.dx + width, origin.dy);
  Offset centerLeft(Offset origin) => Offset(origin.dx, origin.dy + height / 2.0);
  Offset center(Offset origin) => Offset(origin.dx + width / 2.0, origin.dy + height / 2.0);
  Offset centerRight(Offset origin) => Offset(origin.dx + width, origin.dy + height / 2.0);
  Offset bottomLeft(Offset origin) => Offset(origin.dx, origin.dy + height);
  Offset bottomCenter(Offset origin) => Offset(origin.dx + width / 2.0, origin.dy + height);
  Offset bottomRight(Offset origin) => Offset(origin.dx + width, origin.dy + height);
  bool contains(Offset offset) {
    return offset.dx >= 0.0 && offset.dx < width && offset.dy >= 0.0 && offset.dy < height;
  }

  Size get flipped => Size(height, width);
  static Size? lerp(Size? a, Size? b, double t) {
    assert(t != null); // ignore: unnecessary_null_comparison
    if (b == null) {
      if (a == null) {
        return null;
      } else {
        return a * (1.0 - t);
      }
    } else {
      if (a == null) {
        return b * t;
      } else {
        return Size(_lerpDouble(a.width, b.width, t), _lerpDouble(a.height, b.height, t));
      }
    }
  }

  // We don't compare the runtimeType because of _DebugSize in the framework.
  @override
  bool operator ==(Object other) {
    return other is Size
        && other._dx == _dx
        && other._dy == _dy;
  }

  @override
  int get hashCode => hashValues(_dx, _dy);

  @override
  String toString() => 'Size(${width.toStringAsFixed(1)}, ${height.toStringAsFixed(1)})';
}

class Rect {
  const Rect.fromLTRB(this.left, this.top, this.right, this.bottom)
      : assert(left != null), // ignore: unnecessary_null_comparison
        assert(top != null), // ignore: unnecessary_null_comparison
        assert(right != null), // ignore: unnecessary_null_comparison
        assert(bottom != null); // ignore: unnecessary_null_comparison

  const Rect.fromLTWH(double left, double top, double width, double height)
      : this.fromLTRB(left, top, left + width, top + height);

  Rect.fromCircle({ required Offset center, required double radius })
      : this.fromCenter(
          center: center,
          width: radius * 2,
          height: radius * 2,
        );

  Rect.fromCenter({ required Offset center, required double width, required double height })
      : this.fromLTRB(
          center.dx - width / 2,
          center.dy - height / 2,
          center.dx + width / 2,
          center.dy + height / 2,
        );

  Rect.fromPoints(Offset a, Offset b)
      : this.fromLTRB(
          math.min(a.dx, b.dx),
          math.min(a.dy, b.dy),
          math.max(a.dx, b.dx),
          math.max(a.dy, b.dy),
        );

  final double left;
  final double top;
  final double right;
  final double bottom;
  double get width => right - left;
  double get height => bottom - top;
  Size get size => Size(width, height);
  bool get hasNaN => left.isNaN || top.isNaN || right.isNaN || bottom.isNaN;
  static const Rect zero = Rect.fromLTRB(0.0, 0.0, 0.0, 0.0);

  static const double _giantScalar = 1.0E+9; // matches kGiantRect from layer.h
  static const Rect largest = Rect.fromLTRB(-_giantScalar, -_giantScalar, _giantScalar, _giantScalar);
  // included for consistency with Offset and Size
  bool get isInfinite {
    return left >= double.infinity
        || top >= double.infinity
        || right >= double.infinity
        || bottom >= double.infinity;
  }

  bool get isFinite => left.isFinite && top.isFinite && right.isFinite && bottom.isFinite;
  bool get isEmpty => left >= right || top >= bottom;
  Rect shift(Offset offset) {
    return Rect.fromLTRB(left + offset.dx, top + offset.dy, right + offset.dx, bottom + offset.dy);
  }

  Rect translate(double translateX, double translateY) {
    return Rect.fromLTRB(left + translateX, top + translateY, right + translateX, bottom + translateY);
  }

  Rect inflate(double delta) {
    return Rect.fromLTRB(left - delta, top - delta, right + delta, bottom + delta);
  }

  Rect deflate(double delta) => inflate(-delta);
  Rect intersect(Rect other) {
    return Rect.fromLTRB(
      math.max(left, other.left),
      math.max(top, other.top),
      math.min(right, other.right),
      math.min(bottom, other.bottom),
    );
  }

  Rect expandToInclude(Rect other) {
    return Rect.fromLTRB(
      math.min(left, other.left),
      math.min(top, other.top),
      math.max(right, other.right),
      math.max(bottom, other.bottom),
    );
  }

  bool overlaps(Rect other) {
    if (right <= other.left || other.right <= left)
      return false;
    if (bottom <= other.top || other.bottom <= top)
      return false;
    return true;
  }

  double get shortestSide => math.min(width.abs(), height.abs());
  double get longestSide => math.max(width.abs(), height.abs());
  Offset get topLeft => Offset(left, top);
  Offset get topCenter => Offset(left + width / 2.0, top);
  Offset get topRight => Offset(right, top);
  Offset get centerLeft => Offset(left, top + height / 2.0);
  Offset get center => Offset(left + width / 2.0, top + height / 2.0);
  Offset get centerRight => Offset(right, top + height / 2.0);
  Offset get bottomLeft => Offset(left, bottom);
  Offset get bottomCenter => Offset(left + width / 2.0, bottom);
  Offset get bottomRight => Offset(right, bottom);
  bool contains(Offset offset) {
    return offset.dx >= left && offset.dx < right && offset.dy >= top && offset.dy < bottom;
  }

  static Rect? lerp(Rect? a, Rect? b, double t) {
    assert(t != null); // ignore: unnecessary_null_comparison
    if (b == null) {
      if (a == null) {
        return null;
      } else {
        final double k = 1.0 - t;
        return Rect.fromLTRB(a.left * k, a.top * k, a.right * k, a.bottom * k);
      }
    } else {
      if (a == null) {
        return Rect.fromLTRB(b.left * t, b.top * t, b.right * t, b.bottom * t);
      } else {
        return Rect.fromLTRB(
          _lerpDouble(a.left, b.left, t),
          _lerpDouble(a.top, b.top, t),
          _lerpDouble(a.right, b.right, t),
          _lerpDouble(a.bottom, b.bottom, t),
        );
      }
    }
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other))
      return true;
    if (runtimeType != other.runtimeType)
      return false;
    return other is Rect
        && other.left   == left
        && other.top    == top
        && other.right  == right
        && other.bottom == bottom;
  }

  @override
  int get hashCode => hashValues(left, top, right, bottom);

  @override
  String toString() => 'Rect.fromLTRB(${left.toStringAsFixed(1)}, ${top.toStringAsFixed(1)}, ${right.toStringAsFixed(1)}, ${bottom.toStringAsFixed(1)})';
}

class Radius {
  const Radius.circular(double radius) : this.elliptical(radius, radius);
  const Radius.elliptical(this.x, this.y);
  final double x;
  final double y;
  static const Radius zero = Radius.circular(0.0);
  Radius operator -() => Radius.elliptical(-x, -y);
  Radius operator -(Radius other) => Radius.elliptical(x - other.x, y - other.y);
  Radius operator +(Radius other) => Radius.elliptical(x + other.x, y + other.y);
  Radius operator *(double operand) => Radius.elliptical(x * operand, y * operand);
  Radius operator /(double operand) => Radius.elliptical(x / operand, y / operand);
  Radius operator ~/(double operand) => Radius.elliptical((x ~/ operand).toDouble(), (y ~/ operand).toDouble());
  Radius operator %(double operand) => Radius.elliptical(x % operand, y % operand);
  static Radius? lerp(Radius? a, Radius? b, double t) {
    assert(t != null); // ignore: unnecessary_null_comparison
    if (b == null) {
      if (a == null) {
        return null;
      } else {
        final double k = 1.0 - t;
        return Radius.elliptical(a.x * k, a.y * k);
      }
    } else {
      if (a == null) {
        return Radius.elliptical(b.x * t, b.y * t);
      } else {
        return Radius.elliptical(
          _lerpDouble(a.x, b.x, t),
          _lerpDouble(a.y, b.y, t),
        );
      }
    }
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other))
      return true;
    if (runtimeType != other.runtimeType)
      return false;

    return other is Radius
        && other.x == x
        && other.y == y;
  }

  @override
  int get hashCode => hashValues(x, y);

  @override
  String toString() {
    return x == y ? 'Radius.circular(${x.toStringAsFixed(1)})' :
                    'Radius.elliptical(${x.toStringAsFixed(1)}, '
                    '${y.toStringAsFixed(1)})';
  }
}

class RRect {
  const RRect.fromLTRBXY(
    double left,
    double top,
    double right,
    double bottom,
    double radiusX,
    double radiusY,
  ) : this._raw(
        top: top,
        left: left,
        right: right,
        bottom: bottom,
        tlRadiusX: radiusX,
        tlRadiusY: radiusY,
        trRadiusX: radiusX,
        trRadiusY: radiusY,
        blRadiusX: radiusX,
        blRadiusY: radiusY,
        brRadiusX: radiusX,
        brRadiusY: radiusY,
        uniformRadii: radiusX == radiusY,
      );

  RRect.fromLTRBR(
    double left,
    double top,
    double right,
    double bottom,
    Radius radius,
  ) : this._raw(
        top: top,
        left: left,
        right: right,
        bottom: bottom,
        tlRadiusX: radius.x,
        tlRadiusY: radius.y,
        trRadiusX: radius.x,
        trRadiusY: radius.y,
        blRadiusX: radius.x,
        blRadiusY: radius.y,
        brRadiusX: radius.x,
        brRadiusY: radius.y,
        uniformRadii: radius.x == radius.y,
      );

  RRect.fromRectXY(Rect rect, double radiusX, double radiusY)
    : this._raw(
        top: rect.top,
        left: rect.left,
        right: rect.right,
        bottom: rect.bottom,
        tlRadiusX: radiusX,
        tlRadiusY: radiusY,
        trRadiusX: radiusX,
        trRadiusY: radiusY,
        blRadiusX: radiusX,
        blRadiusY: radiusY,
        brRadiusX: radiusX,
        brRadiusY: radiusY,
        uniformRadii: radiusX == radiusY,
      );

  RRect.fromRectAndRadius(Rect rect, Radius radius)
    : this._raw(
        top: rect.top,
        left: rect.left,
        right: rect.right,
        bottom: rect.bottom,
        tlRadiusX: radius.x,
        tlRadiusY: radius.y,
        trRadiusX: radius.x,
        trRadiusY: radius.y,
        blRadiusX: radius.x,
        blRadiusY: radius.y,
        brRadiusX: radius.x,
        brRadiusY: radius.y,
        uniformRadii: radius.x == radius.y,
      );

  RRect.fromLTRBAndCorners(
    double left,
    double top,
    double right,
    double bottom, {
    Radius topLeft = Radius.zero,
    Radius topRight = Radius.zero,
    Radius bottomRight = Radius.zero,
    Radius bottomLeft = Radius.zero,
  }) : this._raw(
          top: top,
          left: left,
          right: right,
          bottom: bottom,
          tlRadiusX: topLeft.x,
          tlRadiusY: topLeft.y,
          trRadiusX: topRight.x,
          trRadiusY: topRight.y,
          blRadiusX: bottomLeft.x,
          blRadiusY: bottomLeft.y,
          brRadiusX: bottomRight.x,
          brRadiusY: bottomRight.y,
          uniformRadii: topLeft.x == topLeft.y &&
            topLeft.x == topRight.x &&
            topLeft.x == topRight.y &&
            topLeft.x == bottomLeft.x &&
            topLeft.x == bottomLeft.y &&
            topLeft.x == bottomRight.x &&
            topLeft.x == bottomRight.y,
        );

  RRect.fromRectAndCorners(
    Rect rect, {
    Radius topLeft = Radius.zero,
    Radius topRight = Radius.zero,
    Radius bottomRight = Radius.zero,
    Radius bottomLeft = Radius.zero,
  }) : this._raw(
          top: rect.top,
          left: rect.left,
          right: rect.right,
          bottom: rect.bottom,
          tlRadiusX: topLeft.x,
          tlRadiusY: topLeft.y,
          trRadiusX: topRight.x,
          trRadiusY: topRight.y,
          blRadiusX: bottomLeft.x,
          blRadiusY: bottomLeft.y,
          brRadiusX: bottomRight.x,
          brRadiusY: bottomRight.y,
          uniformRadii: topLeft.x == topLeft.y &&
              topLeft.x == topRight.x &&
              topLeft.x == topRight.y &&
              topLeft.x == bottomLeft.x &&
              topLeft.x == bottomLeft.y &&
              topLeft.x == bottomRight.x &&
              topLeft.x == bottomRight.y,
        );

  const RRect._raw({
    this.left = 0.0,
    this.top = 0.0,
    this.right = 0.0,
    this.bottom = 0.0,
    this.tlRadiusX = 0.0,
    this.tlRadiusY = 0.0,
    this.trRadiusX = 0.0,
    this.trRadiusY = 0.0,
    this.brRadiusX = 0.0,
    this.brRadiusY = 0.0,
    this.blRadiusX = 0.0,
    this.blRadiusY = 0.0,
    bool uniformRadii = false,
  })  : assert(left != null), // ignore: unnecessary_null_comparison
        assert(top != null), // ignore: unnecessary_null_comparison
        assert(right != null), // ignore: unnecessary_null_comparison
        assert(bottom != null), // ignore: unnecessary_null_comparison
        assert(tlRadiusX != null), // ignore: unnecessary_null_comparison
        assert(tlRadiusY != null), // ignore: unnecessary_null_comparison
        assert(trRadiusX != null), // ignore: unnecessary_null_comparison
        assert(trRadiusY != null), // ignore: unnecessary_null_comparison
        assert(brRadiusX != null), // ignore: unnecessary_null_comparison
        assert(brRadiusY != null), // ignore: unnecessary_null_comparison
        assert(blRadiusX != null), // ignore: unnecessary_null_comparison
        assert(blRadiusY != null), // ignore: unnecessary_null_comparison
        webOnlyUniformRadii = uniformRadii;

  final double left;
  final double top;
  final double right;
  final double bottom;
  final double tlRadiusX;
  final double tlRadiusY;
  Radius get tlRadius => Radius.elliptical(tlRadiusX, tlRadiusY);
  final double trRadiusX;
  final double trRadiusY;
  Radius get trRadius => Radius.elliptical(trRadiusX, trRadiusY);
  final double brRadiusX;
  final double brRadiusY;
  Radius get brRadius => Radius.elliptical(brRadiusX, brRadiusY);
  final double blRadiusX;
  final double blRadiusY;
  // webOnly
  final bool webOnlyUniformRadii;
  Radius get blRadius => Radius.elliptical(blRadiusX, blRadiusY);
  static const RRect zero = RRect._raw();

  RRect shift(Offset offset) {
    return RRect._raw(
      left: left + offset.dx,
      top: top + offset.dy,
      right: right + offset.dx,
      bottom: bottom + offset.dy,
      tlRadiusX: tlRadiusX,
      tlRadiusY: tlRadiusY,
      trRadiusX: trRadiusX,
      trRadiusY: trRadiusY,
      blRadiusX: blRadiusX,
      blRadiusY: blRadiusY,
      brRadiusX: brRadiusX,
      brRadiusY: brRadiusY,
    );
  }

  RRect inflate(double delta) {
    return RRect._raw(
      left: left - delta,
      top: top - delta,
      right: right + delta,
      bottom: bottom + delta,
      tlRadiusX: tlRadiusX + delta,
      tlRadiusY: tlRadiusY + delta,
      trRadiusX: trRadiusX + delta,
      trRadiusY: trRadiusY + delta,
      blRadiusX: blRadiusX + delta,
      blRadiusY: blRadiusY + delta,
      brRadiusX: brRadiusX + delta,
      brRadiusY: brRadiusY + delta,
    );
  }

  RRect deflate(double delta) => inflate(-delta);
  double get width => right - left;
  double get height => bottom - top;
  Rect get outerRect => Rect.fromLTRB(left, top, right, bottom);
  Rect get safeInnerRect {
    const double kInsetFactor = 0.29289321881; // 1-cos(pi/4)

    final double leftRadius = math.max(blRadiusX, tlRadiusX);
    final double topRadius = math.max(tlRadiusY, trRadiusY);
    final double rightRadius = math.max(trRadiusX, brRadiusX);
    final double bottomRadius = math.max(brRadiusY, blRadiusY);

    return Rect.fromLTRB(
      left + leftRadius * kInsetFactor,
      top + topRadius * kInsetFactor,
      right - rightRadius * kInsetFactor,
      bottom - bottomRadius * kInsetFactor
    );
  }

  Rect get middleRect {
    final double leftRadius = math.max(blRadiusX, tlRadiusX);
    final double topRadius = math.max(tlRadiusY, trRadiusY);
    final double rightRadius = math.max(trRadiusX, brRadiusX);
    final double bottomRadius = math.max(brRadiusY, blRadiusY);
    return Rect.fromLTRB(
      left + leftRadius,
      top + topRadius,
      right - rightRadius,
      bottom - bottomRadius
    );
  }

  Rect get wideMiddleRect {
    final double topRadius = math.max(tlRadiusY, trRadiusY);
    final double bottomRadius = math.max(brRadiusY, blRadiusY);
    return Rect.fromLTRB(
      left,
      top + topRadius,
      right,
      bottom - bottomRadius
    );
  }

  Rect get tallMiddleRect {
    final double leftRadius = math.max(blRadiusX, tlRadiusX);
    final double rightRadius = math.max(trRadiusX, brRadiusX);
    return Rect.fromLTRB(
      left + leftRadius,
      top,
      right - rightRadius,
      bottom
    );
  }

  bool get isEmpty => left >= right || top >= bottom;
  bool get isFinite => left.isFinite && top.isFinite && right.isFinite && bottom.isFinite;
  bool get isRect {
    return (tlRadiusX == 0.0 || tlRadiusY == 0.0)
        && (trRadiusX == 0.0 || trRadiusY == 0.0)
        && (blRadiusX == 0.0 || blRadiusY == 0.0)
        && (brRadiusX == 0.0 || brRadiusY == 0.0);
  }

  bool get isStadium {
    return tlRadius == trRadius
        && trRadius == brRadius
        && brRadius == blRadius
        && (width <= 2.0 * tlRadiusX || height <= 2.0 * tlRadiusY);
  }

  bool get isEllipse {
    return tlRadius == trRadius
        && trRadius == brRadius
        && brRadius == blRadius
        && width <= 2.0 * tlRadiusX
        && height <= 2.0 * tlRadiusY;
  }

  bool get isCircle => width == height && isEllipse;
  double get shortestSide => math.min(width.abs(), height.abs());
  double get longestSide => math.max(width.abs(), height.abs());
  bool get hasNaN => left.isNaN || top.isNaN || right.isNaN || bottom.isNaN ||
                     trRadiusX.isNaN || trRadiusY.isNaN || tlRadiusX.isNaN || tlRadiusY.isNaN ||
                     brRadiusX.isNaN || brRadiusY.isNaN || blRadiusX.isNaN || blRadiusY.isNaN;
  Offset get center => Offset(left + width / 2.0, top + height / 2.0);

  // Returns the minimum between min and scale to which radius1 and radius2
  // should be scaled with in order not to exceed the limit.
  double _getMin(double min, double radius1, double radius2, double limit) {
    final double sum = radius1 + radius2;
    if (sum > limit && sum != 0.0)
      return math.min(min, limit / sum);
    return min;
  }

  RRect scaleRadii() {
    double scale = 1.0;
    final double absWidth = width.abs();
    final double absHeight = height.abs();
    scale = _getMin(scale, blRadiusY, tlRadiusY, absHeight);
    scale = _getMin(scale, tlRadiusX, trRadiusX, absWidth);
    scale = _getMin(scale, trRadiusY, brRadiusY, absHeight);
    scale = _getMin(scale, brRadiusX, blRadiusX, absWidth);

    if (scale < 1.0) {
      return RRect._raw(
        top: top,
        left: left,
        right: right,
        bottom: bottom,
        tlRadiusX: tlRadiusX * scale,
        tlRadiusY: tlRadiusY * scale,
        trRadiusX: trRadiusX * scale,
        trRadiusY: trRadiusY * scale,
        blRadiusX: blRadiusX * scale,
        blRadiusY: blRadiusY * scale,
        brRadiusX: brRadiusX * scale,
        brRadiusY: brRadiusY * scale,
      );
    }

    return RRect._raw(
      top: top,
      left: left,
      right: right,
      bottom: bottom,
      tlRadiusX: tlRadiusX,
      tlRadiusY: tlRadiusY,
      trRadiusX: trRadiusX,
      trRadiusY: trRadiusY,
      blRadiusX: blRadiusX,
      blRadiusY: blRadiusY,
      brRadiusX: brRadiusX,
      brRadiusY: brRadiusY,
    );
  }

  bool contains(Offset point) {
    if (point.dx < left || point.dx >= right || point.dy < top || point.dy >= bottom)
      return false; // outside bounding box

    final RRect scaled = scaleRadii();

    double x;
    double y;
    double radiusX;
    double radiusY;
    // check whether point is in one of the rounded corner areas
    // x, y -> translate to ellipse center
    if (point.dx < left + scaled.tlRadiusX &&
        point.dy < top + scaled.tlRadiusY) {
      x = point.dx - left - scaled.tlRadiusX;
      y = point.dy - top - scaled.tlRadiusY;
      radiusX = scaled.tlRadiusX;
      radiusY = scaled.tlRadiusY;
    } else if (point.dx > right - scaled.trRadiusX &&
               point.dy < top + scaled.trRadiusY) {
      x = point.dx - right + scaled.trRadiusX;
      y = point.dy - top - scaled.trRadiusY;
      radiusX = scaled.trRadiusX;
      radiusY = scaled.trRadiusY;
    } else if (point.dx > right - scaled.brRadiusX &&
               point.dy > bottom - scaled.brRadiusY) {
      x = point.dx - right + scaled.brRadiusX;
      y = point.dy - bottom + scaled.brRadiusY;
      radiusX = scaled.brRadiusX;
      radiusY = scaled.brRadiusY;
    } else if (point.dx < left + scaled.blRadiusX &&
               point.dy > bottom - scaled.blRadiusY) {
      x = point.dx - left - scaled.blRadiusX;
      y = point.dy - bottom + scaled.blRadiusY;
      radiusX = scaled.blRadiusX;
      radiusY = scaled.blRadiusY;
    } else {
      return true; // inside and not within the rounded corner area
    }

    x = x / radiusX;
    y = y / radiusY;
    // check if the point is outside the unit circle
    if (x * x + y * y > 1.0)
      return false;
    return true;
  }

  static RRect? lerp(RRect? a, RRect? b, double t) {
    assert(t != null); // ignore: unnecessary_null_comparison
    if (b == null) {
      if (a == null) {
        return null;
      } else {
        final double k = 1.0 - t;
        return RRect._raw(
          left: a.left * k,
          top: a.top * k,
          right: a.right * k,
          bottom: a.bottom * k,
          tlRadiusX: a.tlRadiusX * k,
          tlRadiusY: a.tlRadiusY * k,
          trRadiusX: a.trRadiusX * k,
          trRadiusY: a.trRadiusY * k,
          brRadiusX: a.brRadiusX * k,
          brRadiusY: a.brRadiusY * k,
          blRadiusX: a.blRadiusX * k,
          blRadiusY: a.blRadiusY * k,
        );
      }
    } else {
      if (a == null) {
        return RRect._raw(
          left: b.left * t,
          top: b.top * t,
          right: b.right * t,
          bottom: b.bottom * t,
          tlRadiusX: b.tlRadiusX * t,
          tlRadiusY: b.tlRadiusY * t,
          trRadiusX: b.trRadiusX * t,
          trRadiusY: b.trRadiusY * t,
          brRadiusX: b.brRadiusX * t,
          brRadiusY: b.brRadiusY * t,
          blRadiusX: b.blRadiusX * t,
          blRadiusY: b.blRadiusY * t,
        );
      } else {
        return RRect._raw(
          left: _lerpDouble(a.left, b.left, t),
          top: _lerpDouble(a.top, b.top, t),
          right: _lerpDouble(a.right, b.right, t),
          bottom: _lerpDouble(a.bottom, b.bottom, t),
          tlRadiusX: _lerpDouble(a.tlRadiusX, b.tlRadiusX, t),
          tlRadiusY: _lerpDouble(a.tlRadiusY, b.tlRadiusY, t),
          trRadiusX: _lerpDouble(a.trRadiusX, b.trRadiusX, t),
          trRadiusY: _lerpDouble(a.trRadiusY, b.trRadiusY, t),
          brRadiusX: _lerpDouble(a.brRadiusX, b.brRadiusX, t),
          brRadiusY: _lerpDouble(a.brRadiusY, b.brRadiusY, t),
          blRadiusX: _lerpDouble(a.blRadiusX, b.blRadiusX, t),
          blRadiusY: _lerpDouble(a.blRadiusY, b.blRadiusY, t),
        );
      }
    }
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other))
      return true;
    if (runtimeType != other.runtimeType)
      return false;
    return other is RRect
        && other.left      == left
        && other.top       == top
        && other.right     == right
        && other.bottom    == bottom
        && other.tlRadiusX == tlRadiusX
        && other.tlRadiusY == tlRadiusY
        && other.trRadiusX == trRadiusX
        && other.trRadiusY == trRadiusY
        && other.blRadiusX == blRadiusX
        && other.blRadiusY == blRadiusY
        && other.brRadiusX == brRadiusX
        && other.brRadiusY == brRadiusY;
  }

  @override
  int get hashCode => hashValues(left, top, right, bottom,
    tlRadiusX, tlRadiusY, trRadiusX, trRadiusY,
    blRadiusX, blRadiusY, brRadiusX, brRadiusY);

  @override
  String toString() {
    final String rect = '${left.toStringAsFixed(1)}, '
                        '${top.toStringAsFixed(1)}, '
                        '${right.toStringAsFixed(1)}, '
                        '${bottom.toStringAsFixed(1)}';
    if (tlRadius == trRadius &&
        trRadius == brRadius &&
        brRadius == blRadius) {
      if (tlRadius.x == tlRadius.y)
        return 'RRect.fromLTRBR($rect, ${tlRadius.x.toStringAsFixed(1)})';
      return 'RRect.fromLTRBXY($rect, ${tlRadius.x.toStringAsFixed(1)}, ${tlRadius.y.toStringAsFixed(1)})';
    }
    return 'RRect.fromLTRBAndCorners('
             '$rect, '
             'topLeft: $tlRadius, '
             'topRight: $trRadius, '
             'bottomRight: $brRadius, '
             'bottomLeft: $blRadius'
           ')';
  }
}
// Modeled after Skia's SkRSXform.

class RSTransform {
  RSTransform(double scos, double ssin, double tx, double ty) {
    _value
      ..[0] = scos
      ..[1] = ssin
      ..[2] = tx
      ..[3] = ty;
  }
  factory RSTransform.fromComponents({
    required double rotation,
    required double scale,
    required double anchorX,
    required double anchorY,
    required double translateX,
    required double translateY,
  }) {
    final double scos = math.cos(rotation) * scale;
    final double ssin = math.sin(rotation) * scale;
    final double tx = translateX + -scos * anchorX + ssin * anchorY;
    final double ty = translateY + -ssin * anchorX - scos * anchorY;
    return RSTransform(scos, ssin, tx, ty);
  }

  final Float32List _value = Float32List(4);
  double get scos => _value[0];
  double get ssin => _value[1];
  double get tx => _value[2];
  double get ty => _value[3];
}
