package com.ruoyi.common.utils.map;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.geotools.data.DataUtilities;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.geojson.feature.FeatureJSON;
import org.geotools.geojson.geom.GeometryJSON;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.io.WKTWriter;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.referencing.operation.MathTransform;
import org.locationtech.jts.operation.distance.DistanceOp;

import java.io.File;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;
import java.util.List;


/**
 * GeoUtils.
 *
 * @author qiuzhenzhao
 * @date 2024/04/19
 */
@Slf4j
@UtilityClass
public class GeoUtils {
    private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
    private static final String AUTO = "AUTO:42001,%s,%s";
    private static final WKTWriter WKT_WRITER = new WKTWriter();
    private static final WKTReader WKT_READER = new WKTReader();
    private static final GeometryJSON GEOMETRY_JSON = new GeometryJSON(15);
    private final static FeatureJSON FEATURE_JSON = new FeatureJSON(GEOMETRY_JSON);

    private static final double EARTH_RADIUS = 6371000; // 地球半径，单位：米

    /**
     * 合并多边形  Geographic
     * */
    public Geometry union(Collection<Geometry> geometries) {
        return GEOMETRY_FACTORY.buildGeometry(geometries).union();
    }

    /**
     * geometry转geojson
     * */
    @SneakyThrows
    public String geoJson(Geometry geometry) {
        SimpleFeatureType type = DataUtilities.createType("Link", "geometry:" + geometry.getGeometryType());
        SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(type);
        featureBuilder.add(geometry);
        SimpleFeature feature = featureBuilder.buildFeature(null);
        FeatureCollection<?, ?> featureCollection = new ListFeatureCollection(type, Collections.singletonList(feature));
        return FEATURE_JSON.toString(featureCollection);
    }

    /**
     *  定的geometry对象转换为GeoJSON格式，并将其写入到指定的file中
     * */
    @SneakyThrows
    public void geoJson(Geometry geometry, File file) {
        SimpleFeatureType type = DataUtilities.createType("Link", "geometry:" + geometry.getGeometryType());
        SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(type);
        featureBuilder.add(geometry);
        SimpleFeature feature = featureBuilder.buildFeature(null);
        FeatureCollection<?, ?> featureCollection = new ListFeatureCollection(type, Collections.singletonList(feature));
        FEATURE_JSON.writeFeatureCollection(featureCollection, FileUtils.openOutputStream(file));
    }
    /**
     *  wkt转Geometry对象
     *  wkt格式：POLYGON((x1 y1,x2 y2,x3 y3,x4 y4,x1 y1))
     * */
    public Geometry fromWkt(String wkt) {
        if (StringUtils.isBlank(wkt)) {
            return null;
        }
        try {
            return WKT_READER.read(wkt);
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
            return null;
        }
    }
    /**
     *  json转Geometry对象
     *  格式：[[120.356267,26.941506],[120.357168,26.941262],[120.35697,26.940564]]
     * */
    public Geometry toGeometry(String json) {
        if (StringUtils.isBlank(json)) {
            return null;
        }
        try {
            // 将json转成二位数组并形成闭合
            double[][] coordinates = transform(json);
            log.info("coordinates:{}", JSON.toJSONString(coordinates));
            // 构建坐标数组
            Coordinate[] points = new Coordinate[coordinates.length];
            for (int i = 0; i < coordinates.length; i++) {
                double lon = coordinates[i][0];
                double lat = coordinates[i][1];
                points[i] = new Coordinate(lon, lat);
            }
            // 创建LinearRing对象
            LinearRing linearRing = GEOMETRY_FACTORY.createLinearRing(points);
            // 创建Polygon对象
            Polygon polygon = GEOMETRY_FACTORY.createPolygon(linearRing);
            return polygon;
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
            return null;
        }
    }

    /**
     *  json转Geometry对象 直线
     *  格式：[[120.356267,26.941506],[120.357168,26.941262],[120.35697,26.940564]]
     * */
    public Geometry toGeometryByLinearRing(String json) {
        if (StringUtils.isBlank(json)) {
            return null;
        }
        try {
            // 将json转成二位数组
            double[][] coordinates = transform(json);
            log.info("coordinates:{}", JSON.toJSONString(coordinates));
            // 构建坐标数组
            Coordinate[] points = new Coordinate[coordinates.length];
            for (int i = 0; i < coordinates.length; i++) {
                double lon = coordinates[i][0];
                double lat = coordinates[i][1];
                points[i] = new Coordinate(lon, lat);
            }
            // 创建LineString对象
            LineString lineString = GEOMETRY_FACTORY.createLineString(points);
            return lineString;
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
            return null;
        }
    }

    /**
     *  将json转成二位数组
     * */
    public double[][] transform(String json) {
        // 使用 Jackson 库解析 JSON 字符串为二维数组
        ObjectMapper objectMapper = new ObjectMapper();
        double[][] coordinates;
        try {
            coordinates = objectMapper.readValue(json, double[][].class);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        // 添加闭合多边形的最后一个点
        int numPoints = coordinates.length;
        double[][] closedCoordinates = new double[numPoints + 1][2];
        for (int i = 0; i < numPoints; i++) {
            closedCoordinates[i] = coordinates[i];
        }
        closedCoordinates[numPoints] = coordinates[0]; // 将第一个点复制到最后一个点
        return closedCoordinates;
    }



    /**
     *  Geometry对象转wkt
     * */
    public String wkt(Geometry geometry) {
        if (geometry == null) {
            return null;
        }
        try {
            return WKT_WRITER.write(geometry);
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
            return null;
        }
    }
    /**
     *  JSON转Geometry
     * */
    public Geometry fromJson(String json) {
        if (StringUtils.isBlank(json)) {
            return null;
        }
        try {
            return GEOMETRY_JSON.read(json);
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
            return null;
        }
    }
    /**
     * 计算Geometry的面积
     * */
    @SneakyThrows
    public Double calcArea(Geometry geometry) {
        Point centroid = geometry.getCentroid();
        MathTransform mathTransform = CRS.findMathTransform(DefaultGeographicCRS.WGS84,
                CRS.decode(String.format(AUTO, centroid.getX(), centroid.getY())));
        Geometry transform = JTS.transform(geometry, mathTransform);
        return transform.getArea();
    }

    /**
     * 判断经纬度是否在多边形区域内
     * */
    public boolean isInCircle(String longitude, String latitude,Geometry polygon){
        /**创建一个点*/
        GeometryFactory geometryFactory = new GeometryFactory();
        Coordinate coordinate = new Coordinate(Double.parseDouble(longitude), Double.parseDouble(latitude));
        Point point = geometryFactory.createPoint(coordinate);
        return polygon.contains(point);
    }

    /**
     * 判断一个点是否在一个多边形区域内或者在区域外（考虑误差）
     * */
    public static boolean isInPolygonWithTolerance(BigDecimal longitude, BigDecimal latitude, Geometry polygon, BigDecimal tolerance) {
        if (longitude == null || latitude == null || polygon == null) {
            return false;
        }
        double lon = longitude.doubleValue();
        double lat = latitude.doubleValue();
        double toleranceValue = tolerance == null ? 0 : tolerance.doubleValue();

        GeometryFactory geometryFactory = new GeometryFactory();
        Point point = geometryFactory.createPoint(new Coordinate(lon, lat));

        if (polygon != null && polygon.contains(point)) {
            return true;
        } else {
            // 计算点到多边形的最短距离，若在误差范围内，则认为在多边形内
            double distance = calculateMinDistanceToPolygon(polygon, lon, lat);
            return distance <= toleranceValue;
        }
    }

    /**
     * 判断一个点是否在缩短后的区域内
     * */
    public static boolean isInPolygonWithShorten(BigDecimal longitude, BigDecimal latitude, Geometry polygon, BigDecimal shortenDistance) {
        if (longitude == null || latitude == null || polygon == null) {
            return false;
        }
        double lon = longitude.doubleValue();
        double lat = latitude.doubleValue();
        double shortenDistanceValue = shortenDistance == null ? 0 : shortenDistance.doubleValue();
        
        Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(lon, lat));
        
        if (polygon.contains(point)) {
            // 计算点到多边形边界的最短距离
            double minDistance = calculateMinDistanceToPolygon(polygon, lon, lat);
            // 如果最短距离小于缩短距离，则认为点在缩短区域外
            return minDistance > shortenDistanceValue;
        }
        return false;
    }

    // 将角度转换为弧度
    private static double deg2rad(double deg) {
        return deg * (Math.PI / 180);
    }

    // 使用 Haversine 公式计算两点间的距离
    public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
        double dLat = deg2rad(lat2 - lat1);
        double dLon = deg2rad(lon1 - lon2); // 修正此处为 (lon1 - lon2)
        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
                Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
                        Math.sin(dLon / 2) * Math.sin(dLon / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return EARTH_RADIUS * c;
    }

    public static BigDecimal calculateDistance(BigDecimal lat1, BigDecimal lon1, BigDecimal lat2, BigDecimal lon2) {
        if (lat1 == null) {
            lat1 = BigDecimal.ZERO;
        }
        if (lon1 == null) {
            lon1 = BigDecimal.ZERO;
        }
        if (lat2 == null) {
            lat2 = BigDecimal.ZERO;
        }
        if (lon2 == null) {
            lon2 = BigDecimal.ZERO;
        }
        return BigDecimal.valueOf(calculateDistance(lat1.doubleValue(), lon1.doubleValue(), lat2.doubleValue(), lon2.doubleValue()));
    }

    public static double[][] parseJsonTrack(String jsonString) {
        JSONArray jsonArray = JSONArray.parseArray(jsonString);
        double[][] track = new double[jsonArray.size()][2];
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONArray coord = jsonArray.getJSONArray(i);
            track[i][0] = coord.getDouble(0);
            track[i][1] = coord.getDouble(1);
        }
        return track;
    }

    /**
     * 计算一段轨迹的总距离
     * */
    public static double calculateTotalDistance(double[][] track) {
        double totalDistance = 0;
        for (int i = 0; i < track.length - 1; i++) {
            totalDistance += haversineDistance(track[i], track[i + 1]);
        }
        return totalDistance;
    }

    public static double haversineDistance(double[] coord1, double[] coord2) {
        double lat1 = deg2rad(coord1[1]);
        double lon1 = deg2rad(coord1[0]);
        double lat2 = deg2rad(coord2[1]);
        double lon2 = deg2rad(coord2[0]);

        double dLat = lat2 - lat1;
        double dLon = lon2 - lon1;

        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
                Math.cos(lat1) * Math.cos(lat2) *
                        Math.sin(dLon / 2) * Math.sin(dLon / 2);

        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

        return EARTH_RADIUS * c; // 距离（米）
    }


    /**
     * 计算给定点到多边形的最短距离（单位：米）
     */
    public static double calculateMinDistanceToPolygon(Geometry polygon, double lon, double lat) {
        Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(lon, lat));
        
        // 获取多边形的边界
        Geometry boundary = polygon.getBoundary();
        Coordinate[] coordinates = boundary.getCoordinates();
        
        double minDistance = Double.MAX_VALUE;
        
        // 遍历多边形的每条边
        for (int i = 0; i < coordinates.length - 1; i++) {
            Coordinate c1 = coordinates[i];
            Coordinate c2 = coordinates[i + 1];
            
            // 计算点到线段的最短距离
            double distance = calculateDistanceToSegment(lat, lon, c1.y, c1.x, c2.y, c2.x);
            minDistance = Math.min(minDistance, distance);
        }
        
        return minDistance;
    }

    /**
     * 计算点到线段的最短距离（单位：米）
     */
    private static double calculateDistanceToSegment(double lat, double lon, 
                                                   double lat1, double lon1, 
                                                   double lat2, double lon2) {
        // 计算点到线段两端点的距离
        double d1 = calculateDistance(lat, lon, lat1, lon1);
        double d2 = calculateDistance(lat, lon, lat2, lon2);
        double lineLength = calculateDistance(lat1, lon1, lat2, lon2);
        
        // 如果线段长度接近0，返回到端点的距离
        if (lineLength < 0.000001) {
            return Math.min(d1, d2);
        }
        
        // 计算点到线段的投影是否在线段上
        double t = ((lat - lat1) * (lat2 - lat1) + (lon - lon1) * (lon2 - lon1)) / 
                   ((lat2 - lat1) * (lat2 - lat1) + (lon2 - lon1) * (lon2 - lon1));
        
        if (t < 0) {
            return d1;  // 最近点是第一个端点
        }
        if (t > 1) {
            return d2;  // 最近点是第二个端点
        }
        
        // 计算投影点的坐标
        double projLat = lat1 + t * (lat2 - lat1);
        double projLon = lon1 + t * (lon2 - lon1);
        
        // 返回点到投影点的距离
        return calculateDistance(lat, lon, projLat, projLon);
    }

}
