package net.vtstar.zhongtong.avi.monitoring.job;

import lombok.extern.slf4j.Slf4j;
import net.vtstar.protocol.modbus.tcp.netty.utils.NettyUtils;
import net.vtstar.protocol.modbus.tcp.netty.utils.PoolKey;
import net.vtstar.scada.base.equipmgt.domain.*;
import net.vtstar.scada.base.equipmgt.service.EquipInfoService;
import net.vtstar.scada.base.gather.job.GatherJob;
import net.vtstar.scada.base.global.service.ModbusService;
import net.vtstar.utils.CollecUtils;
import net.vtstar.zhongtong.avi.equipment.domain.PaintshopStation;
import net.vtstar.zhongtong.avi.global.constant.Constant;
import net.vtstar.zhongtong.avi.monitoring.domain.Skid;
import net.vtstar.zhongtong.avi.monitoring.domain.Vehicle;
import net.vtstar.zhongtong.avi.monitoring.domain.bo.MQTTMessageBO;
import net.vtstar.zhongtong.avi.monitoring.domain.enums.Direction;
import net.vtstar.zhongtong.avi.monitoring.domain.enums.StationState;
import net.vtstar.zhongtong.avi.monitoring.service.StationPassInfoService;
import net.vtstar.zhongtong.avi.mqtt.handle.RfidMessageHandler;
import net.wimpi.modbus.io.ModbusTCPTransaction;
import net.wimpi.modbus.msg.*;
import net.wimpi.modbus.net.TCPMasterConnection;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.net.InetAddress;
import java.util.*;

@Slf4j
@Component
public class AVIMonitorJob extends GatherJob {

    private static final String EQUIPMENT_TYPE = "PLC";
    public static MQTTMessageBO messageBO = new MQTTMessageBO();

    @Autowired
    private EquipInfoService equipInfoService;
    @Autowired
    private ModbusService modbusService;
    @Autowired
    private StationPassInfoService passInfoService;
    @Resource(name = "vehicleCache")
    private Cache vehicleCache;
    @Resource(name = "laneStationCache")
    private Cache laneStationCache;
    @Resource(name = "redisTemplate")
    private ValueOperations valueOperations;
    @Resource(name = "redisTemplate")
    private RedisTemplate redisTemplate;
    @Resource(name = "laneRfidStationCache")
    private Cache laneRfidStationCache;

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {

        log.info("-------------------------------");
        log.info("AVIMonitorJob start");

        List<EquipInfo> equipInfoList = equipInfoService.getEquipInfoList(EQUIPMENT_TYPE);
        if (CollectionUtils.isEmpty(equipInfoList)) {
            return;
        }
        //处理平移车正反转用
        for (EquipInfo equipInfo : equipInfoList) {

            List<EquipCommunication> commList = equipInfoService.getCommunicationListByEquipType(equipInfo.getEquipCode(), 1);
            if (CollectionUtils.isEmpty(commList)) {
                return;
            }
            for (EquipCommunication communication : commList) {
                EquipDataChannel dataChannel = communication.getDataChannel();
                if (null != dataChannel.getEquipCode() && !dataChannel.getEquipCode().startsWith(EQUIPMENT_TYPE)) {
                    continue;
                }
                String ip = dataChannel.getIp();
                Integer port = dataChannel.getPort();
                PoolKey poolKey = NettyUtils.wrapPoolKey(ip, port);
                Integer slaveId = dataChannel.getNo();
                EquipDataTemplate template = communication.getDataTemplate();
                if (template == null) {
                    log.info("无法获取设备的数据模板，设备编号：{}", dataChannel.getEquipCode());
                    continue;
                }
                List<EquipDataTemplateItem> templateItemList = template.getTemplateItemList();
                if (CollectionUtils.isEmpty(templateItemList)) {
//                    log.info("无法获取数据模板的条目，设备编号：{},模板名称：{}", dataChannel.getEquipCode(), template.getName());
                    continue;
                }

                Map<Integer, List<EquipDataTemplateItem>> listMap = modbusService.groupByFuntionCode(templateItemList);
                for (Map.Entry<Integer, List<EquipDataTemplateItem>> entry : listMap.entrySet()) {
                    List<EquipDataTemplateItem> value = entry.getValue();
                    if (CollectionUtils.isEmpty(value)) {
                        continue;
                    }
                    Integer start = value.get(0).getAddress();
                    Integer end = value.get(value.size() - 1).getAddress();
                    Integer size = value.get(value.size() - 1).getLength();
                    Integer quantity = end + size - start;

                    Integer functionCode = entry.getKey();
                    ModbusRequest modbusRequest = null;
                    net.wimpi.modbus.msg.ModbusResponse response = null;
                    if (1 == functionCode) {
                        modbusRequest = new ReadCoilsRequest(start, quantity);
                    } else if (2 == functionCode) {
                        modbusRequest = new ReadInputDiscretesRequest(start, quantity);
                    } else if (3 == functionCode) {
                        modbusRequest = new ReadMultipleRegistersRequest(start, quantity);
                    } else if (4 == functionCode) {
                        modbusRequest = new ReadInputRegistersRequest(start, quantity);
                    }
                    modbusRequest.setUnitID(slaveId);
                    try {
                        TCPMasterConnection connection = new TCPMasterConnection(InetAddress.getByName(poolKey.getHost()));
                        connection.setPort(port);
                        connection.setTimeout(3000);
                        connection.connect();
                        ModbusTCPTransaction transaction = new ModbusTCPTransaction(connection);
                        transaction.setReconnecting(false);
                        transaction.setRequest(modbusRequest);
                        transaction.execute();
                        response = transaction.getResponse();
                    } catch (Exception e) {
                        log.error("PLC连接异常, PLC地址: {}, 端口:{}", poolKey.getHost(), poolKey.getPort());
                        continue;
                    }
                    if (null == response) {
                        log.error("读取失败！ response 为空！");
                        return;
                    }
                    String[] hexStrs = response.getHexMessage().split(" ");
                    if (hexStrs.length == 9) {
                        log.error("读取失败！ length 为9！");
                        return;
                    }
                    StringBuilder sb = new StringBuilder();
                    for (int i = 9; i < hexStrs.length; i++) {
                        Integer newValue = Integer.valueOf(hexStrs[i], 16);
                        int j = 0;
                        while (j < 8){
                            sb.append(newValue % 2);
                            newValue = newValue / 2;
                            j ++;
                        }
                    }
                    byte[] bytes = sb.toString().getBytes();

                    /*log.error("读取读取平移车正反转数据。ip{}， 端口{}, slave{}, start{}, size{}", poolKey.getHost(), poolKey.getPort(), slaveId, start, quantity);
                    log.error("data:{}", Arrays.toString(hexStrs));*/

                    for (EquipDataTemplateItem templateItem : value) {
                        Integer address = templateItem.getAddress();
                        int quality = address - start;
                        byte state = bytes[quality];

                        String fieldName = templateItem.getFieldName();
                        if (fieldName.startsWith("vehicle:turn")) {
                            String[] split = fieldName.split(":");
                            String vehicleCode = fieldName.split(":")[2];

                            Vehicle vehicle = (Vehicle) valueOperations.get(Constant.VEHICLE_PREFIX + vehicleCode);
                            if (null == vehicle) {
                                vehicle = vehicleCache.get(vehicleCode, Vehicle.class);
                            }
                            if (state == 48) {
                                state = 0;
                            } else {
                                state = 1;
                            }
                            String turn = split[1];
                            if (turn.endsWith("F")) {
                                vehicle.setTurnF(state == 1);
                            }
                            if (turn.endsWith("R")) {
                                vehicle.setTurnR(state == 1);
                            }
                            valueOperations.set(Constant.VEHICLE_PREFIX + vehicleCode, vehicle);
                        }
                    }
                }
            }
        }

        List<Vehicle> vehicles = new ArrayList<>();
        Set<String> veKeys = redisTemplate.keys(Constant.VEHICLE_PREFIX + "*");
        for (String veKey : veKeys) {
            Vehicle ve = (Vehicle) valueOperations.get(veKey);
            if (null == ve) {
                ve = vehicleCache.get(veKey.split(":")[1], Vehicle.class);
            }
            if (null == ve) {
                log.error("平移车:{} 未在到缓存中!", veKey);
                continue;
            }
            //判断平移车是否有下车信号
            if (null == ve.getState() || ve.getState() == true) {
                continue;
            }
            //判断当前平移车是否在转动
            if (null !=  ve.getTurnF() && ve.getTurnF()) {
                log.error("----------平移车:{}的正转信号为true!--------------", ve.getMqttPrefix());
                //判断下车时间和当前旋转时间是否大于10s
                if (((System.currentTimeMillis() - ve.getDownTime().getTime()) / 1000) > 10 && ve.getDownTimeMin() > 5) {
                    vehicles.add(ve);
                } else {
                    ve.setDownTimeMin(ve.getDownTimeMin() + 2);
                    valueOperations.set(veKey, ve);
                }
            }
            if (null !=  ve.getTurnR() && ve.getTurnR()) {
                log.error("----------平移车:{}的反转信号为true!--------------", ve.getMqttPrefix());
                //判断下车时间和当前旋转时间是否大于10s
                if (((System.currentTimeMillis() - ve.getDownTime().getTime()) / 1000) > 10 && ve.getDownTimeMin() > 5) {
                    vehicles.add(ve);
                } else {
                    ve.setDownTimeMin(ve.getDownTimeMin() + 2);
                    valueOperations.set(veKey, ve);
                }
            }
        }

        if (CollecUtils.isEmpty(vehicles)){
            return;
        }

        for (Vehicle vehicle : vehicles) {
            log.error("--------------开始下车逻辑------------------");
            if (null == vehicle.getLaneRfid()){
                log.error("----下车平移车没有当前车道信息!LaneRfid = null!");
                continue;
            }
            List<PaintshopStation> stations = laneRfidStationCache.get(vehicle.getLaneRfid(), ArrayList.class);
            if (CollecUtils.isEmpty(stations)) {
                log.error("----下车逻辑1-----");
                return;
            }
            for (PaintshopStation station : stations) {
                //若平移车正转, 且工位在平移车西侧; 或者平移车反转, 且工位在平移车东侧
                if ((vehicle.getTurnF() && Direction.WEST.equals(station.getDirection())) || (vehicle.getTurnR() && Direction.EAST.equals(station.getDirection()))) {
                    log.error("----下车逻辑2");
                    Skid skid = vehicle.getSkid();
                    //TODO: 若平移车上车时找不到对应的滑撬,  则skid为空, 下车时不做处理
                    if (null == skid) {
                        log.error("平移车上车时找不到对应的滑撬: {}", vehicle.getMqttPrefix());
                        continue;
                    }
                    log.error("平移车: {} 下车到工位:{}", vehicle.getMqttPrefix(), station.getCode());
                    vehicle.setSkid(null);
                    vehicle.setCurrentRfid(null);
                    vehicle.setState(null);
                    vehicle.setDownTime(null);
                    vehicle.setDownTimeMin(0);
                    vehicle.setMqttCode(null);
                    //更新平移车
                    valueOperations.set(Constant.VEHICLE_PREFIX + vehicle.getMqttPrefix(), vehicle);
                    log.error("-------------重置平移车------------------");
                    //todo: 若平移车位于HXG2-1位置, 且平移车反转(向东), 则认为是向底盘下车操作, 判断工序补扫情况
                    if ("439".equals(vehicle.getLaneRfid()) && vehicle.getTurnR()){
                        passInfoService.checkPassInfo(skid.getWorkOrderNo(), skid.getBusNo(), skid.getRfid());
                        skid.setBusNo("");
                        skid.setWorkOrderNo("");
                        valueOperations.set(Constant.SKID_PREFIX + skid.getRfid(), skid);
                        continue;
                    }
                    Long laneId = station.getLaneId();
                    List<PaintshopStation> lands = new ArrayList<>();
                    List<PaintshopStation> temStation = laneStationCache.get(laneId, ArrayList.class);
                    for (PaintshopStation land : temStation) {
                        PaintshopStation s = (PaintshopStation) valueOperations.get(Constant.STATION_PREFIX + land.getCode());
                        lands.add(s);
                    }

                    lands.sort(Comparator.comparing(v -> v.getSequence()));

                    PaintshopStation start = lands.get(0);
                    PaintshopStation end = lands.get(lands.size() - 1);
                    //下车时, 记录进去车道的开始时间, 并将记录ID绑定在工位上
                    Long passInfoId = 0L;
                    passInfoId = passInfoService.createPassInfo(skid.getWorkOrderNo(), skid.getBusNo(), skid.getRfid(), station.getAreaName(), station.getCode());
                    if (start.getCode().equals(station.getCode())) {
                        //车道西头下车, 所有工位向东平移一位
                        for (int j = lands.size() - 1; j > 0; j--) {
                            PaintshopStation tem = lands.get(j);
                            PaintshopStation tem2 = lands.get(j - 1);
                            tem.setSkid(tem2.getSkid());
                            tem.setState(tem2.getState());
                            tem.setBusType(tem2.getBusType());
                            tem.setWorkOrderNo(tem2.getWorkOrderNo());
                            tem.setBusNo(tem2.getBusNo());
                        }
                        start.setSkid(skid);
                        start.setState(StationState.USE);
                        start.setInTime(new Date());
                        if (passInfoId != 0L) {
                            start.setPassInfoId(passInfoId);
                        }
                    } else if (end.getCode().equals(station.getCode())) {
                        for (int j = 0; j < lands.size() - 1; j++) {
                            PaintshopStation tem = lands.get(j);
                            PaintshopStation tem2 = lands.get(j + 1);
                            tem.setSkid(tem2.getSkid());
                            tem.setState(tem2.getState());
                            tem.setBusType(tem2.getBusType());
                            tem.setWorkOrderNo(tem2.getWorkOrderNo());
                            tem.setBusNo(tem2.getBusNo());
                        }
                        end.setState(StationState.USE);
                        end.setSkid(skid);
                        end.setInTime(new Date());
                        if (passInfoId != 0L) {
                            end.setPassInfoId(passInfoId);
                        }
                    }

                    String today = RfidMessageHandler.dateFormat.format(new Date());
                    //记录过站信息
                    String areaCode = station.getAreaId().toString();
                    String areaKey = Constant.BUS_SIZE_IN + areaCode + ":" + today;
                    Integer size = (Integer) valueOperations.get(areaKey);
                    if (null == size) {
                        size = 1;
                    } else {
                        size++;
                    }
                    valueOperations.set(areaKey, size);
                    saveRfidInfo(areaKey, skid.getRfid());
                    //更新工位
                    for (PaintshopStation land : lands) {
                        valueOperations.set(Constant.STATION_PREFIX + land.getCode(), land);
                    }
                }
            }
            log.error("-------------下车逻辑结束------------------");
        }
    }

    private void saveRfidInfo(String areaKey, String rfid){
        messageBO.setID(rfid);
        messageBO.setState(0);
        messageBO.setCreateDate(RfidMessageHandler.format.format(new Date()));
        listOperations.rightPush("rfid:" + areaKey, messageBO);
    }
}