package cn.edu.whu.boot.allocation.services.impl;

import cn.edu.whu.boot.allocation.entities.TaskCell;
import cn.edu.whu.boot.allocation.entities.TaskCellData;
import cn.edu.whu.boot.allocation.mapper.TaskCellDataMapper;
import cn.edu.whu.boot.allocation.services.TaskCellDataService;
import cn.edu.whu.boot.allocation.services.TaskCellService;
import cn.edu.whu.boot.common.utils.allocation.ArcGisGeoJsonParser;
import cn.edu.whu.boot.xml.domain.Task;
import cn.edu.whu.boot.xml.xmlreader.bean.Field;
import cn.edu.whu.boot.xml.xmlreader.bean.Table;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Polygon;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
public class TaskCellDataServiceImpl implements TaskCellDataService {

    @Autowired
    private TaskCellService taskCellService;

    @Autowired
    private TaskCellDataMapper taskCellDataMapper;

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Override
    public Optional<TaskCellData> findById(int id) {
        return Optional.ofNullable(taskCellDataMapper.findById(id));
    }

    @Override
    public List<TaskCellData> findByTaskId(int taskId) {
        return taskCellDataMapper.findByTaskId(taskId);
    }

    @Override
    public List<TaskCellData> findByTaskIdAndGeohash(int taskId, String cellGeohash) {
        return taskCellDataMapper.findByTaskIdAndCellGeohash(taskId, cellGeohash);
    }

    @Override
    public List<TaskCellData> findByTaskIdAndTableNameAndGeohash(int taskId, String tableName, String cellGeohash) {
        return taskCellDataMapper.findByTaskIdAndTableNameAndCellGeohash(taskId, tableName, cellGeohash);
    }

    @Override
    public Optional<TaskCellData> findByTaskIdAndTableNameAndDataId(int taskId, String tableName, long dataId) {
        return Optional.ofNullable(taskCellDataMapper.findByTaskIdAndTableNameAndDataId(taskId, tableName, dataId));
    }

    @Override
    public List<TaskCellData> findByTaskIdAndCellGeohashIn(int taskId, List<String> cellGeohashes) {
        return taskCellDataMapper.findByTaskIdAndCellGeohashIn(taskId, cellGeohashes);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insert(TaskCellData taskCellData) {
        return taskCellDataMapper.insert(taskCellData);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void insertList(List<TaskCellData> taskCellDataList) {
        if (taskCellDataList == null || taskCellDataList.isEmpty()) {
            return;
        }
        if (taskCellDataList.size() * 4 <= 2000) {
            // 参数个数不大于 2000，用 MyBatis foreach 插入
            taskCellDataMapper.insertForeach(taskCellDataList);
        } else {
            try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
                TaskCellDataMapper mapper = session.getMapper(TaskCellDataMapper.class);
                taskCellDataList.forEach(mapper::insert);
                session.commit();
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteByTaskId(int taskId) {
        taskCellDataMapper.deleteByTaskId(taskId);
    }

    @Override
    public List<TaskCellData> findTaskDataInTheSameCell(int taskId, long dataId) {
        return taskCellDataMapper.findInTheSameTaskAndCell(taskId, dataId);
    }

    @Override
    public List<String> findCellGeohashByData(int taskId, String tableName, List<Long> dataIds) {
        return dataIds == null || dataIds.isEmpty() ? Collections.emptyList() : taskCellDataMapper.findCellGeohashByTaskIdAndTableNameAndDataId(taskId, tableName, dataIds);
    }

    private static final GeometryFactory geometryFactory = new GeometryFactory();

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateAfterDataCreatedOrUpdated(Task task, Table table, long dataId) {
        if (task.getIsAllocationOptional() == 1) {
            return;
        }

        Optional<Geometry> optional = table
                .getFields()
                .values()
                .stream()
                .filter(Field::isGeometry)
                .findFirst()
                .map(f -> ArcGisGeoJsonParser.toGeometryObject(new String(Base64.getDecoder().decode(f.getValue().toString()))));
        if (!optional.isPresent()) {
            return;
        }
        Geometry data = optional.get();

        // 获取所有和数据相交的网格
        int taskId = task.getId();
        List<TaskCell> cells = taskCellService.findByTaskId(taskId);
        if (cells.isEmpty()) {
            return;
        }
        String tableName = table.getKey();
        List<TaskCellData> taskCellData = cells
                .stream()
                .map(cell -> {
                    JSONArray jsonArray = JSON.parseObject(cell.getGeojson()).getJSONObject("geometry").getJSONArray("coordinates").getJSONArray(0);
                    Coordinate[] coordinates = new Coordinate[jsonArray.size()];
                    for (int i = 0; i < jsonArray.size(); i++) {
                        JSONArray coord = jsonArray.getJSONArray(i);
                        coordinates[i] = new Coordinate(coord.getDouble(0), coord.getDouble(1));
                    }
                    Polygon polygon = geometryFactory.createPolygon(coordinates);
                    polygon.setUserData(cell.getGeohash());
                    return polygon;
                })
                .filter(cell -> cell.intersects(data))
                .map(cell -> new TaskCellData(taskId, tableName, dataId, (String) cell.getUserData()))
                .collect(Collectors.toList());

        taskCellDataMapper.deleteByTaskIdAndTableNameAndDataId(taskId, tableName, dataId);
        if (!taskCellData.isEmpty()) {
            taskCellDataMapper.insertForeach(taskCellData);
        }
    }

    @Override
    public List<String> findCellsWhereUserIsAbsent(int taskId, String table, Integer reviewerId, List<Long> dataIds) {
        return dataIds == null || dataIds.isEmpty() ? Collections.emptyList() : taskCellDataMapper.findCellsWhereUserIsAbsent(taskId, table, reviewerId, dataIds);
    }
}
