import 'dart:math' as math;
import 'package:fis_common/logger/logger.dart';
import 'package:fis_measure/vid.dart';

import 'size.dart';

class DRect {
  static final DRect empty = DRect(
    double.infinity,
    double.infinity,
    double.negativeInfinity,
    double.negativeInfinity,
  );

  late double _x;
  late double _y;
  late double _width;
  late double _height;

  bool get isEmpty => _width < 0;

  double get x => _x;
  set x(double value) {
    if (isEmpty) {
      throw UnsupportedError("Rect_CannotModifyEmptyRect");
    }
    _x = value;
  }

  double get y => _y;
  set y(double value) {
    if (isEmpty) {
      throw UnsupportedError("Rect_CannotModifyEmptyRect");
    }
    _y = value;
  }

  double get width => _width;
  set width(double value) {
    if (isEmpty) {
      throw UnsupportedError("Rect_CannotModifyEmptyRect");
    }
    if (value < 0.0) {
      throw ArgumentError("Size_WidthCannotBeNegative");
    }
    _width = value;
  }

  double get height => _height;
  set height(double value) {
    if (isEmpty) {
      throw UnsupportedError("Rect_CannotModifyEmptyRect");
    }
    if (value < 0.0) {
      throw ArgumentError("Size_HeightCannotBeNegative");
    }
    _height = value;
  }

  double get top => _y;
  double get left => _x;

  double get right {
    if (isEmpty) {
      return double.negativeInfinity;
    }
    return (_x + _width);
  }

  double get bottom {
    if (isEmpty) {
      return double.negativeInfinity;
    }
    return (_y + height);
  }

  DRect(double x, double y, double width, double height) {
    if ((width < 0.0) || (height < 0.0)) {
      logger.e("Size_WidthAndHeightCannotBeNegative($x,$y,$width,$height)");
      width = height = 0;
    }
    _x = x;
    _y = y;
    _width = width;
    _height = height;
  }

  factory DRect.fromSize(DSize size) {
    if (size.isEmpty) return empty;
    return DRect(0, 0, size.width, size.height);
  }

  factory DRect.fromPositionedSize(DPoint point, DSize size) {
    return DRect(point.x, 0, point.y, size.height);
  }

  factory DRect.fromCrossPoints(DPoint p1, DPoint p2) {
    final x = math.min(p1.x, p2.x);
    final y = math.min(p1.y, p2.y);
    final width = math.max((math.max(p1.x, p2.x) - x), 0.0);
    final height = math.max((math.max(p1.y, p2.y) - y), 0.0);
    return DRect(x, y, width, height);
  }

  bool containsOffset(double x, double y) {
    if (isEmpty) {
      return false;
    }
    return _containsOffsetInner(x, y);
  }

  bool containsPoint(DPoint point) => containsOffset(point.x, point.y);
  bool contains(DRect rect) {
    if (isEmpty || rect.isEmpty) return false;
    return ((x <= rect.x && y <= rect.y) &&
            ((x + width) >= (rect._x + rect._width))) &&
        ((y + height) >= (rect._y + rect._height));
  }

  bool intersectsWith(DRect rect) {
    if (isEmpty || rect.isEmpty) {
      return false;
    }
    return ((((rect.left <= right) && (rect.right >= left)) &&
            (rect.top <= bottom)) &&
        (rect.bottom >= top));
  }

  void intersect(DRect rect) {
    if (!intersectsWith(rect)) {
      copyFrom(empty);
    } else {
      double num = math.max(left, rect.left);
      double num2 = math.max(top, rect.top);
      _width = math.max((math.min(right, rect.right) - num), 0.0);
      _height = math.max((math.min(bottom, rect.bottom) - num2), 0.0);
      _x = num;
      _y = num2;
    }
  }

  void union(DRect rect) {
    if (isEmpty) {
      copyFrom(rect);
    } else if (!rect.isEmpty) {
      double num = math.min(left, rect.left);
      double num2 = math.min(top, rect.top);
      if ((rect.width == double.infinity) || (width == double.infinity)) {
        _width = double.infinity;
      } else {
        double num3 = math.max(right, rect.right);
        _width = math.max((num3 - num), 0.0);
      }
      if ((rect.height == double.infinity) || (height == double.infinity)) {
        _height = double.infinity;
      } else {
        double num4 = math.max(bottom, rect.bottom);
        _height = math.max((num4 - num2), 0.0);
      }
      _x = num;
      _y = num2;
    }
  }

  void addOffset(double offsetX, double offsetY) {
    if (isEmpty) {
      throw UnsupportedError(("Rect_CannotCallMethod"));
    }
    _x += offsetX;
    _y += offsetY;
  }

  void inflate(double width, double height) {
    if (isEmpty) {
      throw UnsupportedError(("Rect_CannotCallMethod"));
    }
    _x -= width;
    _y -= height;
    _width += width;
    _width += width;
    _height += height;
    _height += height;
    if ((_width < 0.0) || (_height < 0.0)) {
      copyFrom(empty);
    }
  }

  static DRect parse(String source) {
    DRect rect;
    if (source == "Empty") {
      rect = empty;
    } else {
      var items = source.split(',');
      if (items.length != 4) {
        throw ArgumentError("$source can not be parsed to DRect");
      }
      rect = DRect(
        double.parse(items[0]),
        double.parse(items[1]),
        double.parse(items[2]),
        double.parse(items[3]),
      );
    }
    return rect;
  }

  @override
  String toString() => '$x,$y,$width,$height';

  @override
  int get hashCode {
    if (isEmpty) return 0;
    return x.hashCode ^ y.hashCode ^ width.hashCode ^ height.hashCode;
  }

  @override
  bool operator ==(Object other) {
    if (other is DRect) {
      if (isEmpty) return other.isEmpty;

      return x == other.x &&
          y == other.y &&
          width == other.width &&
          height == other.height;
    }
    return false;
  }

  bool _containsOffsetInner(double x, double y) {
    return (x >= this.x && x - width <= this.x) &&
        (y >= this.y && y - height <= this.y);
  }

  void copyFrom(DRect other) {
    _x = other.x;
    _y = other.y;
    _width = other.width;
    _height = other.height;
  }
}
