package incheon.cmm.g2f.util;

import org.geotools.api.referencing.FactoryException;
import org.geotools.api.referencing.crs.CRSFactory;
import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
import org.geotools.api.referencing.crs.GeographicCRS;
import org.geotools.api.referencing.crs.ProjectedCRS;
import org.geotools.api.referencing.datum.GeodeticDatum;
import org.geotools.api.referencing.operation.MathTransform;
import org.geotools.api.referencing.operation.TransformException;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.store.ReprojectingFeatureCollection;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.referencing.datum.BursaWolfParameters;
import org.geotools.referencing.datum.DefaultGeodeticDatum;
import org.locationtech.jts.geom.Geometry;

import java.util.HashMap;
import java.util.Map;

public class CoordinateUtils {

    /**
     * 주어진 ProjectedCRS의 Datum에 포함된 TOWGS84 파라미터를
     * 한국 좌표계에 맞는 교정된 값으로 교체하여 새로운 CRS를 생성한다.
     *
     * @param projectedCRS 교정 대상 ProjectedCRS
     * @return 교정된 TOWGS84 파라미터를 가진 새로운 CRS
     * @throws FactoryException CRS 생성 중 오류가 발생한 경우
     */
    public static CoordinateReferenceSystem createChangedTowgs84CRS(ProjectedCRS projectedCRS) throws FactoryException {
        if (!(projectedCRS.getDatum() instanceof DefaultGeodeticDatum) || !"6162".equals(projectedCRS.getDatum().getIdentifiers().iterator().next().getCode())) {
            return projectedCRS;
        }

        BursaWolfParameters bursaWolfParameters = new BursaWolfParameters(DefaultGeographicCRS.WGS84.getDatum());
        bursaWolfParameters.dx = -115.80;
        bursaWolfParameters.dy = 474.99;
        bursaWolfParameters.dz = 674.11;
        bursaWolfParameters.ex = 1.16;
        bursaWolfParameters.ey = -2.31;
        bursaWolfParameters.ez = -1.63;
        bursaWolfParameters.ppm = 6.43;

        CRSFactory crsFactory = ReferencingFactoryFinder.getCRSFactory(null);
        GeographicCRS geographicCRS = projectedCRS.getBaseCRS();
        Map<String, Object> properties = new HashMap<>(DefaultGeographicCRS.getProperties(geographicCRS));
        properties.put(DefaultGeodeticDatum.BURSA_WOLF_KEY, bursaWolfParameters);
        GeodeticDatum datum = new DefaultGeodeticDatum(properties, geographicCRS.getDatum().getEllipsoid(), geographicCRS.getDatum().getPrimeMeridian());
        geographicCRS = crsFactory.createGeographicCRS(properties, datum, geographicCRS.getCoordinateSystem());
        return crsFactory.createProjectedCRS(new HashMap<>(DefaultGeographicCRS.getProperties(projectedCRS)), geographicCRS, projectedCRS.getConversionFromBase(), projectedCRS.getCoordinateSystem());
    }

    /**
     * 주어진 FeatureCollection의 좌표계를 확인하고,
     * EPSG:3857로 변환하여 반환한다.
     *
     * @param collection 원본 FeatureCollection
     * @return EPSG:3857로 좌표계가 맞춰진 FeatureCollection
     * @throws FactoryException CRS 변환 정의를 찾을 수 없는 경우 발생
     */
    public static SimpleFeatureCollection reprojectTo3857(SimpleFeatureCollection collection) throws FactoryException, FactoryException {
        CoordinateReferenceSystem sourceCRS = collection.getSchema().getCoordinateReferenceSystem();
        CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:3857", true);
        if (CRS.equalsIgnoreMetadata(sourceCRS, targetCRS)) {
            return collection;
        }
        if (sourceCRS instanceof ProjectedCRS) {
            sourceCRS = createChangedTowgs84CRS((ProjectedCRS) sourceCRS);
        }
        return new ReprojectingFeatureCollection(collection, sourceCRS, targetCRS);
    }

    /**
     * EPSG:3857인 Geometry를 주어진 좌표계로 변환하여 반환한다.
     *
     * @param geometry 원본 Geometry
     * @param srid 주어진 좌표계의 srid
     * @return 주어진 좌표계로 변환된 Geometry
     * @throws FactoryException CRS 변환 정의를 찾을 수 없는 경우 발생
     * @throws TransformException CRS 변환 실패하는 경우 발생
     */
    public static Geometry reprojectFrom3857(Geometry geometry, int srid) throws FactoryException, FactoryException, TransformException {
        CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:3857", true);
        CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:" + srid, true);
        if (CRS.equalsIgnoreMetadata(sourceCRS, targetCRS)) {
            return geometry;
        }
        if (sourceCRS instanceof ProjectedCRS) {
            sourceCRS = createChangedTowgs84CRS((ProjectedCRS) sourceCRS);
        }
        MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);
        return JTS.transform(geometry, transform);
    }
}
