import 'dart:ui';

import 'package:fis_measure/interfaces/date_types/point.dart';
import 'package:fis_measure/interfaces/enums/items.dart';
import 'package:fis_measure/interfaces/process/items/item.dart';
import 'package:fis_measure/interfaces/process/items/item_metas.dart';
import 'package:fis_measure/interfaces/process/items/types.dart';
import 'package:fis_measure/interfaces/process/workspace/point_info.dart';
import 'package:fis_measure/process/calcuators/formulas/general.dart';
import 'package:fis_measure/process/items/item.dart';
import 'package:fis_measure/process/items/item_feature.dart';
import 'package:fis_measure/process/primitives/polyline.dart';
import 'package:fis_measure/process/primitives/area_abstract.dart';
import 'package:fis_measure/process/primitives/utils/auto_snap.dart';
import 'package:fis_measure/utils/canvas.dart';
import 'package:flutter/foundation.dart';
import 'package:path_drawing/path_drawing.dart';

enum LvSimpsonStep {
  none,
  splineBeginEdit,
  splineEditing,
  splineEndEdit,
  splineCompleted,
  done,
}

class SimpsonPath extends AreaItemAbstract with AutoSnapMixin {
  static const int SplitterCount = 20;
  static const double MaxPointsCount = 10000;

  SimpsonPath(ItemMeta meta, IMeasureItem? parent) : super(meta, parent);

  PointInfo? firstPoint;

  @protected
  LvSimpsonStep lvSimpsonStep = LvSimpsonStep.none;

  @override
  SimpsonPathFeature? get feature => super.feature as SimpsonPathFeature;

  @override
  bool onExecuteMouse(PointInfo args) {
    if (state == ItemStates.finished) {
      if (args.pointType == PointInfoType.mouseDown) {
        state = ItemStates.waiting;
        lvSimpsonStep = LvSimpsonStep.none;
      }
    }

    if (state == ItemStates.waiting) {
      if (args.pointType == PointInfoType.mouseDown) {
        handleMouseDownWhileWaiting(args);
      }
    } else if (state == ItemStates.running) {
      switch (args.pointType) {
        case PointInfoType.mouseUp:
          return false;
        case PointInfoType.mouseDown:
          if (lvSimpsonStep == LvSimpsonStep.splineCompleted) {
            feature!.adjustEndPoint(args.toAreaLogicPoint());
            lvSimpsonStep = LvSimpsonStep.done;
            // CaliperExtension.ShowCaliper();
          } else {
            final lineLen = (args - firstPoint!).length;
            if ((feature!.innerPoints.length > 3 &&
                    GeneralFormulas.doubleAlmostEquals(lineLen, 0)) ||
                feature!.innerPoints.length >= MaxPointsCount) {
              lvSimpsonStep = LvSimpsonStep.splineCompleted;
              feature!.fixedSpline();
            } else {
              feature!.adopt(args.toAreaLogicPoint());
            }
          }
          break;
        case PointInfoType.mouseMove:
          if (lvSimpsonStep == LvSimpsonStep.splineCompleted) {
            feature!.adjustEndPoint(args.toAreaLogicPoint());
          } else {
            if (!snapState) {
              checkAutoSnap(args, false);
            }
            if (!snapState) {
              feature!.updateActivePoint(args.toAreaLogicPoint());
            }
          }
          break;
        default:
          break;
      }
    }

    doCalculate();
    if (args.pointType == PointInfoType.mouseDown) {
      // doFeatureFinish();
      if (state == ItemStates.waiting || state == ItemStates.finished) {
        state = ItemStates.running;
      }
      if (state == ItemStates.running) {
        updateStatus();
      }
    }

    if (args.pointType == PointInfoType.mouseMove) {
      updateStatus();
    }
    return true;
  }

  @override
  bool onExecuteTouch(PointInfo args) {
    // TODO: implement onExecuteTouch
    throw UnimplementedError();
  }

  void handleMouseDownWhileWaiting(PointInfo args) {
    // TODO: 判断是否当前area
    // 转换为Area逻辑位置
    feature = SimpsonPathFeature(this);
    if (args.hostVisualArea != null) {
      feature!.hostVisualArea = args.hostVisualArea;
    }
    final point = args.toAreaLogicPoint();
    feature!.adopt(point);
    feature!.adopt(point);
    state = ItemStates.running;
    firstPoint = args;
  }

  void updateStatus() {
    switch (lvSimpsonStep) {
      case LvSimpsonStep.splineCompleted:
        firstPoint = null;
        if (feature != null) {
          feature!.activeIndex = feature!.innerPoints.length;
        }
        break;
      case LvSimpsonStep.done:
        state = ItemStates.finished;
        if (feature != null) {
          feature!.activeIndex = -1;
        }
        break;
      default:
        break;
    }
  }

  static SimpsonPath create(ItemMeta meta, [IMeasureItem? parent]) {
    final path = SimpsonPath(meta, parent);
    return path;
  }
}

class SimpsonPathFeature extends AreaItemFeatureAbstract {
  Map<int, double> horizontalSplitterLegths = {};
  IPathGeometry? _pathGeometry;
  List<DPoint>? _splinePoints;
  IPathGeometry? _spline;
  late DPoint _centerLineFixedPoint;
  late DPoint _centerLineMovablePoint;
  late DPoint _leftPoint;
  late DPoint _rightPoint;
  late DPoint _apexPoint;

  SimpsonPathFeature(AreaItemAbstract refItem) : super(refItem) {
    // _splinePoints = [];
    _centerLineFixedPoint = DPointExt.empty;
    _centerLineMovablePoint = DPointExt.empty;
    _leftPoint = DPointExt.empty;
    _rightPoint = DPointExt.empty;
    _apexPoint = DPointExt.empty;
  }

  @override
  SimpsonPath get refItem => super.refItem as SimpsonPath;

  DPoint get centerLineMovablePoint => _centerLineMovablePoint;
  set centerLineMovablePoint(DPoint val) {
    if (val != _centerLineMovablePoint) {
      _centerLineMovablePoint = val;

      updateSplitters();
      // OnVertexPointChanged();
    }
  }

  @override
  void paint(Canvas canvas, Size size) {
    if (innerPoints.isEmpty) return;

    drawId(canvas, size);

    final points = innerPoints.map((e) => convert2ViewPoint(size, e)).toList();
    final startPoint = points.first;
    drawVertex(canvas, startPoint.toOffset(), points.length == 1);

    // if (points.length > 1) {
    //   final Path path = Path();
    //   path.moveTo(startPoint.x, startPoint.y);
    //   for (var i = 1; i < points.length; i++) {
    //     final point = points[i];
    //     path.lineTo(point.x, point.y);
    //   }
    //   if (isClosed) {
    //     path.lineTo(startPoint.x, startPoint.y);
    //   }
    //   canvas.drawPath(
    //     path,
    //     paintLinePan,
    //   );
    // }

    drawVertex(canvas, points.last.toOffset(), isActive);
  }

  @override
  void adopt(DPoint point) {
    super.adopt(point);
    recreateSpline(false);
  }

  void updateActivePoint(DPoint point) {
    if (activeIndex > 0 && activeIndex <= innerPoints.length) {
      // ActivePoint = point;
      recreateSpline(false);
    }
  }

  void fixedSpline() {
    return;
    if (innerPoints.isEmpty) {
      return;
    }
    // _centerLineFixedPoint =
    DPoint((innerPoints.last.x + innerPoints.first.x) * 0.5,
        (innerPoints.last.y + innerPoints.first.y) * 0.5);

    var movablePoint = DPoint(0, double.infinity);

    // if (BaseType == MeasureTypes.SimpsonAutoTrace)
    // {
    //     movablePoint = InnerPoints[InnerPoints.Count >> 1];
    // }
    // else
    // {
    // var isFindMin = innerPoints[innerPoints.length >> 1].y - innerPoints[0].y < 0;
    // if (AutoGetApexPoint)
    // {
    //     movablePoint = isFindMin ? movablePoint :  DPoint(0, double.negativeInfinity);
    // }

    // foreach (DPoint point in InnerPoints)
    // {
    //     if (AutoGetApexPoint && innerPoints.Count > 0)
    //     {
    //         if (isFindMin)
    //         {
    //             if (point.Y < movablePoint.Y)
    //             {
    //                 movablePoint = point;
    //             }
    //         }
    //         else
    //         {
    //             if (point.Y > movablePoint.Y)
    //             {
    //                 movablePoint = point;
    //             }
    //         }
    //     }
    //     else
    //     {
    //         if (point.Y < movablePoint.Y)
    //         {
    //             movablePoint = point;
    //         }
    //     }
    // }
    // }

    // if (_centerLineMovablePoint != movablePoint)
    // {
    //     _centerLineMovablePoint = movablePoint;
    //     if (!DPointExtension.IsEmpty(_centerLineMovablePoint) && (BaseType != MeasureTypes.SimpsonAutoTrace))
    //     {
    //         _centerLineMovablePoint.SynchToMainMonitorScreen(HostArea);
    //     }
    // }
    // UpdateSplitters();
    // OnVertexPointChanged();
  }

  void adjustEndPoint(DPoint point) {
    if (innerPoints.isEmpty) {
      return;
    }
    var endPoint = point;
    double minDistance = double.infinity;
    if (_splinePoints != null) {
      for (DPoint splinePoint in _splinePoints!) {
        double distance = (point - splinePoint).length;
        if (distance < minDistance) {
          minDistance = distance;
          endPoint = splinePoint;
        }
      }
    }

    centerLineMovablePoint = endPoint;
  }

  void recreateSpline(bool isClosed) {
    // if (_breaker.Paused) {
    //   return;
    // }
    final generator = SimpsonGeometryGenerator();
    _pathGeometry ??= generator.createPathGeometry();

    double tempCircumference = 0;
    _spline = generator.createSpline(
      innerPoints,
      0.5,
      null,
      isClosed,
      false,
      0.005,
      tempCircumference,
      _splinePoints,
    );

    if (_spline != null && _splinePoints != null) {
      _pathGeometry!.addGeometry(_spline!);
    }
  }

  void updateSplitters() {
    recreateSpline(true);

    if (_spline != null && !_centerLineMovablePoint.isEmpty) {
      var generator = SimpsonGeometryGenerator();
      _pathGeometry!.clear();

      if (innerPoints.isNotEmpty) {
        _pathGeometry!.addGeometry(_spline!);

        horizontalSplitterLegths = {};
        generator.createSplitters(
          _pathGeometry!,
          _spline!,
          _centerLineFixedPoint,
          _centerLineMovablePoint,
          horizontalSplitterLegths,
        );

        _pathGeometry!.addLineGeometry(
          _centerLineFixedPoint,
          _centerLineMovablePoint,
        );
      }
    }
  }
}

class SimpsonGeometryGenerator {
  static const int splitterCount = 20;

  void createSplitters(
    IPathGeometry pathGeometry,
    IPathGeometry spline,
    DPoint centerLineFixedPoint,
    DPoint centerLineMovablePoint,
    Map<int, double> horizontalSplitterLegths,
  ) {
    // TODO: 辛普森绘制核心
  }

  IPathGeometry? createSpline(
    List<DPoint> sourcePoints,
    double tension,
    List<double>? tensions,
    bool isClosed,
    bool isFilled,
    double tolerance,
    double length,
    List<DPoint>? splinePoints,
  ) {
    length = 0;
    if (sourcePoints.isEmpty) {
      return null;
    }
    List<DPoint> samplePoints = [];
    // var points = Spline.Create(sourcePoints, tension, tensions, isClosed, tolerance, true, out length, samplePoints,true);
    var points = <DPoint>[];
    // var myPoints = GeomTools.ToWindowPoints(points.ToArray());
    // var polyLineSegment = new PolyLineSegment { Points = new PointCollection(myPoints) };
    // var pathFigure = new PathFigure { IsClosed = isClosed, IsFilled = true, StartPoint = sourcePoints[0].ToWindowPoint() };
    // pathFigure.Segments.Add(polyLineSegment);
    var pathGeometry = Path();
    // pathGeometry.Figures.Add(pathFigure);

    splinePoints = points;
    return PathGeometryContainer(pathGeometry);
  }

  IPathGeometry createPathGeometry() {
    return PathGeometryContainer(Path());
  }
}

class PathGeometryContainer implements IPathGeometry {
  final pathList = <Path>[];
  late final Path control;

  PathGeometryContainer(Path geometry) {
    control = geometry;
  }

  @override
  void addGeometry(IPathGeometry geometry) {
    // TODO:
    // pathList.addAll(geometry.pathList);
  }

  void addPath(Path path) {
    pathList.add(path);
  }

  @override
  void clear() {
    pathList.clear();
  }

  @override
  void addLineGeometry(
      DPoint centerLineFixedPoint, DPoint centerLineMovablePoint) {
    final linePath = Path()
      ..moveTo(centerLineFixedPoint.x, centerLineFixedPoint.y)
      ..lineTo(centerLineMovablePoint.x, centerLineMovablePoint.y);
    addPath(linePath);
  }
}

abstract class IPathGeometry {
  void clear();
  void addGeometry(IPathGeometry spline);
  void addLineGeometry(
      DPoint centerLineFixedPoint, DPoint centerLineMovablePoint);
}

extension DPointExt on DPoint {
  static final empty = DPoint(double.minPositive, double.minPositive);

  bool get isEmpty {
    return this == empty;
  }
}