package com.imdroid.sideslope.calc;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.imdroid.secapi.dto.*;
import com.imdroid.sideslope.sal.Device;
import com.imdroid.sideslope.sal.DeviceService;
import com.imdroid.common.util.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class GNSSCalcFilterService {
    private final Logger logger = LoggerFactory.getLogger(GNSSCalcFilterService.class);

    @Resource(name = "local")
    DeviceService gnssDeviceRepository;
    @Autowired
    private GnssGroupCalcMapper groupCalcMapper;
    @Autowired
    private GnssCalcDataMapper repository;

    public static final int FILTER_MIN_CYCLE_HOUR = 2;
    public static final int FILTER_DEFAULT_CYCLE_HOUR = 8;
    static final int FILTER_MIN_RECORD_NUM = 10;
    static final float XY_THRESHOLD = 30; //水平异常点30mm
    static final float Z_THRESHOLD = 30; //高程异常点30mm
    static final float AUTO_THRESHOLD = 50; //触发自适应滤波的门限50mm

    static class VaryFilterCycle{
        public LocalDateTime startTime;
        public int filterCycleHour;
    }
    final private ConcurrentHashMap<String, VaryFilterCycle> autoCycleDevices = new ConcurrentHashMap<>();

    final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public void calc(GnssCalcData locationRecord, boolean isExceed)  {
        String deviceId = locationRecord.getDeviceid();
        Device gnssDevice = gnssDeviceRepository.findByDeviceId(deviceId);
        if(gnssDevice == null) return;

        //补充解算记录的设备信息
        locationRecord.setTenantid(gnssDevice.getTenantId());
        locationRecord.setEnabled(true);

        // 获取平滑参数
        GnssGroupCalc groupCalc = getCalcParams(gnssDevice.getCalcGroupId());

        // 计算平滑周期
        int filterCycle = groupCalc.getFilter_hour();
        VaryFilterCycle varyCycle = autoCycleDevices.get(deviceId);
        if(varyCycle!=null){
            filterCycle = varyCycle.filterCycleHour;
        }

        // 平滑处理
        calcFilterLocation(locationRecord, filterCycle, groupCalc.getFilter_min_hour(),
                groupCalc.getXy_threshold(), groupCalc.getZ_threshold(), groupCalc.getAdv_filter());

        repository.insert(locationRecord);

        // 更新平滑周期
        if(groupCalc.getAuto_filter()) {
            refreshFilterCycle(deviceId, groupCalc, locationRecord);
        }

    }

    GnssGroupCalc getCalcParams(int calcGroupId){
        GnssGroupCalc calcParam = groupCalcMapper.selectById(calcGroupId);
        if(calcParam == null){
            calcParam = new GnssGroupCalc();
            calcParam.setAuto_filter(false);
            calcParam.setFilter_hour(FILTER_DEFAULT_CYCLE_HOUR);
            calcParam.setFilter_min_hour(FILTER_MIN_CYCLE_HOUR);
            calcParam.setXy_threshold(XY_THRESHOLD);
            calcParam.setZ_threshold(Z_THRESHOLD);
            calcParam.setAuto_threshold(AUTO_THRESHOLD);
        }
        return calcParam;
    }

    /**
     * 计算东北天的最近融合数据的加权平均, 刘畅20230725 copy 自 avgEND; id>20, 滤波参数6h 或 25h
     */
    public boolean calcFilterLocation(GnssCalcData newRecord, int filterCycleHour, int minCycleHour,
                                      float xyThreshold, float zThreshold, boolean isAdvFilter){
        String deviceId = newRecord.getDeviceid();
        // 选取[newRecordTime-filterCycleHour, newRcordTime]之间的记录做平滑
        // 如果这个时间段的记录数少于FILTER_MIN_RECORD_NUM，本次不做平滑
        LocalDateTime newRecordTime = newRecord.getCreatetime();
        LocalDateTime filterAfterTime = newRecordTime.minusHours(filterCycleHour);
        LocalDateTime minCycleTime = newRecordTime.minusHours(minCycleHour);
        QueryWrapper<GnssCalcData> query = new QueryWrapper<>();
        query.eq("deviceid", deviceId);
        query.ge("createtime", filterAfterTime.format(dateFormatter));
        query.le("createtime", newRecordTime.format(dateFormatter));
        //query.eq("enabled", true);// 坏点参与比较，不参与滤波
        query.orderByDesc("createtime");

        List<GnssCalcData> gnssDeviceLocationRecords = repository.selectList(query);
        logger.info(deviceId + " select records from "+filterAfterTime.format(dateFormatter)+
                " to "+newRecordTime.format(dateFormatter)+
                ", record num:"+gnssDeviceLocationRecords.size());

        if(gnssDeviceLocationRecords.size() == 0){
            //第一个点无参考，当作坏点处理
            newRecord.setEnabled(false);
        }
        else {
            GnssCalcData record0 = gnssDeviceLocationRecords.get(0);
            // 检查是不是坏点
            if (Math.abs(newRecord.getB562e() - record0.getB562e()) > xyThreshold ||
                    Math.abs(newRecord.getB562n() - record0.getB562n()) > xyThreshold ||
                    Math.abs(newRecord.getB562d() - record0.getB562d()) > zThreshold) {
                newRecord.setEnabled(false); //记录为坏点，下次不参与滤波
                logger.info(deviceId + " abnormal gnss data");
            }
            else if(isAdvFilter){
                GnssCalcData record1 = null;
                if(gnssDeviceLocationRecords.size()>1) record1 = gnssDeviceLocationRecords.get(1);
                if(record1 == null ||
                        Math.abs(newRecord.getB562e() - record1.getB562e()) > xyThreshold*2 ||
                        Math.abs(newRecord.getB562n() - record1.getB562n()) > xyThreshold*2 ||
                        Math.abs(newRecord.getB562d() - record1.getB562d()) > zThreshold*2) {
                    newRecord.setEnabled(false); //记录为坏点，下次不参与滤波
                    logger.info(deviceId + " abnormal gnss data by adv filter");
                }
            }

            if(newRecord.getEnabled()) {
                // 求本组和最近recordNum组原始值的平均值
                double sumE = newRecord.getB562e();
                double sumN = newRecord.getB562n();
                double sumD = newRecord.getB562d();
                double msumE = sumE;
                double msumN = sumN;
                double msumD = sumD;
                int count = 1;
                int minCount = 1;

                for (GnssCalcData record : gnssDeviceLocationRecords) {
                    if (record.getEnabled()) {//只选取好点参与滤波
                        sumE += record.getB562e();
                        sumN += record.getB562n();
                        sumD += record.getB562d();
                        count++;
                        // 计算短周期
                        if(record.getCreatetime().isAfter(minCycleTime)){
                            msumE = sumE;
                            msumN = sumN;
                            msumD = sumD;
                            minCount = count;
                        }
                    }
                }
                logger.info(deviceId + " filter records num: " + count+", min cycle num: "+minCount);
                if (count >= FILTER_MIN_RECORD_NUM) {
                    newRecord.setRpose(NumberUtils.scaleTwo(sumE / count));
                    newRecord.setRposn(NumberUtils.scaleTwo(sumN / count));
                    newRecord.setRposd(NumberUtils.scaleTwo(sumD / count));
                    newRecord.setAuxe(NumberUtils.scaleTwo(msumE / minCount));
                    newRecord.setAuxn(NumberUtils.scaleTwo(msumN / minCount));
                    newRecord.setAuxd(NumberUtils.scaleTwo(msumD / minCount));
                    return true;
                }
            }
        }

        return false;
    }

    void refreshFilterCycle(String deviceId, GnssGroupCalc groupCalc, GnssCalcData curCalcData){
        LocalDateTime now = LocalDateTime.now();
        // 比较当前均值和FilterMinHour前的均值之差是否超过门限
        LocalDateTime cmpTime = now.minusHours(groupCalc.getFilter_min_hour());
        QueryWrapper<GnssCalcData> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("deviceid", deviceId);
        queryWrapper.le("createtime",cmpTime.format(dateFormatter));
        queryWrapper.eq("enabled",true);
        queryWrapper.isNotNull("rpose");
        queryWrapper.last("limit 1");
        queryWrapper.orderByDesc("createtime");
        GnssCalcData gnssCalcData = repository.selectOne(queryWrapper);
        if (gnssCalcData != null) {
            double gapE = Math.abs(gnssCalcData.getAuxe() - curCalcData.getAuxe());
            double gapN = Math.abs(gnssCalcData.getAuxn() - curCalcData.getAuxn());
            if (gapE >= groupCalc.getAuto_threshold() || gapN >= groupCalc.getAuto_threshold()) {
                VaryFilterCycle varyCycle = new VaryFilterCycle();
                varyCycle.startTime = now;
                varyCycle.filterCycleHour = groupCalc.getFilter_min_hour();
                autoCycleDevices.put(deviceId, varyCycle);
                logger.info(deviceId + ": filter cycle change to " + varyCycle.filterCycleHour);
            }
            else {
                VaryFilterCycle varyCycle = autoCycleDevices.get(deviceId);
                if(varyCycle != null){
                    if(varyCycle.filterCycleHour >= groupCalc.getFilter_hour()){
                        //变周期结束
                        autoCycleDevices.remove(deviceId);
                    }
                    else {
                        if (now.isAfter(varyCycle.startTime.plusHours(1))) {
                            varyCycle.filterCycleHour++;
                            varyCycle.startTime = now;
                            logger.info(deviceId + ": refresh filter cycle "+varyCycle.filterCycleHour);
                        }
                    }
                }
            }
        }
    }

    public LocalDateTime updateRpos(String deviceId, LocalDateTime afterTime){
        // 获取平滑参数
        Device gnssDevice = gnssDeviceRepository.findByDeviceId(deviceId);
        if(gnssDevice == null) return afterTime;
        GnssGroupCalc groupCalc = getCalcParams(gnssDevice.getCalcGroupId());

        // 平滑处理
        LocalDateTime beforTime = afterTime.plusHours(groupCalc.getFilter_hour());
        QueryWrapper<GnssCalcData> query = new QueryWrapper<>();
        query.eq("deviceid", deviceId);
        query.le("createtime", beforTime.format(dateFormatter));
        query.ge("createtime", afterTime.format(dateFormatter));
        //query.eq("enabled",true); //好点坏点重新判断
        query.orderByAsc("createtime");

        LocalDateTime lastTime = afterTime;

        List<GnssCalcData> calcDataListToUpdate = repository.selectList(query);
        for(GnssCalcData calcData:calcDataListToUpdate){
            calcFilterLocation(calcData, groupCalc.getFilter_hour(), groupCalc.getFilter_min_hour(),
                    groupCalc.getXy_threshold(), groupCalc.getZ_threshold(), groupCalc.getAdv_filter());
            repository.updateById(calcData);
            lastTime = calcData.getCreatetime();
            logger.info(deviceId + " update rpos");
        }
        return lastTime;
    }
}
