import 'dart:math' as math;

import 'point.dart';
import 'sr_segment_infos.dart';
import 'vector.dart';

enum MatrixTypes {
  transformIsIdentity,
  transformIsTranslation,
  transformIsScaling,
  placeholder3,
  transformIsUnknown,
}

extension _MatrixTypesExt on MatrixTypes {
  MatrixTypes operator |(MatrixTypes other) {
    final idx = index | other.index;
    return MatrixTypes.values[idx];
  }
}

class DMatrix {
  static DMatrix identity = DMatrix()
    ..setMatrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0, MatrixTypes.transformIsIdentity);

  /* Private Fields */

  double _m11 = 0.0;
  double _m12 = 0.0;
  double _m21 = 0.0;
  double _m22 = 0.0;
  double _offsetX = 0.0;
  double _offsetY = 0.0;
  MatrixTypes _type = MatrixTypes.transformIsIdentity;
  int _padding = 0;

/* Contructions */

  DMatrix();

  factory DMatrix.fill(
    double m11,
    double m12,
    double m21,
    double m22,
    double offsetX,
    double offsetY,
  ) {
    final m = DMatrix();
    m._m11 = m11;
    m._m12 = m12;
    m._m21 = m21;
    m._m22 = m22;
    m._offsetX = offsetX;
    m._offsetY = offsetY;
    m._type = MatrixTypes.transformIsUnknown;
    m._padding = 0;
    m._deriveMatrixType();
    return m;
  }

/* Properties */

  bool get _isDistinguishedIdentity => _type == MatrixTypes.transformIsIdentity;
  bool get isIdentity {
    if ((_type == MatrixTypes.transformIsIdentity)) return true;
    final idM1 = (_m11 == 1.0) && (_m12 == 0.0);
    final idM2 = (_m21 == 0.0) && (_m22 == 1.0);
    return (idM1 && idM2) && (_offsetX == 0.0) && (_offsetY == 0.0);
  }

  double get m11 {
    if (_type == MatrixTypes.transformIsIdentity) {
      return 1.0;
    }
    return _m11;
  }

  set m11(double value) {
    if (_type == MatrixTypes.transformIsIdentity) {
      setMatrix(value, 0.0, 0.0, 1.0, 0.0, 0.0, MatrixTypes.transformIsScaling);
    } else {
      _m11 = value;
      if (_type != MatrixTypes.transformIsUnknown) {
        _type = _type |= MatrixTypes.transformIsScaling;
      }
    }
  }

  double get m12 {
    if (_type == MatrixTypes.transformIsIdentity) {
      return 0.0;
    }
    return _m12;
  }

  set m12(double value) {
    if (_type == MatrixTypes.transformIsIdentity) {
      setMatrix(1.0, value, 0.0, 1.0, 0.0, 0.0, MatrixTypes.transformIsUnknown);
    } else {
      _m12 = value;
      _type = MatrixTypes.transformIsUnknown;
    }
  }

  double get m21 {
    if (_type == MatrixTypes.transformIsIdentity) {
      return 0.0;
    }
    return _m21;
  }

  set m21(double value) {
    if (_type == MatrixTypes.transformIsIdentity) {
      setMatrix(1.0, 0.0, value, 1.0, 0.0, 0.0, MatrixTypes.transformIsUnknown);
    } else {
      _m21 = value;
      _type = MatrixTypes.transformIsUnknown;
    }
  }

  double get m22 {
    if (_type == MatrixTypes.transformIsIdentity) {
      return 1.0;
    }
    return _m22;
  }

  set m22(double value) {
    if (_type == MatrixTypes.transformIsIdentity) {
      setMatrix(1.0, 0.0, 0.0, value, 0.0, 0.0, MatrixTypes.transformIsScaling);
    } else {
      _m22 = value;
      if (_type != MatrixTypes.transformIsUnknown) {
        _type |= MatrixTypes.transformIsScaling;
      }
    }
  }

  double get offsetX {
    if (_type == MatrixTypes.transformIsIdentity) {
      return 0.0;
    }
    return _offsetX;
  }

  set offsetX(double value) {
    if (_type == MatrixTypes.transformIsIdentity) {
      setMatrix(
          1.0, 0.0, 0.0, 1.0, value, 0.0, MatrixTypes.transformIsTranslation);
    } else {
      _offsetX = value;
      if (_type != MatrixTypes.transformIsUnknown) {
        _type |= MatrixTypes.transformIsTranslation;
      }
    }
  }

  double get offsetY {
    if (_type == MatrixTypes.transformIsIdentity) {
      return 0.0;
    }
    return _offsetY;
  }

  set offsetY(double value) {
    if (_type == MatrixTypes.transformIsIdentity) {
      setMatrix(
          1.0, 0.0, 0.0, 1.0, 0.0, value, MatrixTypes.transformIsTranslation);
    } else {
      _offsetY = value;
      if (_type != MatrixTypes.transformIsUnknown) {
        _type |= MatrixTypes.transformIsTranslation;
      }
    }
  }

  double get determinant {
    switch (_type) {
      case MatrixTypes.transformIsIdentity:
      case MatrixTypes.transformIsTranslation:
        return 1.0;

      case MatrixTypes.transformIsScaling:
      case MatrixTypes.placeholder3:
        //(MatrixTypes.TRANSFORM_IS_SCALING | MatrixTypes.TRANSFORM_IS_TRANSLATION)
        return (_m11 * _m22);
      default:
        break;
    }
    return (_m11 * _m22) - (_m12 * _m21);
  }

  bool get hasInverse {
    return !_DoubleUtil.isZero(determinant);
  }

  /* Public Methods */

  void invert() {
    if (_DoubleUtil.isZero(determinant)) {
      throw Exception("Transform_NotInvertible");
    }
    switch (_type) {
      case MatrixTypes.transformIsIdentity:
        break;
      case MatrixTypes.transformIsTranslation:
        _offsetX = -_offsetX;
        _offsetY = -_offsetY;
        return;
      case MatrixTypes.transformIsScaling:
        _m11 = 1.0 / _m11;
        _m22 = 1.0 / _m22;
        return;
      case MatrixTypes.placeholder3:
        //(MatrixTypes.TRANSFORM_IS_SCALING | MatrixTypes.TRANSFORM_IS_TRANSLATION)
        _m11 = 1.0 / _m11;
        _m22 = 1.0 / _m22;
        _offsetX = -_offsetX * _m11;
        _offsetY = -_offsetY * _m22;
        return;
      default:
        {
          double num2 = 1.0 / determinant;
          setMatrix(
            _m22 * num2,
            -_m12 * num2,
            -_m21 * num2,
            _m11 * num2,
            ((_m21 * _offsetY) - (_offsetX * _m22)) * num2,
            ((_offsetX * _m12) - (_m11 * _offsetY)) * num2,
            MatrixTypes.transformIsUnknown,
          );
          break;
        }
    }
  }

  void multiplyVector(DVector vector) {
    double x = vector.x;
    double y = vector.y;
    switch (_type) {
      case MatrixTypes.transformIsIdentity:
      case MatrixTypes.transformIsTranslation:
        return;

      case MatrixTypes.transformIsScaling:
      case MatrixTypes
          .placeholder3: // MatrixTypes.transformIsScaling | MatrixTypes.transformIsTranslation;
        x *= _m11;
        y *= _m22;
        return;
      default:
        break;
    }
    double num = y * _m21;
    double num2 = x * _m12;
    x *= _m11;
    x += num;
    y *= _m22;
    y += num2;
    vector.x = x;
    vector.y = y;
  }

  void multiplyPoint(DPoint point) {
    double x = point.x;
    double y = point.y;
    switch (_type) {
      case MatrixTypes.transformIsIdentity:
        return;

      case MatrixTypes.transformIsTranslation:
        x += _offsetX;
        y += _offsetY;
        return;

      case MatrixTypes.transformIsScaling:
        x *= _m11;
        y *= _m22;
        return;

      case MatrixTypes
          .placeholder3: // MatrixTypes.transformIsScaling | MatrixTypes.transformIsTranslation;
        x *= _m11;
        x += _offsetX;
        y *= _m22;
        y += _offsetY;
        return;
      default:
        break;
    }
    double num = (y * _m21) + _offsetX;
    double num2 = (x * _m12) + _offsetY;
    x *= _m11;
    x += num;
    y *= _m22;
    y += num2;
    point.x = x;
    point.y = y;
  }

  void setMatrix(
    double m11,
    double m12,
    double m21,
    double m22,
    double offsetX,
    double offsetY,
    MatrixTypes type,
  ) {
    _m11 = m11;
    _m12 = m12;
    _m21 = m21;
    _m22 = m22;
    _offsetX = offsetX;
    _offsetY = offsetY;
    _type = type;
  }

  void copyFrom(DMatrix other) {
    setMatrix(
      other.m11,
      other.m12,
      other.m21,
      other.m22,
      other.offsetX,
      other.offsetY,
      other._type,
    );
  }

  void skew(double skewX, double skewY) {
    skewX = skewX % 360.0;
    skewY = skewY % 360.0;
    final radians = createSkewRadians(
      skewX * 0.017453292519943295,
      skewY * 0.017453292519943295,
    );
    final outcome = this * radians;
    copyFrom(outcome);
  }

  void apppend(DMatrix matrix) {
    final outcome = this * matrix;
    copyFrom(outcome);
  }

  void rotateAt(double angle, double centerX, double centerY) {
    angle = angle % 360.0;
    final outcome = this *
        createRotationRadiansAt(
          angle * 0.017453292519943295,
          centerX,
          centerY,
        );
    copyFrom(outcome);
  }

  void scale(double scaleX, double scaleY) {
    final outcome = this * createScaling(scaleX, scaleY);
    copyFrom(outcome);
  }

  void scaleAt(
    double scaleX,
    double scaleY,
    double centerX,
    double centerY,
  ) {
    final outcome = this * createScalingAt(scaleX, scaleY, centerX, centerY);
    copyFrom(outcome);
  }

  void translate(double offsetX, double offsetY) {
    if (_type == MatrixTypes.transformIsIdentity) {
      setMatrix(
        1.0,
        0.0,
        0.0,
        1.0,
        offsetX,
        offsetY,
        MatrixTypes.transformIsTranslation,
      );
    } else if (_type == MatrixTypes.transformIsUnknown) {
      _offsetX += offsetX;
      _offsetY += offsetY;
    } else {
      _offsetX += offsetX;
      _offsetY += offsetY;
      _type |= MatrixTypes.transformIsTranslation;
    }
  }

  DPoint transformWidthPoint(DPoint point) {
    DPoint point2 = point;
    multiplyPoint(point2);
    return point2;
  }

  VecPoint transformWithVecPoint(VecPoint point) {
    final vPoint = VecPoint();
    final dPoint = DPoint(point.x, point.y);
    multiplyPoint(dPoint);
    final ddPoint = DPoint(point.x + point.dx, point.y + point.dy);
    multiplyPoint(ddPoint);
    vPoint.x = dPoint.x;
    vPoint.y = dPoint.y;
    vPoint.dx = ddPoint.x - dPoint.x;
    vPoint.dy = ddPoint.y - dPoint.y;
    return vPoint;
  }

  void transformWithPoints(List<DPoint>? points) {
    if (points != null) {
      for (int i = 0; i < points.length; i++) {
        multiplyPoint(points[i]);
      }
    }
  }

  DVector transformWithVector(DVector vector) {
    DVector vector2 = vector;
    multiplyVector(vector2);
    return vector2;
  }

  void transformWithVectors(List<DVector>? vectors) {
    if (vectors != null) {
      for (int i = 0; i < vectors.length; i++) {
        multiplyVector(vectors[i]);
      }
    }
  }

  /* Public Static Methods */

  static DMatrix createRotationRadians(double angle) {
    return createRotationRadiansAt(angle, 0.0, 0.0);
  }

  static DMatrix createRotationRadiansAt(
    double angle,
    double centerX,
    double centerY,
  ) {
    DMatrix matrix = DMatrix();
    double num = math.sin(angle);
    double num2 = math.cos(angle);
    double offsetX = (centerX * (1.0 - num2)) + (centerY * num);
    double offsetY = (centerY * (1.0 - num2)) - (centerX * num);
    matrix.setMatrix(
      num2,
      num,
      -num,
      num2,
      offsetX,
      offsetY,
      MatrixTypes.transformIsUnknown,
    );
    return matrix;
  }

  static DMatrix createScalingAt(
    double scaleX,
    double scaleY,
    double centerX,
    double centerY,
  ) {
    DMatrix matrix = DMatrix();
    matrix.setMatrix(
      scaleX,
      0.0,
      0.0,
      scaleY,
      centerX - (scaleX * centerX),
      centerY - (scaleY * centerY),
      MatrixTypes.transformIsScaling | MatrixTypes.transformIsTranslation,
    );
    return matrix;
  }

  static DMatrix createScaling(double scaleX, double scaleY) {
    DMatrix matrix = DMatrix();
    matrix.setMatrix(
      scaleX,
      0.0,
      0.0,
      scaleY,
      0.0,
      0.0,
      MatrixTypes.transformIsScaling,
    );
    return matrix;
  }

  static DMatrix createSkewRadians(double skewX, double skewY) {
    final matrix = DMatrix()
      ..setMatrix(
        1.0,
        math.tan(skewY),
        math.tan(skewX),
        1.0,
        0.0,
        0.0,
        MatrixTypes.transformIsUnknown,
      );
    return matrix;
  }

  /* Operators & Override methods */

  DMatrix operator *(DMatrix other) {
    MatrixUtil.multiplyMatrix(this, other);
    return this;
  }

  @override
  bool operator ==(Object other) {
    if (other is DMatrix) {
      if (_isDistinguishedIdentity || other._isDistinguishedIdentity) {
        return (isIdentity == other.isIdentity);
      }
      final m1Equals = (m11 == other.m11) && (m12 == other.m12);
      final m2Equals = (m21 == other.m21) && (m22 == other.m22);
      return (m1Equals && m2Equals) &&
          (offsetX == other.offsetX) &&
          (offsetY == other.offsetY);
    }
    return false;
  }

  @override
  int get hashCode {
    if (_isDistinguishedIdentity) return 0;
    return (((((m11.hashCode ^ m12.hashCode) ^ m21.hashCode) ^ m22.hashCode) ^
            offsetX.hashCode) ^
        offsetY.hashCode);
  }

  void _deriveMatrixType() {
    _type = MatrixTypes.transformIsIdentity;
    if ((_m21 != 0.0) || (_m12 != 0.0)) {
      _type = MatrixTypes.transformIsUnknown;
    } else {
      if ((_m11 != 1.0) || (_m22 != 1.0)) {
        _type = MatrixTypes.transformIsScaling;
      }

      if ((_offsetX != 0.0) || (_offsetY != 0.0)) {
        _type |= MatrixTypes.transformIsTranslation;
      }
      if ((_type.index &
              (MatrixTypes.transformIsScaling.index |
                  MatrixTypes.transformIsTranslation.index)) ==
          MatrixTypes.transformIsIdentity.index) {
        _type = MatrixTypes.transformIsIdentity;
      }
    }
  }
}

class MatrixUtil {
  static void multiplyMatrix(DMatrix matrix1, DMatrix matrix2) {
    MatrixTypes types = matrix1._type;
    MatrixTypes types2 = matrix2._type;
    if (types2 != MatrixTypes.transformIsIdentity) {
      if (types == MatrixTypes.transformIsIdentity) {
        matrix1 = matrix2;
      } else if (types2 == MatrixTypes.transformIsTranslation) {
        matrix1._offsetX += matrix2._offsetX;
        matrix1._offsetY += matrix2._offsetY;
        if (types != MatrixTypes.transformIsUnknown) {
          matrix1._type |= MatrixTypes.values[1];
          //*((int*)&matrix1._type) |= 1;
        }
      } else if (types == MatrixTypes.transformIsTranslation) {
        double num2 = matrix1._offsetX;
        double num3 = matrix1._offsetY;
        matrix1 = matrix2;
        matrix1._offsetX =
            ((num2 * matrix2._m11) + (num3 * matrix2._m21)) + matrix2._offsetX;
        matrix1._offsetY =
            ((num2 * matrix2._m12) + (num3 * matrix2._m22)) + matrix2._offsetY;
        if (types2 == MatrixTypes.transformIsUnknown) {
          matrix1._type = MatrixTypes.transformIsUnknown;
        } else {
          matrix1._type = MatrixTypes.transformIsUnknown |
              MatrixTypes.transformIsTranslation;
        }
      } else {
        final typeIdx = (types.index << 4) | types2.index;
        switch (typeIdx) {
          case 0x22:
            matrix1._m11 *= matrix2._m11;
            matrix1._m22 *= matrix2._m22;
            return;

          case 0x23:
            matrix1._m11 *= matrix2._m11;
            matrix1._m22 *= matrix2._m22;
            matrix1._offsetX = matrix2._offsetX;
            matrix1._offsetY = matrix2._offsetY;
            matrix1._type = MatrixTypes.transformIsScaling |
                MatrixTypes.transformIsTranslation;
            return;

          case 0x24:
          case 0x34:
          case 0x42:
          case 0x43:
          case 0x44:
            matrix1 = DMatrix.fill(
              (matrix1._m11 * matrix2._m11) + (matrix1._m12 * matrix2._m21),
              (matrix1._m11 * matrix2._m12) + (matrix1._m12 * matrix2._m22),
              (matrix1._m21 * matrix2._m11) + (matrix1._m22 * matrix2._m21),
              (matrix1._m21 * matrix2._m12) + (matrix1._m22 * matrix2._m22),
              ((matrix1._offsetX * matrix2._m11) +
                      (matrix1._offsetY * matrix2._m21)) +
                  matrix2._offsetX,
              ((matrix1._offsetX * matrix2._m12) +
                      (matrix1._offsetY * matrix2._m22)) +
                  matrix2._offsetY,
            );
            return;

          case 50:
            matrix1._m11 *= matrix2._m11;
            matrix1._m22 *= matrix2._m22;
            matrix1._offsetX *= matrix2._m11;
            matrix1._offsetY *= matrix2._m22;
            return;

          case 0x33:
            matrix1._m11 *= matrix2._m11;
            matrix1._m22 *= matrix2._m22;
            matrix1._offsetX =
                (matrix2._m11 * matrix1._offsetX) + matrix2._offsetX;
            matrix1._offsetY =
                (matrix2._m22 * matrix1._offsetY) + matrix2._offsetY;
            return;
        }
      }
    }
  }
}

class _DoubleUtil {
  static bool isZero(double value) {
    return (value.abs() < 2.2204460492503131E-15);
  }
}
