package com.ruoyi.ibs.grid.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.ibs.draw.utils.DrawUtils;
import com.ruoyi.ibs.grid.domain.dto.BaiduResponseDTO;
import com.ruoyi.ibs.grid.domain.dto.GeoInfoDTO;
import com.ruoyi.ibs.grid.domain.entity.AdminDivision;
import com.ruoyi.ibs.grid.domain.entity.AdminDivisionFence;
import com.ruoyi.ibs.grid.domain.vo.AddressResultVO;
import com.ruoyi.ibs.grid.domain.vo.RegionMapVO;
import com.ruoyi.ibs.grid.mapper.AdminDivisionFenceMapper;
import com.ruoyi.ibs.grid.mapper.AdminDivisionMapper;
import com.ruoyi.ibs.grid.service.AddressAnalyseService;
import com.ruoyi.ibs.grid.service.BaiduApiService;
import com.ruoyi.ibs.grid.service.SysAddressService;
import lombok.extern.slf4j.Slf4j;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class AddressAnalyseServiceImpl implements AddressAnalyseService {
    private final GeometryFactory geometryFactory = new GeometryFactory();

    @Resource
    private AdminDivisionMapper adminDivisionMapper;

    @Resource
    private AdminDivisionFenceMapper adminDivisionFenceMapper;

    @Resource
    private BaiduApiService baiduApiService;

    @Resource
    private SysAddressService sysAddressService;

    // 地理围栏结构树
    private final List<GeoInfoDTO> geoInfoDTOList = new ArrayList<>();

    // 根据id获取地理围栏
    private final Map<Integer, Geometry> regionFenceMap = new HashMap<>();

    // 根据上级行政编码获取所有下级行政区划
    private final Map<String, List<AdminDivision>> adminDivisionMap = new HashMap<>();

    // WKT转地理围栏类
    private final WKTReader reader = new WKTReader();

    @Override
    public AddressResultVO pointInGeometry(double lng, double lat) {
        Point point = geometryFactory.createPoint(new Coordinate(lng, lat));

        for(GeoInfoDTO geoInfoDTO : geoInfoDTOList){
            if(geoInfoDTO.getGrid().contains(point)){
                return geoInfoDTO.childrenMatching(point);
            }
        }
        AddressResultVO addressResultVO = new AddressResultVO();
        addressResultVO.setStatus(999);
        addressResultVO.setLng(lng);
        addressResultVO.setLat(lat);
        addressResultVO.setMsg("暂无法解析该地址");
        return addressResultVO;
    }

    @Override
    public AddressResultVO addressStandardize(String address){

        BaiduResponseDTO geocoding = baiduApiService.geocoding(address);
        if(geocoding.getStatus() != 0 || Objects.isNull(geocoding.getResult())){
            AddressResultVO addressResultVO = new AddressResultVO();
            addressResultVO.setStatus(1);
            addressResultVO.setMsg(geocoding.getMsg());
            return addressResultVO;
        }
        Point point = geometryFactory.createPoint(new Coordinate(geocoding.getResult().getLocation().getLng(), geocoding.getResult().getLocation().getLat()));
        for(GeoInfoDTO geoInfoDTO : geoInfoDTOList){
            if(geoInfoDTO.getGrid().contains(point)){
                return geoInfoDTO.childrenMatching(point);
            }
        }

        // 不在浙江省内 返回信息
        AddressResultVO addressResultVO = new AddressResultVO();
        addressResultVO.setStatus(999);
        addressResultVO.setLng(point.getX());
        addressResultVO.setLat(point.getY());
        addressResultVO.setMsg("暂无法解析该地址");
        return addressResultVO;
    }

    /**
     * @param code
     * @return
     */
    @Override
    public RegionMapVO getPointListByCode(String code) {
        LambdaQueryWrapper<AdminDivision> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AdminDivision::getCode, code);
        AdminDivision adminDivision = adminDivisionMapper.selectOne(queryWrapper);

        if (!regionFenceMap.containsKey(adminDivision.getId())) {
            throw new ServiceException("暂无该区域地理围栏数据！");
        }
        Geometry geometry = regionFenceMap.get(adminDivision.getId());
        if (Objects.isNull(geometry)) {
            return null;
        }

        RegionMapVO regionMapVO = new RegionMapVO();
        List<List<Coordinate[]>> polygonList = new ArrayList<>();
        int geoCount = geometry.getNumGeometries();
        for (int i = 0; i < geoCount; i++) {
            List<Coordinate[]> pointList = new ArrayList<>();
            Polygon polygon = (Polygon) geometry.getGeometryN(i);
            pointList.add(polygon.getExteriorRing().getCoordinates());
            if (polygon.getNumInteriorRing() > 0) {
                for (int j = 0; j < polygon.getNumInteriorRing(); j++) {
                    pointList.add(polygon.getInteriorRingN(j).getCoordinates());
                }
            }
            polygonList.add(pointList);
        }
        regionMapVO.setPolygonList(polygonList);
        return regionMapVO;
    }

    @Override
    public Geometry getGeometryById(Integer adminId) {
        if (!regionFenceMap.containsKey(adminId)) {
            throw new ServiceException("暂无该区域地理围栏数据！");
        }
        return regionFenceMap.get(adminId);
    }

    @Override
    public Boolean checkWktValid(String wkt) {
        Geometry geometry = DrawUtils.parseWkt(wkt);
        int geoCount = geometry.getNumGeometries();
        for (int i = 0; i < geoCount; i++) {
            Polygon polygon = (Polygon) geometry.getGeometryN(i);
            for (Coordinate coordinate : polygon.getExteriorRing().getCoordinates()) {
                AddressResultVO addressResultVO = pointInGeometry(coordinate.getX(), coordinate.getY());
                if (addressResultVO.getStatus() != 0) {
                    return Boolean.FALSE;
                }
            }
        }
        return Boolean.TRUE;
    }


    @PostConstruct
    public void loadGeometry() throws ParseException {
        log.info("<-------------------Loading geo data...------------------->");
        List<AdminDivisionFence> adminDivisionFences = adminDivisionFenceMapper.selectList(null);
        if(adminDivisionFences.isEmpty()){
            throw new ServiceException("未找到行政区划围栏数据");
        }
        // WKT转换为围栏对象
        for(AdminDivisionFence r: adminDivisionFences){
            Geometry geometry = null;
            if(Objects.nonNull(r.getGrid())){
                geometry = reader.read(r.getGrid());
            }
            regionFenceMap.put(r.getId(), geometry);
        }
        // 获取行政区划结构树
        LambdaQueryWrapper<AdminDivision> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByAsc(AdminDivision::getLevel);
        List<AdminDivision> adminDivisions = adminDivisionMapper.selectList(null);
        for(AdminDivision adminDivision : adminDivisions){
            if(Objects.isNull(adminDivision.getParentCode()) || adminDivision.getParentCode().isEmpty()){
                continue;
            }
            if(!adminDivisionMap.containsKey(adminDivision.getParentCode())){
                adminDivisionMap.put(adminDivision.getParentCode(), new ArrayList<AdminDivision>());
            }
            adminDivisionMap.get(adminDivision.getParentCode()).add(adminDivision);
        }
        // 根据行政区划等级倒序，优先返回五级行政区划
        adminDivisionMap.forEach((s, adminDivisions1) -> {
            adminDivisionMap.put(s, adminDivisions1.stream().sorted(Comparator.comparing(AdminDivision::getLevel).reversed()).collect(Collectors.toList()));
        });
        // 根据结构树载入地理围栏
        geoInfoDTOList.addAll(adminDivisionMap.get("33").stream().map(this::dfs).collect(Collectors.toList()));
        geoInfoDTOList.addAll(adminDivisionMap.get("31").stream().map(this::dfs).collect(Collectors.toList()));
        log.info("<-------------------Loading completed.------------------->");
    }

    private GeoInfoDTO dfs(AdminDivision adminDivision){
        GeoInfoDTO geoInfoDTO = new GeoInfoDTO();
        BeanUtils.copyProperties(adminDivision, geoInfoDTO);
        geoInfoDTO.setGrid(regionFenceMap.get(adminDivision.getId()));

        if(adminDivisionMap.containsKey(adminDivision.getCode())){
            List<GeoInfoDTO> collect = adminDivisionMap.get(adminDivision.getCode()).stream().map(this::dfs).collect(Collectors.toList());
            geoInfoDTO.setChildren(collect);
        }
        return geoInfoDTO;
    }



}
