// ramer_douglas_peucker.dart

import 'package:flutter/material.dart';

/// 封装了拉默-道格拉斯-普克（Ramer-Douglas-Peucker, RDP）算法的工具类。
///
/// RDP算法用于将一条由大量点组成的曲线，在保持其大致形状的前提下，减少点的数量。
/// 这对于手势识别前的路径简化至关重要，可以有效去除噪声并提取关键特征点。
class RamerDouglasPeucker {
  /// 计算点到由起点和终点定义的线段的垂直距离。
  ///
  /// [point]: 需要计算距离的点。
  /// [start]: 线段的起点。
  /// [end]: 线段的终点。
  /// 返回点到线段的垂直距离。如果线段长度为0，则返回点到起点的距离。
  static double _getPerpendicularDistance(Offset point, Offset start, Offset end) {
    final double dx = end.dx - start.dx;
    final double dy = end.dy - start.dy;
    final double lengthSquared = dx * dx + dy * dy;

    // 如果起点和终点重合，线段长度为0，直接返回点到起点的距离。
    if (lengthSquared == 0.0) {
      return (point - start).distance;
    }

    // 计算点在线段上的投影位置。
    // t是投影点在线段方向向量上的比例因子。
    final double t = ((point.dx - start.dx) * dx + (point.dy - start.dy) * dy) / lengthSquared;

    // 将t限制在[0, 1]范围内，确保投影点位于线段上。
    // 如果t<0，最近点是start；如果t>1，最近点是end。
    final double tClamped = t.clamp(0.0, 1.0);
    final Offset projection = Offset(start.dx + tClamped * dx, start.dy + tClamped * dy);

    return (point - projection).distance;
  }

  /// 使用迭代方式实现的RDP简化算法，以优化性能和避免栈溢出。
  ///
  /// [points]: 原始的点列表。
  /// [epsilon]: 距离容差。决定了简化的精细程度，值越小，保留的点越多。
  /// 返回一个只包含关键点的简化后列表。
  static List<Offset> simplify(List<Offset> points, double epsilon) {
    final int totalPoints = points.length;
    // 如果点的数量不足以构成一个可以简化的形状，直接返回原列表。
    if (totalPoints < 3) {
      return points;
    }

    // 使用一个栈来管理需要处理的点段索引，避免递归。
    final List<List<int>> stack = [];
    // `keepPoint`数组用于标记哪些点是关键点，需要保留。
    final List<bool> keepPoint = List.filled(totalPoints, false);

    // 初始时，起点和终点总是保留的。
    keepPoint[0] = true;
    keepPoint[totalPoints - 1] = true;

    // 将整个点列表的索引范围[0, totalPoints - 1]压入栈中。
    stack.add([0, totalPoints - 1]);

    while (stack.isNotEmpty) {
      final List<int> currentSegment = stack.removeLast();
      final int startIndex = currentSegment[0];
      final int endIndex = currentSegment[1];

      double maxDistance = 0.0;
      int farthestIndex = 0;

      // 在当前段中寻找距离基准线（由startIndex和endIndex构成）最远的点。
      for (int i = startIndex + 1; i < endIndex; i++) {
        final double distance = _getPerpendicularDistance(points[i], points[startIndex], points[endIndex]);
        if (distance > maxDistance) {
          maxDistance = distance;
          farthestIndex = i;
        }
      }

      // 如果最远点的距离大于容差epsilon，说明这个点是重要的特征点。
      if (maxDistance > epsilon) {
        // 保留这个最远点。
        keepPoint[farthestIndex] = true;

        // 将原线段以最远点为界，分成两个新的子段，并压入栈中等待处理。
        // 左半段
        if (farthestIndex - startIndex > 1) {
          stack.add([startIndex, farthestIndex]);
        }
        // 右半段
        if (endIndex - farthestIndex > 1) {
          stack.add([farthestIndex, endIndex]);
        }
      }
    }

    // 根据标记数组，构建并返回最终的简化点列表。
    final List<Offset> simplifiedPoints = [];
    for (int i = 0; i < totalPoints; i++) {
      if (keepPoint[i]) {
        simplifiedPoints.add(points[i]);
      }
    }

    return simplifiedPoints;
  }
}
