package com.sikey.wa04.business.application.service;

import com.sikey.wa04.business.domain.usecase.CoordinateConverter;
import lombok.extern.slf4j.Slf4j;
import org.gavaghan.geodesy.Ellipsoid;
import org.gavaghan.geodesy.GlobalCoordinates;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: nelson
 * @Date: 2025/5/6
 * @Description: 坐标
 */
@Slf4j
@Service
public class CoordinateConverterService {

    /**
     * 点位数据
     * <br/>
     * 一个 {@link CoordinateConverter.CoordinatePoints 区域枚举} 对应多个区域的坐标点。
     * <br/>
     * <dl>
     *     <dt>举个例子:</dt>
     *     <dd>中国境内有很多个省份，省份下面有很多个市，那么 {@link CoordinateConverter.CoordinatePoints#CHINA china} 下面就应该有很多个 {@link  ArrayList 省} </dd>
     * </dl>
     */

    private final Map<CoordinateConverter.CoordinatePoints, List<List<GlobalCoordinates>>> points = new ConcurrentHashMap<>();

    private static final double X_PI = Math.PI * 3000.0 / 180.0;
    private static final double AXIS = 6378245.0;
    private static final double OFFSET = 0.00669342162296594323;
    private static final Ellipsoid GCJ02 = Ellipsoid.fromAAndInverseF(AXIS, 297.815847);

    @Autowired
    public CoordinateConverterService(
            @Value("${coordinate.default.points:CHINA,CHINA_HONG_KONG}")
            CoordinateConverter.CoordinatePoints[] defaultPoints
    ) {
        loadPoints(defaultPoints);
    }

    /**
     * 加载点位
     *
     * @param coordinatePoints 点位 {@link CoordinateConverter.CoordinatePoints 区域枚举}
     */
    /*private void loadPoints(CoordinateConverter.CoordinatePoints... coordinatePoints) {
        for (CoordinateConverter.CoordinatePoints coordinatePoint : coordinatePoints) {
            List<List<GlobalCoordinates>> areas;
            if (points.containsKey(coordinatePoint)) {
                areas = points.get(coordinatePoint);
            } else {
                areas = new ArrayList<>();
            }

            // 解析文件
            try {
                InputStream inputStream = CoordinateConverter.class.getClassLoader().getResourceAsStream(coordinatePoint.getFilename());
                if (inputStream == null) {
                    log.warn("loadPoints error: file not found, {}", coordinatePoint.getFilename());
                    return;
                }

                List<GlobalCoordinates> coordinates = new ArrayList<>();
                BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
                String line;
                while ((line = br.readLine()) != null) {
                    areas.add(coordinates);
                    if (line.equals("|")) {
                        coordinates = new ArrayList<>();
                        continue;
                    }

                    // 这些点位都是高德提供的，所以都是 GCJ-02 的定位，我们系统所使用的都是 BD09 的坐标系
                    // 因此这里先要进行转换，然后再将点位储存
                    String[] split = line.split(",");
                    coordinates.add(GCJ02toBD09(Double.parseDouble(split[0]), Double.parseDouble(split[1])));
                }

            } catch (Exception e) {
                log.error("loadPoints error", e);
            }

            points.put(coordinatePoint, areas);
            log.info("loadPoints success: {}", coordinatePoint.getFilename());
        }
    }*/

    /**
     * 加载点位
     *
     * @param coordinatePoints 点位 {@link CoordinateConverter.CoordinatePoints 区域枚举}
     */
    private void loadPoints(CoordinateConverter.CoordinatePoints... coordinatePoints) {
        for (CoordinateConverter.CoordinatePoints coordinatePoint : coordinatePoints) {
            List<List<GlobalCoordinates>> areas = points.getOrDefault(coordinatePoint, new ArrayList<>());

            // 使用 try-with-resources 确保 InputStream 和 Reader 自动关闭
            try (InputStream inputStream = CoordinateConverter.class.getClassLoader().getResourceAsStream(coordinatePoint.getFilename());
                 InputStreamReader isr = new InputStreamReader(inputStream);
                 BufferedReader br = new BufferedReader(isr)) {

                if (inputStream == null) {
                    log.warn("loadPoints error: file not found, {}", coordinatePoint.getFilename());
                    continue;  // 跳过当前文件，继续处理下一个
                }

                List<GlobalCoordinates> coordinates = new ArrayList<>();
                String line;
                while ((line = br.readLine()) != null) {
                    if ("|".equals(line)) {
                        areas.add(coordinates);
                        coordinates = new ArrayList<>();  // 创建新的坐标集合
                        continue;
                    }

                    // 解析坐标并转换
                    String[] split = line.split(",");
                    if (split.length != 2) {
                        log.warn("Invalid line format: {}", line);
                        continue;
                    }

                    try {
                        double lng = Double.parseDouble(split[0]);
                        double lat = Double.parseDouble(split[1]);
                        coordinates.add(GCJ02toBD09(lng, lat));
                    } catch (NumberFormatException e) {
                        log.warn("Invalid number format in line: {}", line, e);
                    }
                }

                // 添加最后一个坐标集合（如果存在且未以 | 结尾）
                if (!coordinates.isEmpty()) {
                    areas.add(coordinates);
                }

                points.put(coordinatePoint, areas);
              //  log.info("loadPoints success: {}", coordinatePoint.getFilename());

            } catch (IOException e) {
                log.error("Failed to load points from file: {}", coordinatePoint.getFilename(), e);
            } catch (Exception e) {
                log.error("Unexpected error while processing file: {}", coordinatePoint.getFilename(), e);
            }
        }
    }

    /**
     * 默认的点位
     *
     * @return 默认的点位
     */
    public CoordinateConverter.CoordinatePoints[] defaultPoints() {
        return new CoordinateConverter.CoordinatePoints[]{CoordinateConverter.CoordinatePoints.CHINA, CoordinateConverter.CoordinatePoints.CHINA_HONG_KONG};
    }

    /**
     * 将百度坐标系（BD09）转换为火星坐标系（GCJ02）
     *
     * @param lon 百度坐标系下的经度
     * @param lat 百度坐标系下的纬度
     * @return 包含转换后的火星坐标系下经度和纬度的数组，数组第一个元素是经度，第二个元素是纬度
     */
    public GlobalCoordinates BD09toGCJ02(double lon, double lat) {
        double x = lon - 0.0065;
        double y = lat - 0.006;

        double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI);
        double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI);

        double gLon = z * Math.cos(theta);
        double gLat = z * Math.sin(theta);

//        return new double[]{gLon, gLat};
        return new GlobalCoordinates(gLat, gLon);
    }

    /**
     * 将火星坐标系（GCJ02）转换为百度坐标系（BD09）
     *
     * @param lon 火星坐标系下的经度
     * @param lat 火星坐标系下的纬度
     * @return 包含转换后的百度坐标系下经度和纬度的数组，数组第一个元素是经度，第二个元素是纬度
     */
    private GlobalCoordinates GCJ02toBD09(double lon, double lat) {
        double z = Math.sqrt(lon * lon + lat * lat) + 0.00002 * Math.sin(lat * X_PI);
        double theta = Math.atan2(lat, lon) + 0.000003 * Math.cos(lon * X_PI);

        double bdLon = z * Math.cos(theta) + 0.0065;
        double bdLat = z * Math.sin(theta) + 0.006;

//        return new double[]{bdLon, bdLat};
        return new GlobalCoordinates(bdLat, bdLon);
    }

    /**
     * 将WGS84坐标系转换为火星坐标系（GCJ02）
     *
     * @param lon WGS84坐标系下的经度
     * @param lat WGS84坐标系下的纬度
     * @return 包含转换后的火星坐标系下经度和纬度的数组，数组第一个元素是经度，第二个元素是纬度
     */
    private GlobalCoordinates WGS84toGCJ02(double lon, double lat) {
        return delta(lon, lat);
    }

    /**
     * 将火星坐标系（GCJ02）转换为WGS84坐标系
     *
     * @param lon 火星坐标系下的经度
     * @param lat 火星坐标系下的纬度
     * @return 包含转换后的WGS84坐标系下经度和纬度的数组，数组第一个元素是经度，第二个元素是纬度
     */
    private GlobalCoordinates GCJ02toWGS84(double lon, double lat) {
        GlobalCoordinates result = delta(lon, lat);
//        return new double[]{lon * 2 - result[0], lat * 2 - result[1]};
        return new GlobalCoordinates(lat * 2 - result.getLatitude(), lon * 2 - result.getLongitude());
    }

    /**
     * 将百度坐标系（BD09）转换为WGS84坐标系
     *
     * @param lon 百度坐标系下的经度
     * @param lat 百度坐标系下的纬度
     * @return 包含转换后的WGS84坐标系下经度和纬度的数组，数组第一个元素是经度，第二个元素是纬度
     */
    public GlobalCoordinates BD09toWGS84(double lon, double lat) {
        GlobalCoordinates gcj02Coord = BD09toGCJ02(lon, lat);
        return GCJ02toWGS84(gcj02Coord.getLongitude(), gcj02Coord.getLatitude());
    }

    /**
     * 将WGS84坐标系转换为百度坐标系（BD09）
     *
     * @param lon WGS84坐标系下的经度
     * @param lat WGS84坐标系下的纬度
     * @return 包含转换后的百度坐标系下经度和纬度的数组，数组第一个元素是经度，第二个元素是纬度
     */
    private GlobalCoordinates WGS84toBD09(double lon, double lat) {
        GlobalCoordinates gcj02Coord = WGS84toGCJ02(lon, lat);
        return GCJ02toBD09(gcj02Coord.getLongitude(), gcj02Coord.getLatitude());
    }

    private GlobalCoordinates delta(double lon, double lat) {
        double[] d = transform(lon - 105.0, lat - 35.0);
        double radlat = lat / 180.0 * Math.PI;
        double magic = Math.sin(radlat);
        magic = 1 - OFFSET * magic * magic;
        double sqrtmagic = Math.sqrt(magic);

        d[0] = (d[0] * 180.0) / ((AXIS * (1 - OFFSET)) / (magic * sqrtmagic) * Math.PI);
        d[1] = (d[1] * 180.0) / (AXIS / sqrtmagic * Math.cos(radlat) * Math.PI);

        double mgLat = lat + d[0];
        double mgLon = lon + d[1];

//        return new double[]{mgLon, mgLat};
        return new GlobalCoordinates(mgLat, mgLon);
    }

    private double[] transform(double lon, double lat) {
        double lonlat = lon * lat;
        double absX = Math.sqrt(Math.abs(lon));
        double lonPi = lon * Math.PI;
        double latPi = lat * Math.PI;
        double d = 20.0 * Math.sin(6.0 * lonPi) + 20.0 * Math.sin(2.0 * lonPi);

        double[] result = new double[]{d, d};
        result[0] += 20.0 * Math.sin(latPi) + 40.0 * Math.sin(latPi / 3.0);
        result[1] += 20.0 * Math.sin(lonPi) + 40.0 * Math.sin(lonPi / 3.0);
        result[0] += 160.0 * Math.sin(latPi / 12.0) + 320 * Math.sin(latPi / 30.0);
        result[1] += 150.0 * Math.sin(lonPi / 12.0) + 300.0 * Math.sin(lonPi / 30.0);
        result[0] *= 2.0 / 3.0;
        result[1] *= 2.0 / 3.0;
        result[0] += -100.0 + 2.0 * lon + 3.0 * lat + 0.2 * lat * lat + 0.1 * lonlat + 0.2 * absX;
        result[1] += 300.0 + lon + 2.0 * lat + 0.1 * lon * lon + 0.1 * lonlat + 0.1 * absX;
        return result;
    }

    /**
     * 获取点位
     *
     * @param coordinatePoints 点位 {@link CoordinateConverter.CoordinatePoints 区域枚举}
     * @return 点位
     */
    public List<List<GlobalCoordinates>> getPoints(CoordinateConverter.CoordinatePoints coordinatePoints) {
        List<List<GlobalCoordinates>> areas = points.get(coordinatePoints);
        if (areas == null) {
            throw new RuntimeException("points not found");
        }
        return areas;
    }

    /**
     * 射线法判断点是否在多边形内部
     * 判断国内外
     *
     */

    /**
     * 判断给定的点是否在多边形内部（使用射线法）
     *
     * @param point 待判断的点，用包含经度和纬度的数组表示，如{经度, 纬度}
     * @param areas 多边形的顶点坐标列表，每个顶点也是用包含经度和纬度的数组表示，如{{经度1, 纬度1}, {经度2, 纬度2},...}
     * @return 如果点在多边形内部，返回true；否则返回false
     */
    public boolean isPointInPolygonAreas(GlobalCoordinates point, List<List<GlobalCoordinates>> areas) {
        boolean bool = false;
        for (List<GlobalCoordinates> area : areas) {
            bool = isPointInPolygon(point, area);
            if (bool) {
                break;
            }
        }
        return bool;
    }

    /**
     * 判断给定的点是否在多边形内部（使用射线法）
     *
     * @param point   待判断的点，用包含经度和纬度的数组表示，如{经度, 纬度}
     * @param polygon 多边形的顶点坐标列表，每个顶点也是用包含经度和纬度的数组表示，如{{经度1, 纬度1}, {经度2, 纬度2},...}
     * @return 如果点在多边形内部，返回true；否则返回false
     */
    public boolean isPointInPolygon(GlobalCoordinates point, List<GlobalCoordinates> polygon) {
        int intersectionCount = 0;
        int n = polygon.size();

        for (int i = 0; i < n; i++) {
            GlobalCoordinates p1 = polygon.get(i);
            GlobalCoordinates p2 = polygon.get((i + 1) % n);

            // 检查射线与多边形的边是否相交
            if (isRayIntersectsSegment(point, p1, p2)) {
                intersectionCount++;
            }
        }

        return intersectionCount % 2 == 1;
    }

    /**
     * 判断从给定点出发的水平射线是否与给定的线段相交
     *
     * @param point        射线的起点（待判断的点），用包含经度和纬度的数组表示，如{经度, 纬度}
     * @param segmentStart 线段的起点，用包含经度和纬度的数组表示，如{经度, 纬度}
     * @param segmentEnd   线段的终点，用包含经度和纬度的数组表示，如{经度, 纬度}
     * @return 如果射线与线段相交，返回true；否则返回false
     */
    private boolean isRayIntersectsSegment(GlobalCoordinates point, GlobalCoordinates segmentStart, GlobalCoordinates segmentEnd) {
        double px = point.getLongitude();
        double py = point.getLatitude();
        double s1x = segmentStart.getLongitude();
        double s1y = segmentStart.getLatitude();
        double s2x = segmentEnd.getLongitude();
        double s2y = segmentEnd.getLatitude();

        // 排除特殊情况，如点与线段端点重合等
        if ((py == s1y && px == s1x) || (py == s2y && px == s2x)) {
            return false;
        }

        // 点在水平射线下方，不相交
        if (py < Math.min(s1y, s2y)) {
            return false;
        }

        // 点在水平射线上方，不相交
        if (py >= Math.max(s1y, s2y)) {
            return false;
        }

        // 计算交点的横坐标
        double xIntersect = (px - s1x) * (s2y - s1y) / (py - s1y) + s1x;

        // 交点在射线起点左侧，不相交
        if (xIntersect < px) {
            return false;
        }

        // 线段是垂直的且在射线起点左侧，不相交
        return s1y != s2y || !(s1x < px);
    }

}
