package com.tsi.comm.server.vo;

import com.tsi.app.common.cpu.dto.TsiCvimDto;
import com.tsi.app.common.utils.Counter;
import com.tsi.comm.server.protocol.TsiCpuPacket;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Getter
@Setter
public class TsiNodeVo {
    private long nodeId;
    private String ipAddr;
    private boolean sendTest;
    private boolean sendNode;
    private boolean sendCvim;
    private boolean addNodeEnabled;

    private Channel channel;

    private boolean registered;         // Database 등록 여부, DB에 등록안되고 통신으로 접속하는 경우 false
    private boolean installed;
    private boolean checkInstalled;

    private Counter connectCount    = new Counter();
    private Counter disconnectCount = new Counter();
    private Counter recvPackets = new Counter();
    private Counter sendPackets = new Counter();

    private TsiCvimDto tsiCvimDto;

    TsiNodeAddVo tsiNodeAddVo;

    protected long connectTm;
    protected long disconnectTm;
    protected long lastCommTm;

    protected long recvTime;
    protected long handleTime;
    protected long queueTime;
    protected long endTime;

    private byte[] rawBytes = new byte[1];
    private byte[] nodePacket = new byte[1];

    private boolean dump;
    private int idx;

    public TsiNodeVo(long nodeId, String ipAddr, boolean sendTest, boolean sendNode, boolean sendCvim) {
        this.nodeId = nodeId;
        this.ipAddr = ipAddr;
        this.sendTest = sendTest;
        this.sendNode = sendNode;
        this.sendCvim = sendCvim;
        this.addNodeEnabled = false;

        this.channel = null;
        this.installed = true;
        this.checkInstalled = true;

        this.connectTm = 0;
        this.disconnectTm = 0;
        this.lastCommTm = 0;
        this.dump = false;
        this.idx = 0;
    }

    public void initPacket() {
        this.tsiCvimDto = new TsiCvimDto();
        this.tsiCvimDto.setNodeId(this.nodeId);
    }

    public boolean isConnect() {
        return (this.channel != null);
    }

    public void setConnect(Channel channel) {
        this.channel = channel;
        if (channel != null) {
            this.connectCount.increment();
            this.connectTm = System.currentTimeMillis();
        }
        else {
            this.disconnectCount.increment();
            this.disconnectTm = System.currentTimeMillis();
        }
    }

    public String getKey() {
        return String.valueOf(this.nodeId);
    }

    public byte[] getTestPacket() {
        return null;//this.packet;
    }
    public byte[] getCvimPacket() {
        return null;//this.packet;
    }

//    public boolean parsing(byte[] packet) {
//
//        boolean result = true;
//        //long start = System.nanoTime();
//
//        // 0 단계. STX1, STX2 체크
//        if (packet[TsiCpuPacket.INDEX_STX1] != TsiCpuPacket.STX1 || packet[TsiCpuPacket.INDEX_STX2] != TsiCpuPacket.STX2) {
//            log.info("Node: {}, STX Error: {}, {}", nodeId, packet[TsiCpuPacket.INDEX_STX1], packet[TsiCpuPacket.INDEX_STX2]);
//            return false;
//        }
//        //log.info("Node: {}, a: {}", nodeId, TimeUtils.elapsedTime(start));
//        //start = System.nanoTime();
//
//        // 1 단계. 패킷 길이 체크
//        int dataLength = ByteUtils.getUnsignedShort(packet, TsiCpuPacket.INDEX_LENGTH);
//        int statusCount = (int)(packet[TsiCpuPacket.INDEX_STATUS_HDR+3] & 0x7F);
//        if (dataLength != ( TsiCpuPacket.SIZE_PACKET_DATA + (TsiCpuPacket.SIZE_STATUS_DATA * statusCount) ) ) {
//            log.info("Node: {}, Length Error: {}, status count: {}, {}", nodeId, dataLength, statusCount, TsiCpuPacket.SIZE_PACKET_DATA + (TsiCpuPacket.SIZE_STATUS_DATA * statusCount));
//            return false;
//        }
//        //log.info("Node: {}, b: {}", nodeId, TimeUtils.elapsedTime(start));
//        //start = System.nanoTime();
//
//        // 2단계. 체크섬
//        int recvCheckSum = ByteUtils.getUnsignedShort(packet, packet.length-2);
//        int calcCheckSum = CRC16Utils.CRC16_ccitt_cvim(packet, TsiCpuPacket.INDEX_LENGTH, dataLength-2);
//        if (recvCheckSum != calcCheckSum) {
//            log.info("Node: {}, Check Sum Error: recv: {}, calc: {}", nodeId, recvCheckSum, calcCheckSum);
//            return false;
//        }
//        //log.info("Node: {}, c: {}", nodeId, TimeUtils.elapsedTime(start));
//        //start = System.nanoTime();
//
//        initPacket();
//        //log.info("Node: {}, 1: {}", nodeId, TimeUtils.elapsedTime(start));
//        //start = System.nanoTime();
//
//        if (this.addNodeEnabled && this.tsiNodeAddVo != null) {
//            // 연등지 교차로가 존재하는 경우
//            for (Map.Entry<Integer, TsiNodeAddDetailVo> obj : this.tsiNodeAddVo.getAddNodeMap().entrySet()) {
//                if (obj.getValue().getNodeObj() != null) {
//                    obj.getValue().getNodeObj().initPacket();
//                }
//            }
//        }
//        //log.info("Node: {}, 2: {}", nodeId, TimeUtils.elapsedTime(start));
//        //start = System.nanoTime();
//
//        // CVIB Protocol Format(CPU-->CENTER)
//        //TYPE    STX1    STX2    LEN                 OPCODE  DataVer NodeID  DATA    CHKSUM
//        //Size    Byte    Byte    2Byte               Byte    1Byte   4Byte   NByte   2Byte
//        //Value   0x7E    0x7E    Size(LEN…CHKSUM)    0x13    0x01            …      CRC16
//        //- LEN   : LEN∼CHKSUM data length
//        //        - OPCODE: 0x13, 신호상태정보전송
//        //        - NodeID: Node ID
//        //- DATA  : Opcode에 따른 데이터 영역
//        //- CRC16 : CCITT / ITU / CRC - 16, bits shift right, final little(x) big(o) endian encoding.from LEN to DATA
//        ByteBuffer buffer = ByteBuffer.wrap(packet);
//
//        TsiCvimControl tscControlInfo = tsiCvimDto.getTscControlInfo();
//        TsiCvimAbnormal tscAbnormalInfo = tsiCvimDto.getTscAbnormalInfo();
//
//        tsiCvimDto.setNodeId(this.nodeId);
//        tsiCvimDto.setConnect(true);
//        tsiCvimDto.setCollectTime(SysUtils.getSysTimeStr());
//
//        //log.info("Node: {}, 3: {}", nodeId, TimeUtils.elapsedTime(start));
//        //start = System.nanoTime();
//
//        int stx1 = buffer.get();
//        int stx2 = buffer.get();
//        int length = (int)buffer.getShort() & 0xFFFF;
//        int opcode = buffer.get();
//        int version = buffer.get();
//        long recvNodeId = (long)(buffer.getInt() & 0xFFFFFFFFL);
//
//        byte control = buffer.get();
//        tscControlInfo.inManualControl     = ((control     ) & 0x01) == 0x01;  //수동
//        tscControlInfo.inFlashingControl   = ((control >> 1) & 0x01) == 0x01;  //점멸
//        tscControlInfo.inLightsOutControl  = ((control >> 2) & 0x01) == 0x01;  //소등
//        tscControlInfo.inActuationControl  = ((control >> 3) & 0x01) == 0x01;  //감응
//        tscControlInfo.inTransitionControl = ((control >> 4) & 0x01) == 0x01;  //전이
//
//        byte abnormal = buffer.get();
//        tscAbnormalInfo.inSignalConflict = ((abnormal     ) & 0x01) == 0x01;   //모순상태
//        tscAbnormalInfo.inCenterComm     = ((abnormal >> 1) & 0x01) == 0x01;   //센터상태
//        tscAbnormalInfo.inScuComm        = ((abnormal >> 2) & 0x01) == 0x01;   //SCU 상태
//
//        tsiCvimDto.setCycleElapsedTime((int)(buffer.get() & 0xFF));
//
//        byte stts = buffer.get();
//        tsiCvimDto.setSignalStatusInfoCount((int)(stts & 0x7F));
//        //int divFlag = (int)((stts >> 7) & 0x01);
//
//        long currTime = ((long)buffer.getInt() & 0xFFFFFFFFL);
//        //log.info("Node: {}, 4: {}", nodeId, TimeUtils.elapsedTime(start));
//        //start = System.nanoTime();
//
//        for (int idx = 0; idx < tsiCvimDto.getSignalStatusInfoCount(); idx++) {
//
//            byte dirInfo         = buffer.get();
//            byte statusInfo      = buffer.get();
//            int totalSeconds     = buffer.get();
//            int remainingSeconds = buffer.get();
//            int directionCode    = buffer.get();
//
//            TsiCvimStatus status = new TsiCvimStatus();
//
//            int dirAdd =                          (int)((dirInfo     ) & 0x0F);           // 3 ~ 0, 방향추가정보, 해당 방향에 연등지 없음(0), 해당 방향의 첫번째 연등지(1), 해당 방향의 두번째 연등지(2)
//            status.setLightsType(eLightsType.getByValue((dirInfo >> 4) & 0x0F));          // 신호등정보 [직진,좌,보행]
//
//            status.setLightsStatus(eLightsStatus.getByValue(      (statusInfo     ) & 0x07));          //신호등상태
//            status.setTimeReliability(eTimeReliability.getByValue((statusInfo >> 7) & 0x01));          //시간정보신뢰성
//            status.setReadyPedestrianSignal((                     (statusInfo >> 6) & 0x01) == 0x01);  //보행자
//            status.setUnProtectedSignal((                         (statusInfo >> 3) & 0x01) == 0x01);  //비보호 상태
//
//            status.setTotalSeconds    (totalSeconds);       //표출시간
//            status.setRemainingSeconds(remainingSeconds);   //잔여시간
//            status.setDirectionCode   (directionCode);      //방향코드
//
//            if (dirAdd == 0) {
//                tsiCvimDto.getSignalStatusInfos().add(status);
//            }
//            else {
//                if (this.tsiNodeAddVo != null) {
//                    TsiNodeAddDetailVo detailVo = this.tsiNodeAddVo.getAddNodeMap().get(directionCode * 1000 + dirAdd);
//                    if (detailVo != null) {
//                        for (int dirIdx = 0; dirIdx < 2; dirIdx++) {
//                            if (detailVo.getAddDirCode()[dirIdx] != 0x00) {
//                                if (dirIdx == 0) {
//                                    status.setDirectionCode((int) detailVo.getAddDirCode()[dirIdx]);
//                                    detailVo.getNodeObj().getTsiCvimDto().getSignalStatusInfos().add(status);
//                                }
//                                else {
//                                    TsiCvimStatus statusTmp = new TsiCvimStatus();
//                                    status.copy(statusTmp);
//                                    statusTmp.setDirectionCode((int) detailVo.getAddDirCode()[dirIdx]);
//                                    detailVo.getNodeObj().getTsiCvimDto().getSignalStatusInfos().add(statusTmp);
//                                }
//                            }
//                        }
//                    }
//                }
//            }
//        }
//
//        //log.info("Node: {}, 5: {}", nodeId, TimeUtils.elapsedTime(start));
//        //start = System.nanoTime();
//
//        Date date = new java.util.Date(currTime * 1000L);
//        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//        sdf.setTimeZone(java.util.TimeZone.getTimeZone("GMT+9"));
//        tsiCvimDto.setTscDateTime(sdf.format(date));
//
//        if (this.addNodeEnabled && this.tsiNodeAddVo != null) {
//            makePacket((byte) stx1, (byte) stx2, (byte) opcode, (byte) version, tsiCvimDto, currTime);
//            for (Map.Entry<Integer, TsiNodeAddDetailVo> obj : this.tsiNodeAddVo.getAddNodeMap().entrySet()) {
//                if (obj.getValue().getNodeObj() != null) {
//                    obj.getValue().getNodeObj().makePacket((byte) stx1, (byte) stx2, (byte) opcode, (byte) version, tsiCvimDto, currTime);
//                }
//            }
//        }
//        else {
//            //this.tsiNodeVo.setPacket(this.rawBytes);
//            makeNodePacket(length);
//        }
//        //log.info("Node: {}, 6: {}", nodeId, TimeUtils.elapsedTime(start));
//        //start = System.nanoTime();
//
//        return result;
//    }

    public void makeNodePacket(int length) {
        this.nodePacket = null;
        if (length > TsiCpuPacket.INDEX_NODE_ID) {
            // STX1, STX2, LEN(2), OpCode(1), Version(1), NodeId
            // LEN = LEN ~ CHECK SUM
            int packetLen = length-2-1-1-2;
            this.nodePacket = new byte[packetLen];
            try {
                //System.arraycopy(this.packet, 6, this.nodePacket, 0, packetLen);
            }
            catch(Exception e) {
            }
        }
        else {
            log.warn("makeNodePacket: The length must be at least {} bytes: {}, {}", TsiCpuPacket.INDEX_NODE_ID, this.nodeId, length);
        }
    }

//    public void makePacket(byte stx1, byte stx2, byte opcode, byte version, TsiCvimDto tsiCvimDto, long currTime) {
//        if (this.tsiCvimDto == null) {
//            return;
//        }
//
//        if (tsiCvimDto != null) {
//            this.tsiCvimDto.setConnect(true);
//            this.tsiCvimDto.setCollectTime(tsiCvimDto.getCollectTime());
//            this.tsiCvimDto.setTscDateTime(tsiCvimDto.getTscDateTime());
//            this.tsiCvimDto.setCycleElapsedTime(tsiCvimDto.getCycleElapsedTime());
//
//            this.tsiCvimDto.setTscControlInfo(tsiCvimDto.getTscControlInfo());
//            this.tsiCvimDto.setTscAbnormalInfo(tsiCvimDto.getTscAbnormalInfo());
//            this.tsiCvimDto.setSignalStatusInfoCount(this.tsiCvimDto.getSignalStatusInfos().size());
//        }
//        else {
//            this.tsiCvimDto.setConnect(true);
//            this.tsiCvimDto.setSignalStatusInfoCount(0);
//        }
//
//        int length = 2 + 1 + 1 + 4 + 8 + (5 * this.tsiCvimDto.getSignalStatusInfoCount()) + 2;
//        ByteBuffer byteBuffer = ByteBuffer.allocate(2 + length);
//        byteBuffer.order(ByteOrder.BIG_ENDIAN);
//
//        // 헤더 (10 byte)
//        byteBuffer.put(stx1);
//        byteBuffer.put(stx2);
//        //byteBuffer.putShort((short)length);
//        byteBuffer.put((byte)((length>>8) & 0xFF));
//        byteBuffer.put((byte)((length)    & 0xFF));
//        byteBuffer.put(opcode);
//        byteBuffer.put(version);
//        //byteBuffer.putInt((int)this.getNodeId());
//        byteBuffer.put((byte)((this.getNodeId()>>24) & 0xFF));
//        byteBuffer.put((byte)((this.getNodeId()>>16) & 0xFF));
//        byteBuffer.put((byte)((this.getNodeId()>> 8) & 0xFF));
//        byteBuffer.put((byte)((this.getNodeId()    ) & 0xFF));
//
//        byte inManualControl     = (byte) (this.tsiCvimDto.getTscControlInfo().inManualControl     ? 0x01 : 0x00);
//        byte inFlashingControl   = (byte) (this.tsiCvimDto.getTscControlInfo().inFlashingControl   ? 0x01 : 0x00);
//        byte inLightsOutControl  = (byte) (this.tsiCvimDto.getTscControlInfo().inLightsOutControl  ? 0x01 : 0x00);
//        byte inActuationControl  = (byte) (this.tsiCvimDto.getTscControlInfo().inActuationControl  ? 0x01 : 0x00);
//        byte inTransitionControl = (byte) (this.tsiCvimDto.getTscControlInfo().inTransitionControl ? 0x01 : 0x00);
//        byte control = (byte) (
//                (inManualControl         ) |
//                (inFlashingControl   << 1) |
//                (inLightsOutControl  << 2) |
//                (inActuationControl  << 3) |
//                (inTransitionControl << 4)
//        );
//
//        byte inSignalConflict = (byte) (this.tsiCvimDto.getTscAbnormalInfo().inSignalConflict ? 0x01 : 0x00);
//        byte inCenterComm     = (byte) (this.tsiCvimDto.getTscAbnormalInfo().inCenterComm     ? 0x01 : 0x00);
//        byte inScuComm        = (byte) (this.tsiCvimDto.getTscAbnormalInfo().inScuComm        ? 0x01 : 0x00);
//        byte abnormal = (byte) (
//                (inSignalConflict     ) |
//                (inCenterComm     << 1) |
//                (inScuComm        << 2)
//        );
//
//        byte sttsCount = (byte) this.tsiCvimDto.getSignalStatusInfoCount();
//        byte splitFlag = 0x01;
//        byte signalStatusInfoCount = (byte) (sttsCount | (splitFlag << 7));
//
//        // 상태헤더 (8 byte)
//        byteBuffer.put(control);
//        byteBuffer.put(abnormal);
//        byteBuffer.put((byte)this.tsiCvimDto.getCycleElapsedTime());
//        byteBuffer.put(signalStatusInfoCount);
//        //byteBuffer.putInt((int)currTime);
//        byteBuffer.put((byte)((currTime>>24) & 0xFF));
//        byteBuffer.put((byte)((currTime>>16) & 0xFF));
//        byteBuffer.put((byte)((currTime>> 8) & 0xFF));
//        byteBuffer.put((byte)((currTime    ) & 0xFF));
//
//        for (TsiCvimStatus status : this.tsiCvimDto.getSignalStatusInfos()) {
//            byte lightsType = (byte) status.getLightsType().getValue();
//
//            byte lightsStatus = (byte) status.getLightsStatus().getValue();
//            byte unProtectedSignal = (byte) (status.isUnProtectedSignal() ? 0x01 : 0x00);
//            byte readyPedestrianSignal = (byte) (status.isReadyPedestrianSignal() ? 0x01 : 0x00);
//            byte timeReliability = (byte) status.getTimeReliability().getValue();
//
//            byte statusFlag = (byte) (
//                    (lightsStatus              ) |
//                    (unProtectedSignal     << 3) |
//                    (readyPedestrianSignal << 6) |
//                    (timeReliability       << 7)
//            );
//
//            byteBuffer.put((byte) (lightsType << 4));
//            byteBuffer.put(statusFlag);
//            byteBuffer.put((byte) status.getTotalSeconds());
//            byteBuffer.put((byte) status.getRemainingSeconds());
//            byteBuffer.put((byte) status.getDirectionCode());
//        }
//        byteBuffer.put((byte)0x00);
//        byteBuffer.put((byte)0x00);
///*
//
//        this.packet = byteBuffer.array();
//        int checkSum = CRC16Utils.CRC16_ccitt_cvim(this.packet, 2, length-2);
//        this.packet[this.packet.length - 2] = (byte)((checkSum >> 8) & 0xFF);
//        this.packet[this.packet.length - 1] = (byte)((checkSum     ) & 0xFF);
//*/
//
//        makeNodePacket(length);
//    }

}
