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.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.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.DisallowConcurrentExecution;
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.HashOperations;
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.*;
import java.util.stream.Collectors;

@Slf4j
@Component
@DisallowConcurrentExecution
public class AVIMonitorJob extends GatherJob {

    private static final String EQUIPMENT_TYPE = "PLC";

    @Autowired
    private EquipInfoService equipInfoService;
    @Autowired
    private ModbusService modbusService;
    @Autowired
    private StationPassInfoService passInfoService;

    @Resource(name = "stationCache")
    private Cache stationCache;
    @Resource(name = "vehicleCache")
    private Cache vehicleCache;
    @Resource(name = "laneStationCache")
    private Cache laneStationCache;
    @Resource(name = "redisTemplate")
    protected HashOperations<String, String, PaintshopStation> hashOperations;
    @Resource(name = "redisTemplate")
    private ValueOperations valueOperations;
    @Resource(name = "redisTemplate")
    private RedisTemplate redisTemplate;

    @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;
        }
        //处理工位正反转用
        List<PaintshopStation> stations = new ArrayList<>();
        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();
                    /*ModbusResponse response = null;
                    try {
                        if (1 == functionCode) {
                            response = modbusService.readCoilRegister(slaveId, poolKey, start, quantity);
                        } else if (2 == functionCode) {
                            response = modbusService.readInputDiscretes(slaveId, poolKey, start, quantity);
                        } else if (3 == functionCode) {
                            response = modbusService.readMultipleRegister(slaveId, poolKey, start, quantity);
                        } else if (4 == functionCode) {
                            response = modbusService.readInputRegister(slaveId, poolKey, start, quantity);
                        }
                    } catch (Exception e) {
                        log.error("读取失败。ip{}， 端口{}, slave{}, start{}, size{}", poolKey.getHost(), poolKey.getPort(), slaveId, start, quantity);
                        return;
                    }*/
                    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) {
                        e.printStackTrace();
                    }
                    if (null == response) {
                        log.error("读取失败！ response 为空！");
                        return;
                    }
                    /*if (!response.isSuccess()){
                        log.error("读取失败！ response isSuccess 为 false！");
                        return;
                    }
                    ByteBuf body = response.getBody();
                    if (null == body) {
                        log.error("response.body is null!!");
                        //若出现一次读取错误, 则将本次读取结束, 必须保证读取到所有的工位旋转情况
                        return;
                    }
                    int length = body.readableBytes();
                    byte[] bytes = new byte[length];
                    body.getBytes(body.readerIndex(), bytes);*/
                    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);
                        for (int j = 7;j >= 0; j--){
                            sb.append(newValue >>> j & 1);
                        }
                    }
                    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;
                        int register = quality / 8;
                        int offset = quality % 8;
                        byte state = bytes[register + offset];

                        String fieldName = templateItem.getFieldName();
                        if (fieldName.startsWith("station:ve")) {
                            String stationCode = fieldName.split(":")[2];
                            PaintshopStation station = (PaintshopStation) valueOperations.get(Constant.STATION_PREFIX + stationCode);
                            if (null == station) {
                                station = stationCache.get(stationCode, PaintshopStation.class);
                            }
                            if (state == 48) {
                                state = 0;
                            } else {
                                state = 1;
                            }
                            station.setOldState(station.getState());
                            station.setArrive(state == 1 ? true : false);
                            stations.add(station);
                            valueOperations.set(Constant.STATION_PREFIX + stationCode, station);
                        } else if (fieldName.startsWith("station:turn")) {
                            String[] split = fieldName.split(":");
                            String stationCode = fieldName.split(":")[2];

                            PaintshopStation station = (PaintshopStation) valueOperations.get(Constant.STATION_PREFIX + stationCode);
                            if (null == station) {
                                station = stationCache.get(stationCode, PaintshopStation.class);
                            }
                            if (state == 48) {
                                state = 0;
                            } else {
                                state = 1;
                            }
                            station.setOldState(station.getState());
                            String turn = split[1];
                            if (turn.endsWith("F")) {
                                station.setTurnF(state == 1 ? true : false);
                            }
                            if (turn.endsWith("R")) {
                                station.setTurnR(state == 1 ? true : false);
                            }
                            valueOperations.set(Constant.STATION_PREFIX + stationCode, station);
                        }
                    }
                }
            }
        }

        if (CollectionUtils.isEmpty(stations) || stations.size() != 144) {
            log.error("没有维护工位信息!");
            return;
        }

        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() == false){
                vehicles.add(ve);
            }
        }

        Map<String, List<Vehicle>> veMap = vehicles.stream().collect(Collectors.groupingBy(Vehicle::getLane));

        Map<String, List<PaintshopStation>> listMap =  stations.stream().filter(a -> false != a.getTurnR() || false != a.getTurnF()).
                collect(Collectors.groupingBy(PaintshopStation::getVehicleLaneCode));
        for (Map.Entry<String, List<Vehicle>> ve : veMap.entrySet()) {
            String key = ve.getKey();
            if (Integer.valueOf(key) > 4){
                continue;
            }
            List<PaintshopStation> landStations = listMap.get(key);
            if (null == landStations){
                landStations = new ArrayList<>();
            }
            List<Vehicle> value = ve.getValue();
            List<PaintshopStation> turnStation = new ArrayList<>();
            for (PaintshopStation landStation : landStations) {
                //获得 西侧加正转=下车 西+反=上 东+反=下 东+正=上
                if (landStation.getTurnF() && Direction.WEST.equals(landStation.getDirection())) {
                    turnStation.add(landStation);
                }
                if (landStation.getTurnR() && Direction.EAST.equals(landStation.getDirection())) {
                    turnStation.add(landStation);
                }
            }
            int stationSize = 0;

            Map<String, PaintshopStation> entries = hashOperations.entries(Constant.STATION_OUT_PREFIX + key);
            for (PaintshopStation station : turnStation) {
                //若当前工位已进车的方式旋转, 且之前一分钟内没有进过车, 则此工位可再次进车
                if (station.getInTime() == null || ((System.currentTimeMillis() - station.getInTime().getTime()) > 60 * 1000)){
                    entries.put(station.getCode(), station);
                }
            }

            if (!CollectionUtils.isEmpty(entries)) {
                stationSize = entries.size();
            }
            //TODO: 临时校验错误, 每条平移车道存在其旋转工位数, 若超过此值, 则按照当前下车数进行依次下车, 多余旋转工位舍弃;
            int maxStationSize = 0;
            switch (key){
                case "1":
                    maxStationSize = 1;
                    break;
                case "2":
                    maxStationSize = 4;
                    break;
                case "3":
                    maxStationSize = 3;
                    break;
                case "4":
                    maxStationSize = 7;
                    break;
            }
            //若平移车下车信号与与工位下车信号不等，存入工位信息返回
            if (value.size() != stationSize && stationSize <= maxStationSize) {
                hashOperations.putAll(Constant.STATION_OUT_PREFIX + key, entries);
                log.error("当前车道: {}旋转的工位数量为: {}", key, stationSize);
            } else {
                log.error("--------------开始下车逻辑------------------");
                value.sort(Comparator.comparing(v -> v.getSequence()));
                List<PaintshopStation> collect = entries.values().stream().sorted(Comparator.comparing(e -> e.getVehicleLaneCode())).collect(Collectors.toList());
                for (int i = 0; i < value.size(); i++) {

                    //找到滑撬上绑定平移车，找到工位对应车道上所有工位信息
                    Vehicle vehicle = value.get(i);
                    PaintshopStation station = collect.get(i);
                    Skid skid = vehicle.getSkid();
                    //TODO: 若平移车上车时找不到对应的滑撬,  则skid为空, 下车时不做处理
                    if (null == skid){
                        log.error("平移车上车时找不到对应的滑撬: {}", vehicle.getMqttPrefix());
                        continue;
                    }
                    log.error("平移车: {} 下车到工位:{}", vehicle.getMqttPrefix(), station.getCode());
                    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;
                    if (null != skid){
                        passInfoId = passInfoService.createPassInfo(skid.getWorkOrderNo(), skid.getRfid(), station.getAreaName(), station.getLandNo());
                    }
                    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);
                            if (tem.getSkid() != null) {
                                tem2.setSkid(tem.getSkid());
                                tem2.setState(StationState.USE);
                            } else {
                                tem2.setSkid(null);
                                tem2.setState(StationState.FREE);
                            }
                        }
                        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);
                            if (null != tem2.getSkid()) {
                                tem.setState(StationState.USE);
                                tem.setSkid(tem2.getSkid());
                            } else {
                                tem2.setSkid(null);
                                tem2.setState(StationState.FREE);
                            }
                        }
                        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);
                    //更新工位
                    for (PaintshopStation land : lands) {
                        valueOperations.set(Constant.STATION_PREFIX + land.getCode(), land);
                    }
                    vehicle.setSkid(null);
                    vehicle.setCurrentRfid(null);
                    vehicle.setState(null);
                    //更新平移车
                    valueOperations.set(Constant.VEHICLE_PREFIX + vehicle.getCode().replace("ZT/translation/", "").replace("/data/all", "").trim(), vehicle);
                }
                for (PaintshopStation station : collect){
                    //清除工位缓存
                    hashOperations.delete(Constant.STATION_OUT_PREFIX + key, station.getCode());
                }
                log.error("-------------下车逻辑结束------------------");
            }
        }
    }
}