package com.skyversation.poiaddr.util;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import java.math.BigDecimal;

/**
 * 坐标转换工具
 */
public class Coordinate {

    private static Coordinate instance = new Coordinate();
    public static Coordinate getInstance(){
        return instance;
    }

    private static final double x_PI = (Math.PI * 3000.0) / 180.0;
    private static final double PI = Math.PI;
    private static final double ee = 0.00669342162296594323;
    private static final double a = 6378245.0;
    private static final String[] projs = {
            "wgs84", // 84坐标
            "gcj02", // 火星坐标
            "bd09", // 百度坐标
            "utm4", // utm坐标
            "shcj", // 城建坐标
            "meter", // 米制单位
            "degree" // 度制单位
    };
    private static final String[] epsg = {
            // WGS 84 - WGS84 - World Geodetic System 1984, used in GPS
            "4326", "+proj=longlat +datum=WGS84 +no_defs",
            // WGS 84 / Pseudo-Mercator - Spherical Mercator, Google Maps, OpenStreetMap, Bing, ArcGIS, ESRI
            "3857", "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext  +no_defs",
            // CGCS2000 / 3-degree Gauss-Kruger zone 40
            "4528", "+proj=tmerc +lat_0=0 +lon_0=120 +k=1 +x_0=40500000 +y_0=0 +ellps=GRS80 +units=m +no_defs"
    };

    public static double[] bd09_to_gcj02(double bd_lon, double bd_lat) {
        double x = bd_lon - 0.0065;
        double y = bd_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 gg_lng = z * Math.cos(theta);
        double gg_lat = z * Math.sin(theta);
        return new double[] {gg_lng, gg_lat};
    }

    public static double[] gcj02_to_bd09(double lng, double lat) {
        double z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI);
        double theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI);
        double bd_lng = z * Math.cos(theta) + 0.0065;
        double bd_lat = z * Math.sin(theta) + 0.006;
        return new double[] {bd_lng, bd_lat};
    }

    public static double[] wgs84_to_gcj02(double lng, double lat) {
        if (out_of_china(lng, lat)) {
            return new double[] {lng, lat};
        } else {
            double dlat = transformlat(lng - 105.0, lat - 35.0);
            double dlng = transformlng(lng - 105.0, lat - 35.0);
            double radlat = (lat / 180.0) * PI;
            double magic = Math.sin(radlat);
            magic = 1 - ee * magic * magic;
            double sqrtmagic = Math.sqrt(magic);
            dlat = (dlat * 180.0) / (((a * (1 - ee)) / (magic * sqrtmagic)) * PI);
            dlng = (dlng * 180.0) / ((a / sqrtmagic) * Math.cos(radlat) * PI);
            double mglat = lat + dlat;
            double mglng = lng + dlng;
            return new double[] {mglng, mglat};
        }
    }

    public static double[] gcj02_to_wgs84(double lng, double lat) {
        if (out_of_china(lng, lat)) {
            return new double[] {lng, lat};
        } else {
            double dlat = transformlat(lng - 105.0, lat - 35.0);
            double dlng = transformlng(lng - 105.0, lat - 35.0);
            double radlat = (lat / 180.0) * PI;
            double magic = Math.sin(radlat);
            magic = 1 - ee * magic * magic;
            double sqrtmagic = Math.sqrt(magic);
            dlat = (dlat * 180.0) / (((a * (1 - ee)) / (magic * sqrtmagic)) * PI);
            dlng = (dlng * 180.0) / ((a / sqrtmagic) * Math.cos(radlat) * PI);
            double mglat = lat + dlat;
            double mglng = lng + dlng;
            return new double[] {lng * 2 - mglng, lat * 2 - mglat};
        }
    }

    public static boolean out_of_china(double lng, double lat) {
        return !(lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.55);
    }

    public static double transformlat(double lng, double lat) {
        double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
        ret += ((20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0) / 3.0;
        ret += ((20.0 * Math.sin(lat * PI) + 40.0 * Math.sin((lat / 3.0) * PI)) * 2.0) / 3.0;
        ret += ((160.0 * Math.sin((lat / 12.0) * PI) + 320 * Math.sin((lat * PI) / 30.0)) * 2.0) / 3.0;
        return ret;
    }

    public static double transformlng(double lng, double lat) {
        double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
        ret += ((20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0) / 3.0;
        ret += ((20.0 * Math.sin(lng * PI) + 40.0 * Math.sin((lng / 3.0) * PI)) * 2.0) / 3.0;
        ret += ((150.0 * Math.sin((lng / 12.0) * PI) + 300.0 * Math.sin((lng / 30.0) * PI)) * 2.0) / 3.0;
        return ret;
    }

    public static double[] degree_to_meter(double lng, double lat) {
        double x = (lng * 20037508.34) / 180;
        double y = Math.log(Math.tan(((90 + lat) * PI) / 360)) / (PI / 180);
        y = (y * 20037508.34) / 180;
        return new double[] {x, y};
    }

    public static double[] meter_to_degree(double x, double y) {
        double lon = (x / 20037508.34) * 180;
        double lat = (y / 20037508.34) * 180;
        lat = (180 / PI) * (2 * Math.atan(Math.exp((lat * PI) / 180)) - PI / 2);
        return new double[] {lon, lat};
    }

    public static double[] wgs84_to_shcj(double x, double y) {
        double[] xy = shcj_get_UTM_from_WGS(x, y);
        return utm_to_shcj4(xy[0], xy[1]);
    }

    public static double[] utm_to_shcj4(double x, double y) {
        double DX = -500199.29965;
        double DY = -3457078.805985;
        double T = 0.0000001755;
        double K = 1.0004000106;
        return covert_by_four_parm(x, y, DX, DY, T, K);
    }

    public static double[] shcj_get_UTM_from_WGS(double lon, double lat) {
        double a = 6378137;
        double b = 6356752.3142451;
        double f = (a - b) / a;
        double eSquare = 2 * f - f * f;
        double k0 = 0.9996;
        double lonOrigin = 121.46714714;
        double FN = 0;
        double lonTemp = lon + 180 - Math.floor((lon + 180) / 360) * 360 - 180;
        double latRad = (lat * PI) / 180;
        double lonRad = (lonTemp * PI) / 180;
        double lonOriginRad = (lonOrigin * PI) / 180;
        double e2Square = eSquare / (1 - eSquare);
        double V = a / Math.sqrt(1 - eSquare * Math.pow(Math.sin(latRad), 2));
        double T = Math.pow(Math.tan(latRad), 2);
        double C = e2Square * Math.pow(Math.cos(latRad), 2);
        double A = Math.cos(latRad) * (lonRad - lonOriginRad);
        double M = a * ((1 - eSquare / 4 - (3 * Math.pow(eSquare, 2)) / 64 - (5 * Math.pow(eSquare, 3)) / 256) * latRad - ((3 * eSquare) / 8 + (3 * Math.pow(eSquare, 2)) / 32 + (45 * Math.pow(eSquare, 3)) / 1024) * Math.sin(2 * latRad) + ((15 * Math.pow(eSquare, 2)) / 256 + (45 * Math.pow(eSquare, 3)) / 1024) * Math.sin(4 * latRad) - ((35 * Math.pow(eSquare, 3)) / 3072) * Math.sin(6 * latRad));
        double UTMEasting = k0 * V * (A + ((1 - T + C) * Math.pow(A, 3)) / 6 + ((5 - 18 * T + Math.pow(T, 2) + 72 * C - 58 * e2Square) * Math.pow(A, 5)) / 120) + 500000.0;
        double UTMNorthing = k0 * (M + V * Math.tan(latRad) * (Math.pow(A, 2) / 2 + ((5 - T + 9 * C + 4 * Math.pow(C, 2)) * Math.pow(A, 4)) / 24 + ((61 - 58 * T + Math.pow(T, 2) + 600 * C - 330 * e2Square) * Math.pow(A, 6)) / 720));
        UTMNorthing += FN;
        return new double[] {UTMEasting, UTMNorthing};
    }

    public static double[] shcj_to_wgs84(double x, double y) {
        double[] xy = shcj_to_utm4(x, y);
        return shcj_get_WGS_from_UTM(xy[0], xy[1]);
    }

    public static double[] shcj_to_utm4(double x, double y) {
        double DX = 499999.90104;
        double DY = 3455696.403019;
        double T = -0.0000001755;
        double K = 0.999600149344;
        return covert_by_four_parm(x, y, DX, DY, T, K);
    }

    public static double[] covert_by_four_parm(double x, double y, double dx, double dy, double a, double k) {
        double px = x * k * Math.cos(a) - y * k * Math.sin(a) + dx;
        double py = x * k * Math.sin(a) + y * k * Math.cos(a) + dy;
        return new double[] {px, py};
    }

    public static double[] shcj_get_WGS_from_UTM(double x, double y) {
        double a = 6378137;
        double b = 6356752.3142451;
        double f = (a - b) / a;
        double e2 = 2 * f - f * f;
        double k0 = 0.9996;
        double lonOrigin = 121.46714714;
        double FN = 0;
        x = x - 500000;
        double e = Math.sqrt(1 - Math.pow(b, 2) / Math.pow(a, 2));
        double M = y / k0;
        double mu = M / (a * (1 - Math.pow(e, 2) / 4 - (3 * Math.pow(e, 4)) / 64 - (5 * Math.pow(e, 6)) / 256));
        double e1 = (1 - Math.pow(1 - Math.pow(e, 2), 1.0 / 2)) / (1 + Math.pow(1 - Math.pow(e, 2), 1.0 / 2));
        double J1 = (3 * e1) / 2 - (27 * Math.pow(e1, 3)) / 32;
        double J2 = (21 * Math.pow(e1, 2)) / 16 - (55 * Math.pow(e1, 4)) / 32;
        double J3 = (151 * Math.pow(e1, 3)) / 96;
        double J4 = (1097 * Math.pow(e1, 4)) / 512;
        double fp = mu + J1 * Math.sin(2 * mu) + J2 * Math.sin(4 * mu) + J3 * Math.sin(6 * mu) + J4 * Math.sin(8 * mu);
        double e2Square = e2 / (1 - e2);
        double C1 = e2Square * Math.pow(Math.cos(fp), 2);
        double T1 = Math.pow(Math.tan(fp), 2);
        double R1 = (a * (1 - e2)) / Math.pow(1 - e2 * Math.pow(Math.sin(fp), 2), 3.0 / 2);
        double N1 = a / Math.pow(1 - e2 * Math.pow(Math.sin(fp), 2), 1.0 / 2);
        double D = x / (N1 * k0);
        double Q1 = (N1 * Math.tan(fp)) / R1;
        double Q2 = Math.pow(D, 2) / 2;
        double Q3 = ((5 + 3 * T1 + 10 * C1 - 4 * Math.pow(C1, 2) - 9 * e2) * Math.pow(D, 4)) / 24;
        double Q4 = ((61 + 90 * T1 + 298 * C1 + 45 * Math.pow(T1, 2) - 3 * Math.pow(C1, 2) - 252 * e2) * Math.pow(D, 6)) / 720;
        double lat = ((fp - Q1 * (Q2 - Q3 + Q4)) * 180) / PI;
        double Q5 = D;
        double Q6 = ((1 + 2 * T1 + C1) * Math.pow(D, 3)) / 6;
        double Q7 = ((5 - 2 * C1 + 28 * T1 - 3 * Math.pow(C1, 2) + 8 * e2 + 24 * Math.pow(T1, 2)) * Math.pow(D, 5)) / 120;
        double lonmid = 121.46714714;
        double lon = lonmid - (((Q5 - Q6 + Q7) / Math.cos(fp)) * 180) / PI;
        double[] xy = {lon, lat};
        return xy;
    }

    public static double[] wgs84_to_bd09(double x, double y) {
        double[] ll = wgs84_to_gcj02(x, y);
        ll = gcj02_to_bd09(ll[0], ll[1]);
        return ll;
    }

    public static double[] bd09_to_wgs84(double x, double y) {
        double[] ll = bd09_to_gcj02(x, y);
        ll = gcj02_to_wgs84(ll[0], ll[1]);
        return ll;
    }

    public static double[] gcj02_to_shcj(double x, double y) {
        double[] ll = gcj02_to_wgs84(x, y);
        ll = wgs84_to_shcj(ll[0], ll[1]);
        return ll;
    }

    public static double[] shcj_to_gcj02(double x, double y) {
        double[] ll = shcj_to_wgs84(x, y);
        ll = wgs84_to_gcj02(ll[0], ll[1]);
        return ll;
    }

    public static double[] convert_proj_from_to(String from, String to, double[] xy) {
        if (from.equals(to)) {
            return xy;
        } else {
            String fromProj = projs[Integer.parseInt(from) - 1];
            String toProj = projs[Integer.parseInt(to) - 1];
            String targetMethod = fromProj + "_to_" + toProj;
            try {
                return (double[]) Coordinate.class.getMethod(targetMethod, double.class, double.class).invoke(null, xy[0], xy[1]);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    }

    public static double[] convert_proj_from_to2(String from, String to, double[] xy) {
        if (from.equals(to)) {
            return xy;
        } else {
            String fromProj = "";
            String toProj = "";
            for (int i = 0; i < epsg.length; i += 2) {
                if (epsg[i].equals(from)) {
                    fromProj = epsg[i + 1];
                }
                if (epsg[i].equals(to)) {
                    toProj = epsg[i + 1];
                }
            }
            if (!fromProj.isEmpty() && !toProj.isEmpty()) {
                double[] coor = proj4(fromProj, toProj, xy);
                return coor;
            } else {
                return null;
            }
        }
    }

    public static double[] proj4(String fromProj, String toProj, double[] xy) {
        // Implement the proj4 conversion here
        return xy;
    }

    public JSONArray shcjToWgs84(JSONArray geojson) {
        for (int i = 0; i < geojson.size(); i++) {
            JSONObject json = geojson.getJSONObject(i);
            JSONArray coordinates = json.getJSONObject("geometry").getJSONArray(
                    "coordinates");
            resolverCoordinates(coordinates);
            coordinates.toString();
        }

        return geojson;
    }

    public String[] shcjToWgs84(String[] wktStrs){
        String[] result = new String[wktStrs.length];
        for(int j = 0; j < wktStrs.length; j ++){

            String[] strs = wktStrs[j].split(" ");
            String sult = strs[0] + "(((";
            for(int i = 1; i < strs.length; i += 2){
                String s1 = strs[i];
                String s2 = strs[i + 1];
                s1 = s1.replace("(", "");
                s1 = s1.replace(")", "");
                s1 = s1.replace(",", "");
                s2 = s2.replace("(", "");
                s2 = s2.replace(")", "");
                s2 = s2.replace(",", "");
                double[] tempLatlngsAry =
                        shcj_to_wgs84(Double.valueOf(s1),
                                Double.valueOf(s2));
                sult += tempLatlngsAry[0] + " " + tempLatlngsAry[1] + ", ";
            }
            sult = sult.substring(0, sult.length() - 2) + ")))";
            result[j] = sult;
        }
        return result;
    }

    public void resolverCoordinates(JSONArray coordinates) {
        if(coordinates.size() > 0){
            for(int i = 0; i < coordinates.size(); i++){
                if(coordinates.get(i) instanceof Double || coordinates.get(i) instanceof BigDecimal){
                    double[] tempLatlngsAry =
                            shcj_to_wgs84(coordinates.getDouble(0),
                            coordinates.getDouble(1));
                    coordinates.clear();
                    coordinates.add(tempLatlngsAry[0] - 0.19195);
                    coordinates.add(tempLatlngsAry[1] + 0.00011);
                    break;
                } else {
                    resolverCoordinates(coordinates.getJSONArray(i));
                }
            }
        }
    }

//  根据输入和输出坐标系转换坐标
    public static double[] transformationCoordinateByCoordinate(Double lat,Double lon, String inCoordinate,String outCoordinate){
        double[] lonLat = new double[0];
        if ("WGS84".equals(inCoordinate)) {
            switch (outCoordinate) {
                case "GCJ02":
                    lonLat = wgs84_to_gcj02(lon, lat);
                    break;
                case "BD09":
                    lonLat = wgs84_to_bd09(lon, lat);
                    break;
                case "SH2000":
                    lonLat = wgs84_to_shcj(lon, lat);
                    break;
            }
        } else if ("GCJ02".equals(inCoordinate)) {
            switch (outCoordinate) {
                case "WGS84":
                    lonLat = gcj02_to_wgs84(lon, lat);
                    break;
                case "BD09":
                    lonLat = gcj02_to_bd09(lon, lat);
                    break;
                case "SH2000":
                    lonLat = gcj02_to_shcj(lon, lat);
                    break;
            }
        } else if ("BD09".equals(inCoordinate)) {
            switch (outCoordinate) {
                case "WGS84":
                    lonLat = bd09_to_wgs84(lon, lat);
                    break;
                case "GCJ02":
                    lonLat = bd09_to_gcj02(lon, lat);
                    break;
                case "SH2000":
                    double[] lonLat2 = bd09_to_wgs84(lon, lat);
                    lonLat = wgs84_to_shcj(lonLat2[0], lonLat2[1]);
                    break;
            }
        } else if ("SH2000".equals(inCoordinate)) {
            switch (outCoordinate) {
                case "WGS84":
                    lonLat = shcj_to_wgs84(lon, lat);
                    break;
                case "BD09":
                    double[] lonLat2 = shcj_to_wgs84(lon, lat);
                    lonLat = wgs84_to_bd09(lonLat2[0], lonLat2[1]);
                    break;
                case "GCJ02":
                    lonLat = shcj_to_gcj02(lon, lat);
                    break;
            }
        }
        return lonLat;
    }

    /***
     *  计算两个经纬度之间的距离，返回单位米
     * @param lat1
     * @param lon1
     * @param lat2
     * @param lon2
     * @return
     */
    public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
        // 将经纬度转换为弧度
        double lat1Rad = Math.toRadians(lat1);
        double lon1Rad = Math.toRadians(lon1);
        double lat2Rad = Math.toRadians(lat2);
        double lon2Rad = Math.toRadians(lon2);

//        // 地球半径（单位：千米）
//        double earthRadius = 6371.0;

        // Haversine公式计算两点之间的距离
        double dLat = lat2Rad - lat1Rad;
        double dLon = lon2Rad - lon1Rad;
        double a = Math.pow(Math.sin(dLat / 2), 2) + Math.cos(lat1Rad) * Math.cos(lat2Rad) * Math.pow(Math.sin(dLon / 2), 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        double distance = 6378245.0 * c;

        return distance;
    }
}
