import 'dart:math';

import 'package:latlong2/latlong.dart';

class GeometryMeasure {
  static final Distance _distance = const Distance();

  static double lineLengthMeters(List<LatLng> points) {
    if (points.length < 2) return 0.0;

    double sum = 0.0;

    for (var i = 1; i < points.length; i++) {
      sum += _distance.as(
        LengthUnit.Meter,
        points[i - 1],
        points[i],
      );
    }

    return sum;
  }

  static double polygonAreaSquareMeters(List<LatLng> points) {
    final polygon = _normalizedPolygon(points);

    if (polygon.length < 3) return 0.0;

    final origin = _centroid(polygon);
    final projected =
        polygon.map((p) => _projectToLocalMeters(p, origin)).toList();

    double area = 0.0;

    for (var i = 0; i < projected.length; i++) {
      final j = (i + 1) % projected.length;

      area += projected[i].x * projected[j].y - projected[j].x * projected[i].y;
    }

    return area.abs() / 2.0;
  }

  static List<LatLng> _normalizedPolygon(List<LatLng> source) {
    final points = List<LatLng>.from(source);

    if (points.length < 2) return points;

    final first = points.first;
    final last = points.last;

    final closeDistance = _distance.as(
      LengthUnit.Meter,
      first,
      last,
    );

    if (closeDistance < 0.01) {
      points.removeLast();
    }

    return points;
  }

  static LatLng _centroid(List<LatLng> points) {
    double lat = 0.0;
    double lon = 0.0;

    for (final p in points) {
      lat += p.latitude;
      lon += p.longitude;
    }

    return LatLng(
      lat / points.length,
      lon / points.length,
    );
  }

  static _Point2D _projectToLocalMeters(
    LatLng point,
    LatLng origin,
  ) {
    const earthRadius = 6378137.0;

    final latRad = point.latitude * pi / 180.0;
    final lonRad = point.longitude * pi / 180.0;

    final originLatRad = origin.latitude * pi / 180.0;
    final originLonRad = origin.longitude * pi / 180.0;

    final x = earthRadius * (lonRad - originLonRad) * cos(originLatRad);

    final y = earthRadius * (latRad - originLatRad);

    return _Point2D(x, y);
  }
}

class _Point2D {
  final double x;
  final double y;

  const _Point2D(this.x, this.y);
}
