package cn.edu.whu.boot.collection.controller;

import cn.edu.whu.boot.collection.service.ImportService;
import cn.edu.whu.boot.common.domain.CommonConstant;
import cn.edu.whu.boot.common.domain.ResultMessage;
import cn.edu.whu.boot.common.utils.collection.OtherUtil;
import cn.edu.whu.boot.common.utils.collection.TableOperationUtil;
import cn.edu.whu.boot.xml.domain.Task;
import cn.edu.whu.boot.xml.service.ITaskService;
import cn.edu.whu.boot.xml.xmlreader.bean.Config;
import cn.edu.whu.boot.xml.xmlreader.bean.Field;
import cn.edu.whu.boot.xml.xmlreader.bean.Table;
import com.alibaba.fastjson.JSON;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.geotools.data.FileDataStore;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.opengis.feature.type.AttributeDescriptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.*;

/**
 * 上传数据的相关接口!!!
 */
@RestController
@RequestMapping("import")
public class ImportController{

    private final ITaskService taskService;
    private final ImportService importService;

    @Autowired
    public ImportController(ITaskService taskService, ImportService importService){
        this.taskService = taskService;
        this.importService = importService;
    }

    /**
     * 获取任务下的根表信息
     * @param taskId 任务 id
     */
    @RequestMapping(value = "getRootTablesName", produces = "text/html;charset=UTF-8")
    public String getRootTablesName(Integer taskId){
        Task task = taskService.getTaskById(taskId);
        if(task.getHasCreateTables() == 0){
            return ResultMessage.success(null).toString();
        }
        Map<String, Table> tables = JSON.toJavaObject(JSON.parseObject(task.getConfig()), Config.class).getRootTables();
        return ResultMessage.success(genTableNameList(tables)).toString();
    }

    /**
     * 获取任务下的所有表信息
     * @param taskId 任务 id
     */
    @RequestMapping(value = "getTablesName", produces = "text/html;charset=UTF-8")
    public String getAllTablesName(Integer taskId){
        Task task = taskService.getTaskById(taskId);
        if(task.getHasCreateTables() == 0){
            return ResultMessage.success(null).toString();
        }
        Map<String, Table> tables = JSON.toJavaObject(JSON.parseObject(task.getConfig()), Config.class).getTables();
        return ResultMessage.success(genTableNameList(tables)).toString();
    }

    /**
     * 基于传入的 Map<String, Table> tables 将其按照某个规则转换成 List<Map<String, String>>
     */
    private List<Map<String, String>> genTableNameList(Map<String, Table> tables){
        List<Map<String, String>> result = new ArrayList<>();
        if(MapUtils.isNotEmpty(tables)){
            tables.forEach((tableName, table) -> {
                Map<String, String> tempMap = new HashMap<>();
                tempMap.put("tableName", tableName);
                tempMap.put("tableChineseName", table.getName());
                result.add(tempMap);
            });
            return result;
        }
        else{
            return null;
        }
    }

    /**
     * 获取任务下的表字段信息，得把关联表排除掉!!!
     * @param taskId 任务 id
     * @param tableName 表名
     */
    @RequestMapping(value = "getTableFieldsName", produces = "text/html;charset=UTF-8")
    public String getTableFieldsName(Integer taskId, String tableName){
        Task task = taskService.getTaskById(taskId);
        Table table = JSON.toJavaObject(JSON.parseObject(task.getConfig()), Config.class).getTables().get(tableName);
        List<String> fieldNameList = new ArrayList<>();
        Map<String, Field> fields = table.getFields();
        fields.forEach((fieldName, field) -> {
            if((StringUtils.isBlank(field.getType()) || !CommonConstant.CAMERA.equals(field.getType()))
                    && !CommonConstant.FIX_FIELD_SET.contains(fieldName)
                    && StringUtils.isBlank(field.getAssociate())
                    && StringUtils.isBlank(field.getCollection())){
                fieldNameList.add(fieldName);
            }
        });
        return ResultMessage.success(fieldNameList).toString();
    }

    /**
     * 获取源任务和目标任务间，表的映射关系
     * @param srcTaskId      源任务 id
     * @param destTaskId     目标任务 id
     */
    @RequestMapping(value = "getTaskTableMapping", produces = "text/html;charset=UTF-8")
    public String getTaskTableMapping(Integer srcTaskId, Integer destTaskId){
        Task srcTask = taskService.getTaskById(srcTaskId);
        Task destTask = taskService.getTaskById(destTaskId);
        Map<String, Table> srcRootTables = JSON.toJavaObject(JSON.parseObject(srcTask.getConfig()), Config.class).getRootTables();
        Map<String, Table> destRootTables = JSON.toJavaObject(JSON.parseObject(destTask.getConfig()), Config.class).getRootTables();
        Set<String> destRootTablesNameSet = destRootTables.keySet();
        List<Map<String, String>> result = new ArrayList<>();
        srcRootTables.forEach((rootTableName, rootTable) -> {
            if(destRootTablesNameSet.contains(rootTableName)){
                Map<String, String> tempMap = new HashMap<>();
                tempMap.put("srcTable", rootTableName);
                tempMap.put("destTable", rootTableName);
                result.add(tempMap);
            }
            else{
                Map<String, String> tempMap = new HashMap<>();
                tempMap.put("srcTable", rootTableName);
                tempMap.put("destTable", "");
                result.add(tempMap);
            }
        });
        return ResultMessage.success(result).toString();
    }

    /**
     * 获取源任务和目标任务间，源表和目标表的字段映射关系
     * @param srcTaskId      源任务 id
     * @param destTaskId     目标任务 id
     * @param srcTableName    源表名
     * @param destTableName   目标表名
     */
    @RequestMapping(value = "getTaskTableFieldMapping", produces = "text/html;charset=UTF-8")
    public String getTaskTableFieldMapping(Integer srcTaskId, Integer destTaskId, String srcTableName, String destTableName){
        Task srcTask = taskService.getTaskById(srcTaskId);
        Task destTask = taskService.getTaskById(destTaskId);
        Table srcRootTable = JSON.toJavaObject(JSON.parseObject(srcTask.getConfig()), Config.class).getRootTables().get(srcTableName);
        Table destRootTable = JSON.toJavaObject(JSON.parseObject(destTask.getConfig()), Config.class).getRootTables().get(destTableName);
        Map<String, Field> srcRootTableFields = srcRootTable.getFields();
        Map<String, Field> destRootTableFields = destRootTable.getFields();
        Set<String> destRootTableFieldNameSet = destRootTableFields.keySet();
        List<Map<String, String>> result = new ArrayList<>();
        String srcCameraField = TableOperationUtil.getCameraFieldName(srcRootTable);
        srcRootTableFields.forEach((rootTableFieldName, rootTableField) -> {
            if(!CommonConstant.FIX_FIELD_SET.contains(rootTableFieldName)
                    && !rootTableFieldName.equals(srcCameraField)){
                addFieldNameToList(result, destRootTableFieldNameSet, rootTableFieldName);
            }
        });
        return ResultMessage.success(result).toString();
    }


    /**
     * 根据网页端制定好的字段映射信息进行数据迁移
     * @param srcTaskId      源任务 id
     * @param destTaskId     目标任务 id
     * @param srcTableName    源表名
     * @param destTableName   目标表名
     * @param isSelective     是否抽查
     * @param selectDataNum   抽查数目，注意这是个字符串，如果字符串中带有“%”，说明是按照比例进行抽查!!!
     *                         注意 % 只能在最后
     * @param whereClause  按照 where 语句抽查
     * @param fieldMapping    字段映射关系
     */
    @RequestMapping(value = "taskTableDataMigration", produces = "text/html;charset=UTF-8")
    public String taskTableDataMigration(Integer srcTaskId, Integer destTaskId, String srcTableName,
                                         String destTableName, boolean isSelective,
                                         @RequestParam(required = false) String selectDataNum,
                                         @RequestParam(required = false) String whereClause,
                                         String fieldMapping) throws IOException{
        Task srcTask = taskService.getTaskById(srcTaskId);
        Task destTask = taskService.getTaskById(destTaskId);
        if(isSelective){
            // 检查 selectDataNum 是否符合要求
            if(selectDataNum.contains("%")){
                if(selectDataNum.indexOf("%") != selectDataNum.length() - 1){
                    return new ResultMessage<>(true, "抽查数量不符合要求", 400).toString();
                }
                else{
                    return importService.taskTableDataInsert(srcTask, destTask, srcTableName, destTableName, 2,
                            Integer.parseInt(selectDataNum.substring(0, selectDataNum.length() - 1)), whereClause,
                            fieldMapping).toString();
                }
            }
            else{
                if(Integer.parseInt(selectDataNum) <= 0 || Integer.parseInt(selectDataNum) > 9999){
                    return new ResultMessage<>(true, "抽查数量不符合要求", 400).toString();
                }
                else{
                    return importService.taskTableDataInsert(srcTask, destTask, srcTableName, destTableName, 1,
                            Integer.parseInt(selectDataNum), whereClause, fieldMapping).toString();
                }
            }
        }
        else{
            return importService.taskTableDataInsert(srcTask, destTask, srcTableName, destTableName, 0,
                                                     0, null, fieldMapping).toString();
        }
    }

    /**
     * 获取.xlsx 或.xls文件与目标表的字段映射关系
     * @param destTaskId      目标任务 id
     * @param destTableName   目标表名
     * @param file           .xlsx 或.xls文件
     */
    @RequestMapping(value = "getFileAndFieldMapping", produces = "text/html;charset=UTF-8")
    public String getFileAndFieldMapping(Integer destTaskId, String destTableName,
                                         @RequestParam(name = "file") MultipartFile file) throws IOException{
        Task destTask = taskService.getTaskById(destTaskId);
        Table destRootTable = JSON.toJavaObject(JSON.parseObject(destTask.getConfig()), Config.class).getRootTables().get(destTableName);
        Map<String, Field> destRootTableFields = destRootTable.getFields();
        Set<String> destRootTableFieldNameSet = destRootTableFields.keySet();
        String fileName = file.getOriginalFilename();
        List<Map<String, String>> result = new ArrayList<>();
        if(fileName.endsWith(".xls") || fileName.endsWith(".xlsx")){
            Workbook workbook = new XSSFWorkbook(file.getInputStream());
            Sheet sheet = workbook.getSheetAt(0);
            Row firstRow = sheet.getRow(sheet.getFirstRowNum());
            for(int i = 0; i < firstRow.getLastCellNum(); i++){
                String cellValue = firstRow.getCell(i).toString();
                if(!cellValue.toLowerCase().equals(CommonConstant.FILE_PATH)){
                    addFieldNameToList(result, destRootTableFieldNameSet, cellValue);
                }
            }
            return ResultMessage.success(result).toString();
        }
        else{
            return new ResultMessage<>(true, "文件格式不支持", 400).toString();
        }

    }

    /**
     * 将 .xlsx 或 .xls 文件中数据插入数据库中
     * @param destTaskId      目标任务 id
     * @param destTableName   目标表名
     * @param file           .xlsx 或.xls文件
     * @param fieldMapping    字段映射关系
     */
    @RequestMapping(value = "fileDataMigration", produces = "text/html;charset=UTF-8")
    public String fileDataMigration(Integer destTaskId, String destTableName,
                                    @RequestParam(name = "file") MultipartFile file, String fieldMapping) throws IOException{
        Task destTask = taskService.getTaskById(destTaskId);
        String fileName = file.getOriginalFilename();
        if(fileName.endsWith(".xls") || fileName.endsWith(".xlsx")){
            Workbook workbook = new XSSFWorkbook(file.getInputStream());
            Sheet sheet = workbook.getSheetAt(0);
            importService.fileDataInsert(destTask, destTableName, sheet, fieldMapping);
            return ResultMessage.success(null).toString();
        }
        else{
            return new ResultMessage<>(true, "文件格式不支持", 400).toString();
        }
    }


    /**
     * 获取 Shapefile  文件与目标表的字段映射关系!
     * .shp：存储地理数据的坐标信息。
     * .shx：存储地理数据的位置索引，记录每个地理数据在 shp 文件中的位置，能够快速定位数据。
     * .dbf：存储地理数据的属性信息，以 dBase IV 的数据表格式存储。
     *
     * @param destTaskId      目标任务 id
     * @param destTableName   目标表
     * @param shp              shp文件
     * @param shx              shx 文件
     * @param dbf              dbf 文件
     */
    @RequestMapping(value = "getShpAndFieldMapping", produces = "text/html;charset=UTF-8")
    public String getShpAndFieldMapping(Integer destTaskId, String destTableName,
                                        @RequestParam(name = "shp") MultipartFile shp,
                                        @RequestParam(name = "shx") MultipartFile shx,
                                        @RequestParam(name = "dbf") MultipartFile dbf) throws IOException{
        if(shp.getOriginalFilename().endsWith(".shp")
                && shx.getOriginalFilename().endsWith(".shx")
                && dbf.getOriginalFilename().endsWith(".dbf")){
            String tempDirPath = CommonConstant.TEMP_FILE_PATHNAME + "/" + System.currentTimeMillis() + "/";
            File tempDir = new File(tempDirPath);
            tempDir.mkdirs();
            File shpFile = new File(tempDirPath + "temp.shp");
            shp.transferTo(shpFile);
            File shxFile = new File(tempDirPath +  "temp.shx");
            shx.transferTo(shxFile);
            File dbfFile = new File(tempDirPath +  "temp.dbf");
            dbf.transferTo(dbfFile);

            ShapefileDataStoreFactory factory = new ShapefileDataStoreFactory();
            ShapefileDataStore dataStore = (ShapefileDataStore) factory.createDataStore(shpFile.toURI().toURL());
            List<Map<String, String>> result = new ArrayList<>();
            Task destTask = taskService.getTaskById(destTaskId);
            Table destRootTable = JSON.toJavaObject(JSON.parseObject(destTask.getConfig()), Config.class).getRootTables().get(destTableName);
            Map<String, Field> destRootTableFields = destRootTable.getFields();
            Set<String> destRootTableFieldNameSet = destRootTableFields.keySet();
            try{
                if(dataStore != null){
                    // 确定 ShapeFile 编码
                    String shapeFileCoding = OtherUtil.getShpFileCodingByDbf(dbfFile);
                    if(shapeFileCoding == null){
                        throw new RuntimeException(".dbf文件出错，请检查该文件!");
                    }
                    dataStore.setCharset(Charset.forName(shapeFileCoding));
                    List<AttributeDescriptor> attrList = dataStore.getFeatureSource().getSchema().getAttributeDescriptors();
                    for(AttributeDescriptor attr : attrList){
                        if(!attr.getLocalName().toLowerCase().equals(CommonConstant.FILE_PATH)){
                            addFieldNameToList(result, destRootTableFieldNameSet, attr.getLocalName());
                        }
                    }
                }
            } catch(Exception e){
                e.printStackTrace();
            } finally{
                if(dataStore != null){
                    dataStore.dispose();
                }
                // 将临时文件夹删除
                if(tempDir.exists()){
                    FileUtils.deleteDirectory(tempDir);
                }
            }
            return ResultMessage.success(result).toString();
        }
        else{
            return new ResultMessage<>(true, "文件格式不支持", 400).toString();
        }

    }

    /**
     * 将 Shapefile  文件中数据插入数据库中!
     * @param destTaskId      目标任务 id
     * @param destTableName   目标表
     * @param fieldMapping    字段映射关系
     * @param shp              shp文件
     * @param shx              shx 文件
     * @param dbf              dbf 文件
     */
    @RequestMapping(value = "shpDataMigration", produces = "text/html;charset=UTF-8")
    public String shpDataMigration(Integer destTaskId, String destTableName,String fieldMapping,
                                   @RequestParam(name = "shp") MultipartFile shp,
                                   @RequestParam(name = "shx") MultipartFile shx,
                                   @RequestParam(name = "dbf") MultipartFile dbf) throws IOException{
        if(shp.getOriginalFilename().endsWith(".shp")
                && shx.getOriginalFilename().endsWith(".shx")
                && dbf.getOriginalFilename().endsWith(".dbf")){
            String tempDirPath = CommonConstant.TEMP_FILE_PATHNAME + "/" + System.currentTimeMillis() + "/";
            File tempDir = new File(tempDirPath);
            tempDir.mkdirs();
            File shpFile = new File(tempDirPath + "temp.shp");
            shp.transferTo(shpFile);
            File shxFile = new File(tempDirPath +  "temp.shx");
            shx.transferTo(shxFile);
            File dbfFile = new File(tempDirPath +  "temp.dbf");
            dbf.transferTo(dbfFile);

            //通过store获取featureCollection
            FileDataStore store = FileDataStoreFinder.getDataStore(shpFile);
            // 确定 ShapeFile 编码
            String shapeFileCoding = OtherUtil.getShpFileCodingByDbf(dbfFile);
            if(shapeFileCoding == null){
                throw new RuntimeException(".dbf文件出错，请检查该文件!");
            }
            ((ShapefileDataStore) store).setCharset(Charset.forName(shapeFileCoding));
            SimpleFeatureSource featureSource = store.getFeatureSource();
            SimpleFeatureCollection simpleFeatureCollection = featureSource.getFeatures();
            SimpleFeatureIterator itertor = simpleFeatureCollection.features();
            Task destTask = taskService.getTaskById(destTaskId);
            try{
                importService.shpDataInsert(destTask, destTableName, itertor, fieldMapping,
                                            getMediaFilePathNameFromShp(shpFile));
            }
            catch(Exception e){
                e.printStackTrace();
                return ResultMessage.failed(e.getMessage()).toString();
            }
            finally{
                // 将 SimpleFeatureIterator 和ShapefileDataStore关闭，否则后面删除文件夹会提示shp被占用
                itertor.close();
                store.dispose();

                // 将临时文件夹删除
                if(tempDir.exists()){
                    FileUtils.deleteDirectory(tempDir);
                }
            }
            return ResultMessage.success(null).toString();
        }
        else{
            return new ResultMessage<>(true, "文件格式不支持", 400).toString();
        }
    }

    /**
     * 根据源字段与目标字段生成 List<Map<String,String>>
     */
    private void addFieldNameToList(List<Map<String, String>> result, Set<String> nameSet, String name){
        if(nameSet.contains(name)){
            Map<String, String> tempMap = new HashMap<>();
            tempMap.put("srcTableField", name);
            tempMap.put("destTableField", name);
            result.add(tempMap);
        }
        else{
            Map<String, String> tempMap = new HashMap<>();
            tempMap.put("srcTableField", name);
            tempMap.put("destTableField", "");
            result.add(tempMap);
        }
    }

    /**
     * 从 shp 中获取 filePath 字段名(如果存在的话)
     */
    private String getMediaFilePathNameFromShp(File shpFile) throws IOException{
        ShapefileDataStoreFactory factory = new ShapefileDataStoreFactory();
        ShapefileDataStore dataStore = (ShapefileDataStore) factory.createDataStore(shpFile.toURI().toURL());
        if(dataStore != null){
            List<AttributeDescriptor> attrList = dataStore.getFeatureSource().getSchema().getAttributeDescriptors();
            for(AttributeDescriptor attr : attrList){
                if(attr.getLocalName().toLowerCase().equals(CommonConstant.FILE_PATH)){
                    return attr.getLocalName();
                }
            }
        }
        return null;
    }
}
