package com.changhu.common.utils;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.changhu.common.exception.MessageException;
import lombok.extern.slf4j.Slf4j;
import org.geotools.geojson.geom.GeometryJSON;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.geotools.referencing.GeodeticCalculator;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKBReader;
import org.locationtech.jts.io.WKTReader;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;

import java.awt.geom.Point2D;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.List;
import java.util.Objects;

/**
 * author: luozhun
 * desc: GeometryUtil
 * createTime: 2023/8/25 18:03
 */
@Slf4j
public class GeometryUtil {

    /**
     * - WGS 84（srid=4326）：用于全球地理坐标系统的标准参考坐标系。
     * - 美国地理参考系统（srid=4269）：用于美国本土的地图和地理数据。
     * - 欧洲地理参考系统（srid=4258）：用于欧洲地图和地理数据。
     * - 中国国家大地坐标系（srid=4490）：用于中国的地图和地理数据。
     */
    public static final int SRID = 4326;
    public static final String ST_GeomFromText = StrUtil.format("ST_GeomFromText({},{},{})", "?", SRID, "'axis-order=long-lat'");
    private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(new PrecisionModel(), SRID);
    private static final GeometryFactory LOCATION_TECH_GEOMETRY_FACTORY = new GeometryFactory(new PrecisionModel(), SRID);

    /**
     * 空对象，因为简历空间索引不能为空 Mysql 不支持创建 Empty 的数据 这里自定义一个类型 标识为null
     */
    public static Point emptyPoint() {
        return createPoint("POINT(0 0)");
    }

    public static Polygon emptyPolygon() {
        return createPolygon("POLYGON((0 0, 0 0, 0 0, 0 0))");
    }

    public static LineString emptyLineString() {
        return createLineString("LINESTRING(0 0, 0 0)");
    }

    public static MultiPoint emptyMultiPoint() {
        return createMultiPoint("MULTIPOINT((0 0))");
    }

    public static MultiPolygon emptyMultiPolygon() {
        return createMultiPolygon("MULTIPOLYGON(((0 0, 0 0, 0 0, 0 0)))");
    }

    public static MultiLineString emptyMultiLineString() {
        return createMultiLineString("MULTILINESTRING(((0 0, 0 0)))");
    }


    public static boolean equals(Geometry geometry1, Geometry geometry2) {
        return StrUtil.equals(geometry1.toText(), geometry2.toText());
    }

    //** 点 **\\

    /**
     * 创建点对象
     */
    public static Point createPoint(List<BigDecimal> param) {
        if (param == null) {
            return null;
        }
        BigDecimal longitude = param.get(0);
        BigDecimal latitude = param.get(1);
        if (longitude == null || latitude == null) {
            return null;
        }
        Coordinate coordinate = new Coordinate(longitude.doubleValue(), latitude.doubleValue());
        return GEOMETRY_FACTORY.createPoint(coordinate);
    }

    public static Point createPoint(double longitude, double latitude) {
        Coordinate coordinate = new Coordinate(longitude, latitude);
        return GEOMETRY_FACTORY.createPoint(coordinate);
    }

    /**
     * 从WKT创建点对象
     *
     * @param wkt 注WKT类似： POINT(109.013388 32.715519)
     */
    public static Point createPoint(String wkt) {
        return createGeometryFromWkt(wkt, Point.class);
    }

    /**
     * 从WKT创建点对象
     *
     * @param bytes 数据库二进制数据
     */
    public static Point createPoint(byte[] bytes) {
        return createGeometryFromBytes(bytes, Point.class);
    }


    //** 多边形 **\\

    /**
     * 创建多边形对象
     * <p>
     * 注：后面可能考虑兼容折线类型，如果是折线类型那么需要规范首尾结点要相同才能构成 polygon 否则构成 polyline
     * 目前暂时没有做这方面的规划，用代码兼容了首尾不相等的情况构成polygon
     */
    public static Polygon createPolygon(List<List<BigDecimal>> params) {
        if (params == null || params.size() < 4) {
            throw new MessageException("构成多边形至少需要三个有效坐标，并且保证尾部坐标和首部坐标相同");
        }
        // 检测首尾是否相等，不相等则补充尾坐标
        List<BigDecimal> startPoint = params.get(0);
        List<BigDecimal> entPoint = params.get(params.size() - 1);
        if (!Objects.equals(startPoint.get(0), entPoint.get(0))
                || !Objects.equals(startPoint.get(1), entPoint.get(1))) {
            params.add(params.get(0));
        }
        Coordinate[] coordinates = convertBigDecimalList2CoordinateArray(params);
        return GEOMETRY_FACTORY.createPolygon(coordinates);
    }

    public static Polygon createPolygonWithDouble(List<List<Double>> params) {
        if (params == null || params.size() < 4) {
            throw new MessageException("构成多边形至少需要三个有效坐标，并且保证尾部坐标和首部坐标相同");
        }
        // 检测首尾是否相等，不相等则补充尾坐标
        List<Double> startPoint = params.get(0);
        List<Double> entPoint = params.get(params.size() - 1);
        if (!Objects.equals(startPoint.get(0), entPoint.get(0))
                || !Objects.equals(startPoint.get(1), entPoint.get(1))) {
            params.add(params.get(0));
        }
        Coordinate[] coordinates = convertDoubleList2CoordinateArray(params);
        return GEOMETRY_FACTORY.createPolygon(coordinates);
    }

    /**
     * 从WKT创建多边形对象
     *
     * @param wkt 注WKT类似：POLYGON((20 10, 30 0, 40 10, 30 20, 20 10))
     */
    public static Polygon createPolygon(String wkt) {
        return createGeometryFromWkt(wkt, Polygon.class);
    }

    /**
     * 创建多边形对象
     *
     * @param bytes 数据库二进制数据
     */
    public static Polygon createPolygon(byte[] bytes) {
        return createGeometryFromBytes(bytes, Polygon.class);
    }

    //** 折线 **\\

    /**
     * 创建折线对象
     */
    public static LineString createLineString(List<List<BigDecimal>> params) {
        if (params == null || params.size() < 2) {
            throw new MessageException("构成折线至少需要两个有效坐标");
        }
        Coordinate[] coordinates = convertBigDecimalList2CoordinateArray(params);
        return GEOMETRY_FACTORY.createLineString(coordinates);
    }

    /**
     * 从 wkt 创建折线对象
     */
    public static LineString createLineString(String wkt) {
        return createGeometryFromWkt(wkt, LineString.class);
    }

    /**
     * 从 bytes 创建折线对象
     */
    public static LineString createLineString(byte[] bytes) {
        return createGeometryFromBytes(bytes, LineString.class);
    }


    //** 多点 **\\

    /**
     * 创建多点对象
     */
    public static MultiPoint createMultiPoint(List<List<BigDecimal>> multiParam) {
        int size = multiParam.size();
        Point[] points = new Point[size];
        for (int i = 0; i < size; i++) {
            points[i] = createPoint(multiParam.get(i));
        }
        return GEOMETRY_FACTORY.createMultiPoint(points);
    }


    /**
     * 从 wkt 创建多点对象
     */
    public static MultiPoint createMultiPoint(String wkt) {
        return createGeometryFromWkt(wkt, MultiPoint.class);
    }


    /**
     * 从 bytes 创建多点对象
     */
    public static MultiPoint createMultiPoint(byte[] bytes) {
        return createGeometryFromBytes(bytes, MultiPoint.class);
    }


    //** 多折线 **\\

    /**
     * 创建多折线对象
     */
    public static MultiLineString createMultiLineString(List<List<List<BigDecimal>>> multiParams) {
        int size = multiParams.size();
        LineString[] lineStrings = new LineString[size];
        for (int i = 0; i < size; i++) {
            lineStrings[i] = createLineString(multiParams.get(i));
        }
        return GEOMETRY_FACTORY.createMultiLineString(lineStrings);
    }

    /**
     * 从 wkt 创建多折线对象
     */
    public static MultiLineString createMultiLineString(String wkt) {
        return createGeometryFromWkt(wkt, MultiLineString.class);
    }

    /**
     * 从 wkt 创建多折线对象
     */
    public static MultiLineString createMultiLineString(byte[] bytes) {
        return createGeometryFromBytes(bytes, MultiLineString.class);
    }

    //** 多多边形 **\\

    /**
     * 创建多多边形
     */
    public static MultiPolygon createMultiPolygon(List<List<List<BigDecimal>>> multiParams) {
        int size = multiParams.size();
        Polygon[] polygons = new Polygon[size];
        for (int i = 0; i < size; i++) {
            polygons[i] = createPolygon(multiParams.get(i));
        }
        return GEOMETRY_FACTORY.createMultiPolygon(polygons);
    }

    /**
     * 从 wkt 创建多多边形
     */
    public static MultiPolygon createMultiPolygon(String wkt) {
        return createGeometryFromWkt(wkt, MultiPolygon.class);
    }

    /**
     * 从 bytes 创建多多边形
     */
    public static MultiPolygon createMultiPolygon(byte[] bytes) {
        return createGeometryFromBytes(bytes, MultiPolygon.class);
    }

    //** 通用工具 **\\


    /**
     * bytes 创建空间对象
     *
     * @param bytes 数据库的二进制数据
     */
    public static <T extends Geometry> T createGeometryFromBytes(byte[] bytes, Class<T> clazz) {
        if (bytes == null) {
            return null;
        }
        try {
            WKBReader wkbReader = new WKBReader(GEOMETRY_FACTORY);
            byte[] geomBytes = ByteBuffer.allocate(bytes.length - 4).order(ByteOrder.LITTLE_ENDIAN)
                    .put(bytes, 4, bytes.length - 4).array();
            Geometry geometry = wkbReader.read(geomBytes);
            return Convert.convert(clazz, geometry);
        } catch (Exception e) {
            throw new MessageException("从 Bytes 创建空间对象 {} 时出现错误:{}", clazz.getSimpleName(), e.getMessage());
        }
    }

    /**
     * 从 wkt 创建空间对象
     *
     * @param wkt   类似：POLYGON((20 10, 30 0, 40 10, 30 20, 20 10)) ， POINT(109.013388 32.715519) 等
     * @param clazz 继承
     */
    public static <T extends Geometry> T createGeometryFromWkt(String wkt, Class<T> clazz) {
        if (wkt == null || StrUtil.isBlank(wkt)) {
            return null;
        }
        try {
            WKTReader reader = new WKTReader(GEOMETRY_FACTORY);
            Geometry geometry = reader.read(wkt);
            return Convert.convert(clazz, geometry);
        } catch (ParseException e) {
            throw new MessageException("从WKT创建空间对象 {} 时出现错误。wkt：{}", clazz.getSimpleName(), wkt);
        }
    }

    /**
     * 从 GeoJson 创建空间对象
     */
    public static <T extends Geometry> T createGeometryFromGeoJson(String geoJson, Class<T> clazz) {
        try {
            GeometryJSON geometryJson = new GeometryJSON(20);
            Geometry read = geometryJson.read(new StringReader(geoJson));
            return createGeometryFromWkt(read.toString(), clazz);
        } catch (IOException e) {
            log.error("从 GeoJson 创建空间对象出错:{}", e.getMessage());
        }
        return null;
    }

    /**
     * 将 List<LngLatParam> 转化为 Coordinate[]
     */
    private static Coordinate[] convertBigDecimalList2CoordinateArray(List<List<BigDecimal>> params) {
        int size = params.size();
        Coordinate[] coordinates = new Coordinate[size];
        for (int i = 0; i < params.size(); i++) {
            List<BigDecimal> param = params.get(i);
            Coordinate coordinate = new Coordinate(param.get(0).doubleValue(), param.get(1).doubleValue());
            coordinates[i] = coordinate;
        }
        return coordinates;
    }

    private static Coordinate[] convertDoubleList2CoordinateArray(List<List<Double>> params) {
        int size = params.size();
        Coordinate[] coordinates = new Coordinate[size];
        for (int i = 0; i < params.size(); i++) {
            List<Double> param = params.get(i);
            Coordinate coordinate = new Coordinate(param.get(0), param.get(1));
            coordinates[i] = coordinate;
        }
        return coordinates;
    }

    /**
     * 计算面积 平方米
     * 28155 .195928.042862
     */
    public static BigDecimal getApproximateArea(Geometry geometry) {
        try {
            String wkt = geometry.toText();
            WKTReader wktReader = new WKTReader(LOCATION_TECH_GEOMETRY_FACTORY);
            Geometry geom = wktReader.read(wkt);
            // WGS84(一般项目中常用的是CSR:84和EPSG:4326)
            CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:4326");
            // Pseudo-Mercator(墨卡托投影)
            CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:4490");
            MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);
            Geometry geometryMercator = JTS.transform(geom, transform);
            // 面积、周长
            return BigDecimal.valueOf(geometryMercator.getArea());
        } catch (FactoryException | TransformException | ParseException e) {
            log.error("计算面积出错:{}", e.getMessage());
        }
        return null;
    }

    /**
     * 当前数据库的数据来源都是高德数据源 坐标系为GCJ02 如果来源是其他的地图的需要根据规则转换成GCJ02
     * 同理如果需要转换到其他的数据源的坐标系也需要进行转换
     */
    public static <T extends Geometry> T convertWgs84ToGcj02(T geometry) {
        log.info("原始数据：{}", geometry.toText());
        Coordinate[] coordinates = geometry.getCoordinates();
        for (Coordinate coordinate : coordinates) {
            double lon = coordinate.x;
            double lat = coordinate.y;
            double[] doubles = GpsConvertUtil.wgs84ToGcj02(lon, lat);
            coordinate.x = doubles[0];
            coordinate.y = doubles[1];
        }
        log.info("转后数据：{}", geometry.toText());
        return geometry;
    }

    public static double distance(Point p0, Point p1) {
        GeodeticCalculator calculator = new GeodeticCalculator();
        calculator.setStartingGeographicPoint(p0.getX(), p0.getY());
        calculator.setDestinationGeographicPoint(p1.getX(), p1.getY());
        return calculator.getOrthodromicDistance();
    }

    public static Point2D calculateEndingGlobalCoordinates(double longitude, double latitude, double azi, double dis) {
        GeodeticCalculator calculator = new GeodeticCalculator();
        calculator.setStartingGeographicPoint(longitude, latitude);
        calculator.setDirection(azi, dis);
        return calculator.getDestinationGeographicPoint();
    }
}