package cn.edu.whu.boot.common.utils.allocation;

import cn.edu.whu.boot.common.utils.allocation.dtos.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import org.geojson.Feature;
import org.geojson.FeatureCollection;
import org.geojson.LngLatAlt;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 解析 ArcGIS REST API JSON 数据的工具类
 */
public class ArcGisGeoJsonParser {

    private static final Logger log = LoggerFactory.getLogger(ArcGisGeoJsonParser.class);

    private static final Gson gson = new Gson();

    private static final GeometryFactory geometryFactory = new GeometryFactory();

    /**
     * 将 ArcGIS REST API JSON 字符串解析为 org.locationtech.jts.geom.Geometry 对象
     *
     * @param json ArcGIS REST API JSON 字符串
     * @return org.locationtech.jts.geom.Geometry 对象
     */
    public static Geometry toGeometryObject(String json)
    {
        if (json.contains("xmin") && json.contains("ymin") && json.contains("xmax") && json.contains("ymax"))
        {
            // Envelope
            try
            {
                Envelope envelope = gson.fromJson(json , Envelope.class);
                Coordinate[] coordinates = new Coordinate[]{new Coordinate(envelope.getXmin() , envelope.getYmax()) , new Coordinate(envelope.getXmax() , envelope.getYmax()) , new Coordinate(envelope.getXmax() , envelope.getYmin()) , new Coordinate(envelope.getXmin() , envelope.getYmin()) , new Coordinate(envelope.getXmin() , envelope.getYmax())};
                return geometryFactory.createPolygon(coordinates);
            } catch (JsonSyntaxException e)
            {
                log.error("无法解析的数据：{}，数据应为 ArcGIS GeoJSON 格式" , json);
            }
        }
        else if (json.contains("x") && json.contains("y"))
        {
            // Point
            try
            {
                Point point = gson.fromJson(json , Point.class);
                return geometryFactory.createPoint(new Coordinate(point.getX() , point.getY()));
            } catch (JsonSyntaxException e)
            {
                log.error("无法解析的数据：{}，数据应为 ArcGIS GeoJSON 格式" , json);
            }
        }
        else if (json.contains("points"))
        {
            // MultiPoint
            try
            {
                MultiPoint multiPoint = gson.fromJson(json , MultiPoint.class);
                org.locationtech.jts.geom.Point[] points = Arrays.stream(multiPoint.getPoints()).map(coords -> geometryFactory.createPoint(new Coordinate(coords[0] , coords[1]))).toArray(org.locationtech.jts.geom.Point[]::new);
                return geometryFactory.createMultiPoint(points);
            } catch (JsonSyntaxException e)
            {
                log.error("无法解析的数据：{}，数据应为 ArcGIS GeoJSON 格式" , json);
            }
        }
        else if (json.contains("paths"))
        {
            // Polyline
            try
            {
                Polyline polyline = gson.fromJson(json , Polyline.class);
                LineString[] lineStrings = Arrays.stream(polyline.getPaths()).map(path -> {
                    Coordinate[] coordinates = Arrays.stream(path).map(coordinate -> new Coordinate(coordinate[0] , coordinate[1])).toArray(Coordinate[]::new);
                    return geometryFactory.createLineString(coordinates);
                }).toArray(LineString[]::new);
                return geometryFactory.createMultiLineString(lineStrings);
            } catch (JsonSyntaxException e)
            {
                log.error("无法解析的数据：{}，数据应为 ArcGIS GeoJSON 格式" , json);
            }
        }
        else if (json.contains("rings"))
        {
            // Polygon
            try
            {
                Polygon polygon = gson.fromJson(json , Polygon.class);
                org.locationtech.jts.geom.Polygon[] polygons = Arrays.stream(polygon.getRings()).map(ring -> {
                    Coordinate[] shells = Arrays.stream(ring).map(coordinate -> new Coordinate(coordinate[0] , coordinate[1])).toArray(Coordinate[]::new);
                    return geometryFactory.createPolygon(shells);
                }).toArray(org.locationtech.jts.geom.Polygon[]::new);
                return geometryFactory.createMultiPolygon(polygons);
            } catch (JsonSyntaxException e)
            {
                log.error("无法解析的数据：{}，数据应为 ArcGIS GeoJSON 格式" , json);
            }
        }
        return null;
    }

    /**
     * 将 ArcGIS REST API GeoJSON 转换为标准 GeoJSON
     *
     * @param arcgisJson ArcGIS REST API GeoJSON 字符串
     * @param properties 需要添加的属性
     * @return 标准 GeoJSON 字符串
     */
    public static String toStandardGeoJson(String arcgisJson , Map<String, Object> properties)
    {
        ObjectMapper mapper = new ObjectMapper();
        Object result;
        Optional<Map<String, Object>> optionalMap = Optional.ofNullable(properties);
        if (arcgisJson.contains("xmin") && arcgisJson.contains("ymin") && arcgisJson.contains("xmax") && arcgisJson.contains("ymax"))
        {
            // Envelope
            Envelope envelope = gson.fromJson(arcgisJson , Envelope.class);
            Feature feature = new Feature();
            feature.setGeometry(new org.geojson.Polygon(new LngLatAlt(envelope.getXmin() , envelope.getYmax()) , new LngLatAlt(envelope.getXmax() , envelope.getYmax()) , new LngLatAlt(envelope.getXmax() , envelope.getYmin()) , new LngLatAlt(envelope.getXmin() , envelope.getYmin()) , new LngLatAlt(envelope.getXmin() , envelope.getYmax())));
            optionalMap.ifPresent(feature::setProperties);
            result = feature;
        }
        else if (arcgisJson.contains("x") && arcgisJson.contains("y"))
        {
            // Point
            Point point = gson.fromJson(arcgisJson , Point.class);
            Feature feature = new Feature();
            feature.setGeometry(new org.geojson.Point(point.getX() , point.getY()));
            optionalMap.ifPresent(feature::setProperties);
            result = feature;
        }
        else if (arcgisJson.contains("points"))
        {
            // MultiPoint
            MultiPoint multiPoint = gson.fromJson(arcgisJson , MultiPoint.class);
            Feature feature = new Feature();
            feature.setGeometry(new org.geojson.MultiPoint(Arrays.stream(multiPoint.getPoints()).map(coords -> new LngLatAlt(coords[0] , coords[1])).toArray(LngLatAlt[]::new)));
            optionalMap.ifPresent(feature::setProperties);
            result = feature;
        }
        else if (arcgisJson.contains("paths"))
        {
            // Polyline
            Polyline polyline = gson.fromJson(arcgisJson , Polyline.class);
            List<Feature> features = Arrays.stream(polyline.getPaths()).map(path -> new org.geojson.LineString(Arrays.stream(path).map(coordinate -> new LngLatAlt(coordinate[0] , coordinate[1])).toArray(LngLatAlt[]::new))).map(lineString -> {
                Feature feature = new Feature();
                feature.setGeometry(lineString);
                optionalMap.ifPresent(feature::setProperties);
                return feature;
            }).collect(Collectors.toList());
            FeatureCollection featureCollection = new FeatureCollection();
            featureCollection.addAll(features);
            result = featureCollection;
        }
        else if (arcgisJson.contains("rings"))
        {
            // Polygon
            Polygon polygon = gson.fromJson(arcgisJson , Polygon.class);
            List<Feature> features = Arrays.stream(polygon.getRings()).map(ring -> new org.geojson.Polygon(Arrays.stream(ring).map(coordinate -> new LngLatAlt(coordinate[0] , coordinate[1])).toArray(LngLatAlt[]::new))).map(p -> {
                Feature feature = new Feature();
                feature.setGeometry(p);
                optionalMap.ifPresent(feature::setProperties);
                return feature;
            }).collect(Collectors.toList());
            FeatureCollection featureCollection = new FeatureCollection();
            featureCollection.addAll(features);
            result = featureCollection;
        }
        else
        {
            // 不知道是什么几何类型
            return null;
        }
        try
        {
            return mapper.writeValueAsString(result);
        } catch (JsonProcessingException e)
        {
            log.error(e.getMessage());
            return null;
        }
    }
}

