import 'package:fis_measure/interfaces/date_types/point.dart';
import 'package:fis_measure/interfaces/process/items/terms.dart';
import 'package:fis_measure/process/primitives/area_abstract.dart';
import 'package:fis_measure/process/primitives/ellipse.dart';
import 'package:fis_measure/process/primitives/trace.dart';

import 'calculator.dart';

class AreaPerimeterEllipseCal extends Calculator<Ellipse, double> {
  AreaPerimeterEllipseCal(Ellipse ref) : super(ref);

  @override
  void calculate() {
    if (ref.feature == null) return;

    final feature = ref.feature! as EllipseFeature;
    final viewport = feature.hostVisualArea!.viewport!;

    for (var output in ref.meta.outputs) {
      if (output.name == MeasureTerms.Perimeter) {
        var value = feature.getCircumference(viewport.convertBoundary);
        feature.updateFloatValue(output, value, output.unit);
      } else if (output.name == MeasureTerms.Area) {
        var value = feature.getArea(viewport.convertBoundary);
        feature.updateFloatValue(output, value, output.unit);
      }
    }
  }
}

class AreaPerimeterCal extends Calculator<AreaItemAbstract, double> {
  AreaPerimeterCal(AreaItemAbstract ref) : super(ref);

  @override
  void calculate() {
    if (ref.feature == null) return;

    final feature = ref.feature!;
    final viewport = feature.hostVisualArea!.viewport!;
    final points = feature.innerPoints.map((e) => viewport.convert(e)).toList();

    double area;
    double perimeter;
    if ((feature.splineTension - 0).abs() > 0) {
      // TODO: CreateSpline - Polyline.cs 850
      perimeter = 0;
      area = 0;
    } else {
      double threshold = 0.0;
      // TODO:
      // if (ref.Threshold.HasValue)
      // {
      //     threshold = ref.Threshold.Value;
      // }
      area = calcArea(points);
      perimeter = calcPerimeter(points, feature.isClosed, threshold);
    }

    for (var output in ref.meta.outputs) {
      if (output.name == MeasureTerms.Perimeter) {
        feature.updateFloatValue(output, perimeter, output.unit);
      } else if (output.name == MeasureTerms.Area) {
        feature.updateFloatValue(output, area, output.unit);
      }
    }
    // double value = clacArea(points);
    // updateFloatValue(value);
  }

  static double calcArea(List<DPoint> points) {
    if (points.isEmpty) {
      return 0;
    }

    double sum = 0;
    var ax = points[0].x;
    var ay = points[0].y;
    for (var i = 1; i < points.length - 1; i++) {
      var bx = points[i].x;
      var by = points[i].y;
      var cx = points[i + 1].x;
      var cy = points[i + 1].y;
      sum += ax * by - ay * bx + ay * cx - ax * cy + bx * cy - cx * by;
    }
    return (-sum / 2).abs();
  }

  static double calcPerimeter(
    List<DPoint> points,
    bool isClosed,
    double threshold,
  ) {
    if (points.length < 2) {
      return 0;
    }

    double sum = 0;
    for (int i = 1, j = 0; j < points.length && i < points.length; i++) {
      var length = (points[i] - points[j]).length;
      if (length.abs() > threshold) {
        sum += length;
        j = i;
      }
    }
    if (isClosed) {
      sum += (points[points.length - 1] - points[0]).length;
    }
    return sum;
  }
}

class CurveLengthCal extends Calculator<AreaItemAbstract, double> {
  CurveLengthCal(AreaItemAbstract ref) : super(ref);

  @override
  void calculate() {
    if (ref.feature == null) return;

    final feature = ref.feature!;
    final viewport = feature.hostVisualArea!.viewport!;
    final points = feature.innerPoints.map((e) => viewport.convert(e)).toList();

    double threshold = 0;
    // if (ref.Threshold.HasValue)
    // {
    //     threshold = ref.Threshold.Value;
    // }
    double perimeter = AreaPerimeterCal.calcPerimeter(
        points, ref.feature!.isClosed, threshold);

    updateFloatValue(perimeter, useRound: true);
  }
}