package com.base.track.thread;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.base.api.model.SatelliteAndInstanceRpcDTO;
import com.base.api.model.YxInfoBasisInstanceRpcDTO;
import com.base.api.model.YxInfoBasisSatelliteRpcDTO;


import com.base.orbit.log.ServerNameEnum;
import com.base.orbit.utils.FileUtil;
import com.base.orbit.utils.IdGenerator;
import com.base.orbit.utils.StringUtil;
import com.base.orbit.utils.TimeUtil;
import com.base.track.config.sharding.ShardingAlgorithmTool;
import com.base.track.config.sharding.enums.ShardingTableCacheEnum;
import com.base.track.mapper.EphDataMapper;
import com.base.track.model.dto.request.QueryEphListDTO;
import com.base.track.model.entity.EphData;
import com.base.track.model.entity.SubPointData;
import com.base.track.service.EphDataService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.*;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Service
@Slf4j
@EnableAsync
public class EphThread {

    @Resource
    EphDataMapper ephDataMapper;
    @Resource
    EphDataService ephDataService;

    @Async("ephTaskExecutor")
    public void run(String filePath, String dir, SatelliteAndInstanceRpcDTO satelliteAndInstance, String startTime, String endTime) {
        // 卫星代号
        String code = satelliteAndInstance.getYxInfoBasisSatelliteDTO().getSatelliteCode();

        try {
            log.info("卫星" + code + "开始星历计算", ServerNameEnum.TRACK_SERVER);
            // 创建文件并写入信息
            boolean isFileWrite = writeDependFile(startTime, endTime, satelliteAndInstance, filePath, dir);
            // 判断文件是否创建成功
            if (isFileWrite) {
                // 文件写入成功，开始执行exe文件
                runExeFile(code, filePath, dir, startTime, false);
            } else {
                // 文件写入失败
                log.error("卫星" + code + "写入星历计算条件文件失败", ServerNameEnum.TRACK_SERVER);
            }
            // 判断卫星是否为12或14
            if (code.contains("QWERT12") || code.contains("QWERT14")) {
                // 创建J2000文件并写入信息
                boolean isJ2000FileWrite = writeJ2000DependFile(filePath, dir, startTime, code);
                // 判断文件是否创建成功
                if (isJ2000FileWrite) {
                    // 文件写入成功，开始执行exe文件
                    runExeFile(code, filePath, dir, startTime, true);
                } else {
                    // 文件写入失败
                    log.error("卫星" + code + "写入J2000星历计算条件文件失败", ServerNameEnum.TRACK_SERVER);
                }
            }

            log.info("卫星" + code + "结束星历计算", ServerNameEnum.TRACK_SERVER);
        } catch (Exception e) {
            log.error("卫星" + code + "星历计算错误：" + e.getMessage(), ServerNameEnum.TRACK_SERVER);
        }
    }

    /**
     * 生成计算依赖文件
     *
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @return boolean
     */
    private boolean writeDependFile(String startTime, String endTime, SatelliteAndInstanceRpcDTO satelliteAndInstance, String filePath, String dir) throws IOException {
        // 卫星基本信息
        YxInfoBasisSatelliteRpcDTO infoBasisSatelliteDO = satelliteAndInstance.getYxInfoBasisSatelliteDTO();
        // 轨道根数信息
        YxInfoBasisInstanceRpcDTO infoBasisInstanceDO = satelliteAndInstance.getYxInfoBasisInstanceDTO();

        StringBuilder sb = new StringBuilder();
        // 卫星代号 卫星质量 迎风面积 光面面积 地球重力场模型阶数1 地球重力场模型阶数2
        sb.append("1 ").append(infoBasisSatelliteDO.getSatelliteCode()).append(" ").append(infoBasisSatelliteDO.getMass()).append(" ").append(infoBasisSatelliteDO.getWindArea()).append(" ").append(infoBasisSatelliteDO.getShineArea()).append(" ").append(infoBasisSatelliteDO.getEarthGravityFieldModeOrde1() != null ? infoBasisSatelliteDO.getEarthGravityFieldModeOrde1() : "32.0").append(" ").append(infoBasisSatelliteDO.getEarthGravityFieldModeOrde2() != null ? infoBasisSatelliteDO.getEarthGravityFieldModeOrde2() : "32.0").append("\n");
        // 预报开始时间 预报结束时间 预报数据密度
        sb.append("2 ").append(startTime).append(" ").append(endTime).append(" 1.0\n");
        // 地面站经度 地面站纬度 地面站高度 天线最小跟踪仰角（度）默认5度 天线最小接收仰角（度）默认3度 日凌角（度）默认1度
        sb.append("3 0.0 0.0 0.0 5.0 3.0 1.0\n");
        // 星历   类型
        sb.append("4 1\n");
        // 星历文件目录
        sb.append("5 ").append(dir).append("\n");
        // 星历预报开始时间 星历预报结束时间 积分器类型 积分器步长
        sb.append("6 ").append(startTime).append(" ").append(endTime).append(" 1.0 1.0\n");
        // 输入轨道数据类型 输出轨道数据类型
        sb.append("7 1 1\n");
        // 轨道圈号 输入的历元时刻 输出的历元时刻
        sb.append("8 ").append(infoBasisInstanceDO.getOrbitNum()).append(" ").append(TimeUtil.format(new Date(infoBasisInstanceDO.getEpochTime().getTime() - 8 * 3600 * 1000))).append(" ").append(TimeUtil.format(new Date(infoBasisInstanceDO.getEpochTime().getTime() - 8 * 3600 * 1000))).append("\n");
        // 半长轴（m） 偏心率 轨道倾角（度） 升交点赤经（度） 近地点俯角（度） 平近点角（度）
        sb.append("9 ").append(infoBasisInstanceDO.getSemiMajorAxis()).append(" ").append(infoBasisInstanceDO.getEccentricity()).append(" ").append(infoBasisInstanceDO.getInclination()).append(" ").append(infoBasisInstanceDO.getRaan()).append(" ").append(infoBasisInstanceDO.getArgumentOfPerigee()).append(" ").append(infoBasisInstanceDO.getMeanAnoMaly()).append("\n");
        // GPS数据
        sb.append("10 0 0 0 0 0 0\n");
        // 两行数据
        sb.append("11 1 0 0 0 0 0 0 0 0\n");
        // 两行数据
        sb.append("12 2 0 0 0 0 0 0 0 0");

        File file = new File(filePath + Thread.currentThread().getName().split("-")[2] + "/orbdata/eph.inp");
        // 初始数据
        boolean isDelete = true;
        // 判断该文件是否存在
        if (file.exists()) {
            // 删除该文件
            isDelete = file.delete();
        }

        /**
         * 创建新的inp文件并将构建好的信息写入文件
         */
        if (isDelete) {
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true)));
            bufferedWriter.write(sb.toString());
            bufferedWriter.flush();
            bufferedWriter.close();
        } else {
            return false;
        }

        return true;
    }

    /**
     * 卫星为12或14，生成J2000依赖文件
     *
     * @param filePath  依赖文件存放的地址
     * @param dir       结果文件存放的地址
     * @param startTime 计算开始时间
     * @param code      卫星代号
     * @return
     * @throws IOException
     */
    private boolean writeJ2000DependFile(String filePath, String dir, String startTime, String code) throws IOException {
        // 开始时间根据-分割
        String[] split = startTime.split("-");
        // 拼接星历文件名称
        String ephFileName = code + "_" + split[0] + "_" + split[1] + ".eph";
        // 拼接J2000星历文件名称
        String ephJ2000FileName = code + "_" + split[0] + "_" + split[1] + "ToJ2000.dat";

        // 初始数据
        StringBuilder sb = new StringBuilder();
        //
        sb.append("FileECF       ").append(dir + "/" + ephFileName + "\n");
        //
        sb.append("FileJ2000     ").append(dir + "/" + ephJ2000FileName);
        // J2000以来文件存放地址
        File file = new File(filePath + Thread.currentThread().getName().split("-")[2] + "/orbdata/ephConv.inp");
        // 初始数据
        boolean isDelete = true;
        // 判断该文件是否存在
        if (file.exists()) {
            // 删除该文件
            isDelete = file.delete();
        }

        /**
         * 创建新的inp文件并将构建好的信息写入文件
         */
        if (isDelete) {
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true)));
            bufferedWriter.write(sb.toString());
            bufferedWriter.flush();
            bufferedWriter.close();
        } else {
            return false;
        }
        return true;
    }

    /**
     * 写完依赖文件之后运行执行exe程序
     *
     * @param code      卫星代号
     * @param filePath  可执行文件地址
     * @param dir       inp文件写入的地址
     * @param startTime 计算开始时间
     * @param isToJ2000
     * @throws Exception
     */
    private void runExeFile(String code, String filePath, String dir, String startTime, boolean isToJ2000) throws Exception {
        // 分割开始时间
        String[] split = startTime.split("-");
        // 分割线程名称，获取用的调用那个目录下的exe
        String port = Thread.currentThread().getName().split("-")[2];

        // 星历结果文件地址
        String ephFileName = code + "_" + split[0] + "_" + split[1] + ".eph";
        // ok文件地址
        String okFileName = filePath + port + "/orbdata/eph.ok";
        // exe文件名称
        String exeFileName = "eph.exe";

        // 判断卫星是否为12或14
        if (isToJ2000) {
            // J2000星历文件地址
            ephFileName = code + "_" + split[0] + "_" + split[1] + "ToJ2000.dat";
            // J2000ok文件地址
            okFileName = filePath + port + "/orbdata/ephConv.ok";
            // J2000exe文件名称
            exeFileName = "ECFJ2000.exe";
        }

        // 结果路径文件
        File file = new File(dir + "/" + ephFileName);
        // ok文件
        File okFile = new File(okFileName);
        // 初始数据
        boolean isOkFileDelete = true;
        // 文件是否存在
        if (okFile.exists()) {
            // 删除该文件
            isOkFileDelete = okFile.delete();
        }
        // 文件是否删除成功
        if (isOkFileDelete) {
            try {
                // 调用可执行程序
                Process exec = Runtime.getRuntime().exec(filePath + port + "/bin/" + exeFileName, null, new File(filePath + port + "/bin/"));
                // 判断调用可执行文件是否成功
                if (FileUtil.checkFileStatus(okFile, "卫星" + code + "星历计算", log)) {
                    // 关闭可执行程序
                    exec.destroy();

                    // 判断如果为12或14，并且不是J2000，则不做任何处理
                    if ((code.contains("QWERT12") || code.contains("QWERT14")) && !isToJ2000) {
                        return;
                    }

                    FileInputStream fileInputStream = new FileInputStream(file);
                    InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
                    BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

                    //存库
                    saveEphToDataBase(bufferedReader, code);

                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    /**
     * 异步存库
     *
     * @param bufferedReader1
     */
    public synchronized void saveEphToDataBase(BufferedReader bufferedReader1, String code) throws IOException, ParseException {
        BufferedReader bufferedReader = new BufferedReader(bufferedReader1);
        List<EphData> list = new ArrayList<>();
        String result = null;
        code = StringUtil.change(code);
        while ((result = bufferedReader.readLine()) != null) {
            //  根据空格进行截取
            String[] str = result.split("\\s+");
            EphData rpcDTO = new EphData();
            rpcDTO.setId(IdGenerator.getId());
            String time = str[2].replace(".000", "");
            rpcDTO.setEphTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(str[1] + " " + time));
            Date newdate = new Date(rpcDTO.getEphTime().getTime());
            rpcDTO.setEphTime(newdate);
            rpcDTO.setX(BigDecimal.valueOf(Double.parseDouble(str[3])));
            rpcDTO.setY(BigDecimal.valueOf(Double.parseDouble(str[4])));
            rpcDTO.setZ(BigDecimal.valueOf(Double.parseDouble(str[5])));
            rpcDTO.setVX(BigDecimal.valueOf(Double.parseDouble(str[6])));
            rpcDTO.setVY(BigDecimal.valueOf(Double.parseDouble(str[7])));
            rpcDTO.setVZ(BigDecimal.valueOf(Double.parseDouble(str[8])));
            rpcDTO.setCirCle(str[9]);
            rpcDTO.setCode(code);
            list.add(rpcDTO);
        }
        //先删除
        if (ShardingAlgorithmTool.getShardingTable(ShardingTableCacheEnum.BASEEPHDATA, "base_eph_data_" + code)) {
            // 删除表中该时间内的信息
            Date startTime = list.get(0).getEphTime();
            Date endTime = list.get(list.size() - 1).getEphTime();
            QueryEphListDTO queryEphListDTO = new QueryEphListDTO();
            queryEphListDTO.setStartTime(startTime);
            queryEphListDTO.setEndTime(endTime);
            queryEphListDTO.setCode(code);
            ephDataMapper.delData(queryEphListDTO);
        }
        ephDataService.saveBatch(list);
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveAndDel(List<EphData> list, String code) {
        //先删除
        Date startTime = list.get(0).getEphTime();
        Date endTime = list.get(list.size() - 1).getEphTime();
        QueryEphListDTO queryEphListDTO = new QueryEphListDTO();
        queryEphListDTO.setStartTime(startTime);
        queryEphListDTO.setEndTime(endTime);
        queryEphListDTO.setCode(code);
        ephDataMapper.delData(queryEphListDTO);
        //再添加
        ephDataMapper.batch(list, code);
        //清除缓存
        list.clear();
    }
}
