package com.tsi.comm.server.protocol;

import com.tsi.comm.server.dto.TsiCvimAbnormal;
import com.tsi.comm.server.dto.TsiCvimControl;
import com.tsi.comm.server.dto.TsiCvimStatus;
import com.tsi.comm.server.protocol.enums.eLightsStatus;
import com.tsi.comm.server.protocol.enums.eLightsType;
import com.tsi.comm.server.protocol.enums.eOpCode;
import com.tsi.comm.server.protocol.enums.eTimeReliability;
import com.tsi.comm.server.xnet.NettyUtils;
import com.tsi.comm.server.dto.mongodb.TcsNodeStatus;
import com.tsi.comm.server.repository.TsiNodeAddManager;
import com.tsi.comm.server.vo.TsiNodeAddDetailVo;
import com.tsi.comm.server.vo.TsiNodeAddVo;
import com.tsi.comm.server.vo.TsiNodeVo;
import com.tsi.common.spring.SpringUtils;
import com.tsi.common.utils.ByteUtils;
import com.tsi.common.utils.CRC16Utils;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.text.SimpleDateFormat;
import java.util.*;

@Slf4j
@Getter
@Setter
public class TsiCpuPacket extends AbstractTsiPacket {

    // 헤더(10), 상태헤더(8), 상태정보(5)*상태정보건수, 체크섬(2)
    // 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

    /*
    // 10 byte
    uint8_t         stx1;                   // stx 0x7E
    uint8_t         stx2;                   // stx 0x7E
    uint8_t         length[2];              // length, length byte ~ check sum
    uint8_t         opcode;                 // op code, 0x13
    uint8_t         dataVer;                // Data Version
    uint8_t         nodeid[4];              // signal node id
    // 8 byte
    tsc_cvim_hdr_t  hdr;                    // 8 byte, cvim signal status header
    // 5 byte * count
    tsc_cvim_stts_t stts[MAX_CVIM_STTS];    // 5 byte * 127 = 635 byte
    */
    // cvim-raw packet header
    /*
#define TIMESPEC_SIZE       (sizeof(struct timespec))   // 16 bytes
    uint8_t pktTm[TIMESPEC_SIZE]; // little endian
    uint8_t ipaddr[4];  // big endian
    uint8_t port[2];    // big endian
    uint8_t connect;
    uint8_t nodeid[4];  // big endian
    */

//typedef struct _tsc_cvim_hdr_s
//{
//    uint8_t manual      : 1;    /* 수동, 1: 이상, 0: 정상 */
//    uint8_t blink       : 1;    /* 점멸, 1: 점멸, 0: 정상 */
//    uint8_t turnOff     : 1;    /* 소등, 1: 소등, 0: 정상 */
//    uint8_t response    : 1;    /* 감응, 1: 감응, 0: 정상 */
//    uint8_t trans       : 1;    /* 전이, 1: 전이중, 0: 전이완료 */
//    uint8_t byte1Res0   : 3;    /* 예약, bit 7 ~ 5 */
//
//    uint8_t conflict    : 1;    /* 모순 이상, 1 : 이상, 0 : 정상 */
//    uint8_t centerComm  : 1;    /* 센터 통신 이상, 1: 센터 통신이상, 0 : 정상 */
//    uint8_t scuComm     : 1;    /* SCU 통신 이상,  1: MCU <--> SCU 통신 이상, 0: 정상 */
//    uint8_t byte2Res0   : 5;    /* 예약, bit 7 ~ 3 */
//
//    uint8_t cycleCounter;       /* 주기 카운터, 초 */
//
//    uint8_t sttsCount   : 7;    /* 총 신호상태정보의 개수, N 개 */
//    uint8_t splitFlag   : 1;    /* 데이터를 분할하여 전송할 경우 마지막 정보임을 나타내는 플래그(분할 패킷의 처음과 중간:0, 단일 또는 마지막 패킷:1) */
//
//    uint8_t currTime[4];        /* 현재시간, time_t형, Big Endian */
//} tsc_cvim_hdr_t, *ptsc_cvim_hdr_t;
//
//typedef struct _tsc_cvim_stts_s
//{
//    uint8_t dirAdd      : 4;    /* 3 ~ 0, 방향추가정보, 해당 방향에 연등지 없음(0), 해당 방향의 첫번째 연등지(1), 해당 방향의 두번째 연등지(2) */
//    uint8_t lightInfo   : 4;    /* 7 ~ 4, 신호등 정보, ■ 미지정(0), 직진(1), 좌회전(2), 보행자(3), 자전거(4), 우회전(5), 버스(6), 유턴(7) */
//
//    uint8_t lighting    : 3;    /* 2 ~ 0, 신호등 상태, ■ 소등(0), 적색점등(1), 황색점등(2), 녹색점등(3), 적색점멸(4), 황색점멸(5), 녹색점멸(6) */
//    uint8_t unprotect   : 1;    /* 3, 비보호 상태, ■ 신호등 정보 유턴/좌회전에 대한 비보호 여부, ■ 비보호 아님(0), 비보호(1) */
//    uint8_t Reserved0   : 2;    /* 5 ~ 4, 예비, ■ 예비 */
//    uint8_t walkerPush  : 1;    /* 6, 보행자(푸쉬 또는 자동검지), ■ 없음(0), 버튼 눌림 or 자동검지(1) */
//    uint8_t timeFlag    : 1;    /* 7, 시간 정보 신뢰성, ■ 고정신호시간(0), 가변신호시간(1) */
//
//    uint8_t displayTm;          /* 표출 시간, ■ 초 */
//    uint8_t remainTm;           /* 잔여 시간, ■ 초 */
//    uint8_t dirCode;            /* 방향 코드, ■ 출력지정 테이블에 지정된 방향코드 */
//} tsc_cvim_stts_t, *ptsc_cvim_stts_t;

    // TODO: 20240722 Protocol R28 Added
    public static final byte PROTOCOL_R27 = 0x01;
    public static final byte PROTOCOL_R28 = 0x1C;   // 28

    public static final byte STX1 = 0x7E;
    public static final byte STX2 = 0x7E;

    public static final int SIZE_HEAD         = 10;
    public static final int SIZE_STX1         = 1;
    public static final int SIZE_STX2         = 1;
    public static final int SIZE_LENGTH       = 2;
    public static final int SIZE_OPCODE       = 1;
    public static final int SIZE_VERSION      = 1;
    public static final int SIZE_NODE_ID      = 4;
    public static final int SIZE_STATUS_HDR   = 8;
    public static final int SIZE_STATUS_DATA  = 5;
    public static final int SIZE_CHECKSUM     = 2;
    public static final int SIZE_PACKET_DATA  = 18; // length, opcode, version, node_id, status_hdr, checksum

    public static final int INDEX_STX1        = 0;  // 0
    public static final int INDEX_STX2        = 1;  // 1
    public static final int INDEX_LENGTH      = 2;  // 2,3
    public static final int INDEX_OPCODE      = 4;  // 4
    public static final int INDEX_VERSION     = 5;  // 5
    public static final int INDEX_NODE_ID     = 6;  // 6,7,8,9
    public static final int INDEX_STATUS_HDR  = 10; // 10,11,12,13,14,15,16,17
    public static final int INDEX_STATUS_DATA = 18; // 18,19,20,21,22
    public static final int INDEX_STATUS_DIR_ADD = 0;       // 상태정보내 방향 추가 정보 인덱스
    public static final int INDEX_STATUS_DIRECTION = 4;     // 상태정보내 방향코드

    public static final int SIZE_NODE_DUMMY   = 1+1+2+1+1+2;    // stx1, stx1, length(2), opcode, version, checksum(2)
    public static final int SIZE_NODE_HEAD = SIZE_NODE_ID + SIZE_STATUS_HDR;
    public static final int POS_NODE_HEAD_NODEID = 0;       // node(4),
    public static final int POS_NODE_HEAD_COUNT = 7;        // node(4),

    public static final int SIZE_IPC_SIZE = 27;
    public static final int SIZE_TIMESPEC = 16;
    public static final int POS_IPC_TIMESPEC = 0;
    public static final int POS_IPC_IPADDR = 16;
    public static final int POS_IPC_PORT = 20;
    public static final int POS_IPC_CONNECT = 22;
    public static final int POS_IPC_NODEID = 23;
    public static final int POS_IPC_PACKET = SIZE_IPC_SIZE;
    public static final byte CONNECT = 0x01;
    public static final byte DISCONNECT = 0x00;

    private Object obj;
    private int  length;
    private byte dataVer;
    private int  count;
    private int  checkSum;
    protected byte[] cvimData;    // for cvim-raw topic
    protected byte[] nodeData;    // for node topic

    protected List<TsiCpuAddPacket> addNodes;

    private final TsiNodeAddManager nodeAddManager;
    private int  calcCheckSum;
    public TsiCpuPacket(long nodeId, long msec, long nsec, Channel channel) {
        super(nodeId, msec, nsec, NettyUtils.getRemoteIpAddressToLong(channel), NettyUtils.getRemotePort(channel));
        setOpCode(eOpCode.TSI_CPU_SIGNAL_NOTIFY.getValue());
        this.nodeAddManager = SpringUtils.getBean(TsiNodeAddManager.class);
    }

    public TsiCpuPacket(long nodeId, long msec, long nsec, long remoteIpAddressToLong, int remotePort) {
        super(nodeId, msec, nsec, remoteIpAddressToLong, remotePort);
        this.nodeAddManager = SpringUtils.getBean(TsiNodeAddManager.class);
    }

    // FOR CVIM packet
//    public TsiCpuPacket(long nodeId, byte[] value) {
//        super(nodeId, TimeUtils.currentTimeSeconds(), System.nanoTime(), 0, 0);
//        this.buf = value;   // CVIM Header 를 포함한 내부 IPC 형식 데이터임
//        int nodeLength = this.buf.length - TsiCpuPacket.SIZE_IPC_SIZE;
//        this.cvimData = new byte[TsiCpuPacket.SIZE_IPC_SIZE];
//        System.arraycopy(this.buf, 0, this.cvimData, 0, TsiCpuPacket.SIZE_IPC_SIZE);
//        if (nodeLength > 0) {
//            this.nodeData = new byte[nodeLength];
//            System.arraycopy(this.buf, TsiCpuPacket.SIZE_IPC_SIZE, this.nodeData, 0, nodeLength);
//        }
//    }

    protected byte getStx1() {
        if (this.buf != null) return this.buf[INDEX_STX1];
        return 0x00;
    }
    protected byte getStx2() {
        if (this.buf != null) return this.buf[INDEX_STX2];
        return 0x00;
    }

    public byte[] getCvimData() {
        return this.cvimData;
    }
    public byte[] getTestData() {
        return this.buf;
    }
    public byte[] getNodeData() {
        return this.nodeData;
    }

    /*
     *  Make cvim-raw packet
     */
    protected void makeCvimPacket() {
        final int packetLength = (this.buf == null) ? 0 : this.buf.length;

        this.cvimData = new byte[SIZE_IPC_SIZE + packetLength];

        // cvim-raw header
        System.arraycopy(this.timespec.bytes(), 0, this.cvimData, POS_IPC_TIMESPEC, SIZE_TIMESPEC);
        ByteUtils.setUnsignedInt(this.cvimData, POS_IPC_IPADDR, getRemoteIp());
        ByteUtils.setUnsignedShort(this.cvimData, POS_IPC_PORT, getRemotePort());
        this.cvimData[POS_IPC_CONNECT] = (opCode == (byte)eOpCode.TSI_CPU_SIGNAL_NOTIFY.getValue()) ? CONNECT : DISCONNECT;
        ByteUtils.setUnsignedInt(this.cvimData, POS_IPC_NODEID, this.nodeId);

        // cvim-raw body
        if (packetLength > 0) {
            System.arraycopy(this.buf, 0, this.cvimData, POS_IPC_PACKET, packetLength);
        }
        //log.error("CVIM: {}", HexString.fromBytes(this.cvimData));
    }

    /*
     *  Make cvim-raw packet
     */
    protected void makeAddNodeCvimPaket(TsiCpuPacket cpuPacket, byte[] packet) {
        final int packetLength = (packet == null) ? 0 : packet.length;
        final int headSize = SIZE_IPC_SIZE + 6;

        cpuPacket.cvimData = new byte[headSize + packetLength + 2];   // cpu packet 6 byte(nodeid 제외), crc 2 byte

        // cvim-raw header
        System.arraycopy(timespec.bytes(), 0, cpuPacket.cvimData, POS_IPC_TIMESPEC, SIZE_TIMESPEC);
        ByteUtils.setUnsignedInt(cpuPacket.cvimData, POS_IPC_IPADDR, getRemoteIp());
        ByteUtils.setUnsignedShort(cpuPacket.cvimData, POS_IPC_PORT, getRemotePort());
        cpuPacket.cvimData[POS_IPC_CONNECT] = (opCode == (byte)eOpCode.TSI_CPU_SIGNAL_NOTIFY.getValue()) ? CONNECT : DISCONNECT;
        ByteUtils.setUnsignedInt(cpuPacket.cvimData, POS_IPC_NODEID, cpuPacket.nodeId);

        cpuPacket.cvimData[SIZE_IPC_SIZE+INDEX_STX1] = getStx1();
        cpuPacket.cvimData[SIZE_IPC_SIZE+INDEX_STX2] = getStx2();
        ByteUtils.setUnsignedShort(cpuPacket.cvimData, SIZE_IPC_SIZE+INDEX_LENGTH, packetLength+2);
        cpuPacket.cvimData[SIZE_IPC_SIZE+INDEX_OPCODE] = (byte)getOpCode();
        cpuPacket.cvimData[SIZE_IPC_SIZE+INDEX_VERSION] = getDataVer();

        // cvim-raw body
        if (packetLength > 0) {
            System.arraycopy(packet, 0, cpuPacket.cvimData, headSize, packetLength);
        }
        // 체크섬 계산하지 않음
    }

    protected int checkPacket() {

        // 0 단계. STX1, STX2 체크
        if (this.buf[INDEX_STX1] != STX1 || this.buf[INDEX_STX2] != STX2) {
//            log.info("Node: {}, STX Error: {}, {}", nodeId, this.buf[INDEX_STX1], this.buf[INDEX_STX2]);
//            log.error("{}", HexString.fromBytes(this.buf));
            return -1;
        }

        // 1 단계. 패킷 길이 체크
        // TODO: 20240722, Protocol R28 ADDED
//        int reqLength = SIZE_PACKET_DATA + (SIZE_STATUS_DATA * this.count);
//        if (this.buf[INDEX_VERSION] == PROTOCOL_R28) {
//            reqLength = SIZE_PACKET_DATA + (SIZE_STATUS_DATA * this.count) + 1; // ADD CPU byte packet
//        }
//        if (this.length != reqLength ) {
//            log.info("Node: {}, Length Error: {}, Version: {}, status count: {}, {}", nodeId, this.length, this.buf[INDEX_VERSION],
//                    this.count, reqLength);
//            log.error("{}", HexString.fromBytes(this.buf));
//            return false;
//        }
        int reqLength = SIZE_PACKET_DATA + (SIZE_STATUS_DATA * this.count);
        if (this.length < reqLength ) {
//            log.info("Node: {}, Length Error: {}, Version: {}, status count: {}, {}", nodeId, this.length, this.buf[INDEX_VERSION],
//                    this.count, reqLength);
//            log.error("{}", HexString.fromBytes(this.buf));
            return -2;
        }
        // 2단계. 체크섬
        this.checkSum = ByteUtils.getUnsignedShort(this.buf, this.buf.length-2);
        this.calcCheckSum = CRC16Utils.CRC16_ccitt_cvim(this.buf, INDEX_LENGTH, this.length-2);  // 시작인덱스가 있으므로 전체길이로 계산
        if (this.checkSum != this.calcCheckSum) {
//            log.error("Node: {}, Check Sum Error: Version: {}, recv: {}, calc: {}", nodeId, this.buf[INDEX_VERSION], this.checkSum, this.calcCheckSum);
//            log.error("{}", HexString.fromBytes(this.buf));
            return -3;
        }
        return 0;
    }

    public int parsing(TsiNodeVo obj, boolean isCheckPacket) {

        if (this.buf == null || this.buf.length < INDEX_STATUS_HDR+3) {
            return -4;
        }

        this.opCode = this.buf[INDEX_OPCODE];
        this.dataVer = this.buf[INDEX_VERSION];
        this.length = ByteUtils.getUnsignedShort(this.buf, INDEX_LENGTH);
        this.count = (int)(this.buf[INDEX_STATUS_HDR+3] & 0x7F);

        // CVIM 데이터 및 TEST 데이터가 생성됨
        makeCvimPacket();

        int result = checkPacket();
        if (0 != result) {
            if (isCheckPacket) {
                // 20250425: CRC 체크여부에 따라 바로 리턴(기본값은 체크여부가 true 임)
                return result;
            }
        }

        TsiNodeAddVo tsiNodeAddVo = this.nodeAddManager.get(this.nodeId);
        if (tsiNodeAddVo == null) {
            // 연등지 정보가 없는 경우
            // R25 인 경우 마지막 CPU 제조사 코드를 복사하지 않도록 수정
            int addLength = this.buf.length-SIZE_NODE_DUMMY;
            final int r25Length = SIZE_NODE_HEAD + (this.count * SIZE_STATUS_DATA);
            if (addLength > r25Length) {
                addLength = r25Length;
            }
            this.nodeData = new byte[addLength];
            System.arraycopy(this.buf, INDEX_NODE_ID, this.nodeData, 0, addLength);
            return 0;
        }

        // 연등지 교차로 데이터 파싱
        byte[] head = new byte[SIZE_NODE_HEAD];
        List<byte[]> nodeStatus = new ArrayList<>();
        Map<Long, List<byte[]>> addStatus = new HashMap<>();

        System.arraycopy(this.buf, INDEX_NODE_ID, head, 0, SIZE_NODE_HEAD);

        int loop = 0;
        for (int ii = INDEX_STATUS_DATA; loop < this.count; ii += SIZE_STATUS_DATA, loop++) {
            byte[] status = new byte[SIZE_STATUS_DATA];
            System.arraycopy(this.buf, ii, status, 0, SIZE_STATUS_DATA);

            int dirAdd = status[INDEX_STATUS_DIR_ADD] & 0x0F;
            if (dirAdd == 0) {
                // 원천 노드 정보
                nodeStatus.add(status);
            }
            else {
                // 추가 노드 정보
                int originDirCode = status[INDEX_STATUS_DIRECTION]; // 원천방향코드
                int direction  = originDirCode/10;  // 인천공항 노드 확인용
                int addNodeIdx = originDirCode%10;  // 인천공항 노드 확인용
                if (addNodeIdx > 4) {
                    // 인천공항 연등지 인 경우 2개 이상이기 때문에 연등지1 = 15, 연등지2=16, ....
                    // 즉, 일의 자리 5,6,7,8,9로 설정하고 1,2,3,4,5로 된다. 십의자리는 *10을 해서 원천방향코드가 된다.
                    dirAdd = addNodeIdx - 4;        // 인천공항이 아닌경우는 위에서 구한 dirAdd 을 그대로 사용한다(1,2 만 가능)
                    originDirCode = direction * 10; // 15,16,17,18,19=10, 25,26,27,28,29=20, ....
                }
                status[INDEX_STATUS_DIR_ADD] = (byte) (status[INDEX_STATUS_DIR_ADD] & 0xF0);  // 방향추가정보를 0 으로 초기화
                TsiNodeAddDetailVo detailVo = tsiNodeAddVo.getAddNodeMap().get(originDirCode * 1000 + dirAdd);
                if (detailVo != null && detailVo.getAddDirCode() != null) {
                    for (int dirIdx = 0; dirIdx < detailVo.getAddDirCode().length; dirIdx++) {
//                        if (detailVo.getAddDirCode()[dirIdx] == (byte)0x00) {
//                            continue;
//                        }
                        // 연등지 방향코드를 배열에 가지고 있기 때문에
                        List<byte[]> list = addStatus.get(detailVo.getNodeId());
                        if (list == null) {
                            list = new ArrayList<>();
                            addStatus.put(detailVo.getNodeId(), list);
                        }
                        if (dirIdx == 0) {
                            status[INDEX_STATUS_DIRECTION] = detailVo.getAddDirCode()[dirIdx];
                            list.add(status);
                        }
                        else {
                            byte[] status2 = new byte[SIZE_STATUS_DATA];
                            System.arraycopy(status, 0, status2, 0, SIZE_STATUS_DATA);
                            status2[INDEX_STATUS_DIRECTION] = detailVo.getAddDirCode()[dirIdx];
                            list.add(status2);
                        }
                    }
                }
            }
        }

        byte splitFlag = 0x01;
        int statusCount = nodeStatus.size();
        this.nodeData = new byte[SIZE_NODE_HEAD + (statusCount*SIZE_STATUS_DATA)];
        System.arraycopy(head, 0, this.nodeData, 0, SIZE_NODE_HEAD);
        this.nodeData[POS_NODE_HEAD_COUNT] = (byte) (statusCount | (splitFlag << 7));
        for (int ii = 0; ii < statusCount; ii++) {
            System.arraycopy(nodeStatus.get(ii), 0, this.nodeData, SIZE_NODE_HEAD+(ii*SIZE_STATUS_DATA), SIZE_STATUS_DATA);
        }
        makeAddNodeCvimPaket(this, this.nodeData);

        if (addStatus.isEmpty()) {
            // 연등지 정보가 설정된게 없을까???
            return 0;
        }

        // 연등지 노드 카프카 패킷 생성
        this.addNodes = new ArrayList<>();
        for (Map.Entry<Long, List<byte[]>> entry: addStatus.entrySet()) {
            statusCount = entry.getValue().size();

            TsiCpuAddPacket addPacket = new TsiCpuAddPacket(entry.getKey(), this.timespec, this.remoteIp, this.remotePort);

            addPacket.setObj(this.nodeAddManager.get(entry.getKey()));

            addPacket.setNodeData(new byte[SIZE_NODE_HEAD + (statusCount*SIZE_STATUS_DATA)]);
            System.arraycopy(head, 0, addPacket.getNodeData(), 0, SIZE_NODE_HEAD);
            ByteUtils.setUnsignedInt(addPacket.getNodeData(), POS_NODE_HEAD_NODEID, entry.getKey());
            addPacket.getNodeData()[POS_NODE_HEAD_COUNT] = (byte) (statusCount | (splitFlag << 7));
            for (int ii = 0; ii < statusCount; ii++) {
                System.arraycopy(entry.getValue().get(ii), 0, addPacket.getNodeData(), SIZE_NODE_HEAD+(ii*SIZE_STATUS_DATA), SIZE_STATUS_DATA);
            }
            makeAddNodeCvimPaket(addPacket, addPacket.getNodeData());
            addNodes.add(addPacket);
        }
/*
        log.error(" RAW: {}, {}", this.nodeId, HexString.fromBytes(getBuf()));
        log.error("NODE: {}, {}", this.nodeId, HexString.fromBytes(getNodeData()));
        log.error("CVIM: {}, {}", this.nodeId, HexString.fromBytes(getCvimData()));
        for (int ii = 0; ii < addNodes.size(); ii++) {
            log.error("NODE: {}, {}", addNodes.get(ii).nodeId, HexString.fromBytes(addNodes.get(ii).getNodeData()));
            log.error("CVIM: {}, {}", addNodes.get(ii).nodeId, HexString.fromBytes(addNodes.get(ii).getCvimData()));
        }*/
        return 0;
    }

    public TcsNodeStatus getNodeStatus() {
        int nodeLength = this.buf.length - TsiCpuPacket.SIZE_IPC_SIZE;
        byte[] msec = new byte[8];
        byte[] nsec = new byte[8];
        System.arraycopy(this.cvimData, 0, msec, 0, 8);
        System.arraycopy(this.cvimData, 8, nsec, 0, 8);

        ByteBuffer byteMSec = ByteBuffer.wrap(msec);
        ByteBuffer byteNSec = ByteBuffer.wrap(nsec);
        byteMSec.order(ByteOrder.LITTLE_ENDIAN);
        byteNSec.order(ByteOrder.LITTLE_ENDIAN);

        long milliSeconds = byteMSec.getLong();
        long nanoSeconds = byteNSec.getLong();
        this.remoteIp = ByteUtils.getUnsignedInt(this.cvimData, TsiCpuPacket.POS_IPC_IPADDR);
        this.remotePort = ByteUtils.getUnsignedShort(this.cvimData, TsiCpuPacket.POS_IPC_PORT);

        /////
        getTimespec().tv_nsec(nanoSeconds);     // recv tcp,

        Date date = new Date(milliSeconds * 1000L);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sdf.setTimeZone(java.util.TimeZone.getTimeZone("GMT+9"));
        String collectTime = sdf.format(date);

        if (this.cvimData[TsiCpuPacket.POS_IPC_CONNECT] == TsiCpuPacket.DISCONNECT) {
            return TcsNodeStatus.builder()
                    ._id(this.nodeId)
                    .nodeId(this.nodeId)
                    .isConnect(false)
                    .collectTime(collectTime)
                    .build();
        }

        if (nodeLength < TsiCpuPacket.SIZE_HEAD + TsiCpuPacket.SIZE_STATUS_HDR) {
            // packet error
            return null;
        }

        String tscDateTime;
        int cycleElapsedTime;
        TsiCvimControl tscControlInfo = new TsiCvimControl();
        TsiCvimAbnormal tscAbnormalInfo = new TsiCvimAbnormal();
        int signalStatusInfoCount;
        int divFlag;
        long localTime;
        List<TsiCvimStatus> signalStatusInfos = new ArrayList<>();

        //this.opCode = this.nodeData[TsiCpuPacket.INDEX_OPCODE];
        //byte dataVer = this.nodeData[TsiCpuPacket.INDEX_VERSION];
        //int length = ByteUtils.getUnsignedShort(this.nodeData, TsiCpuPacket.INDEX_LENGTH);
        //int count = (int)(this.nodeData[TsiCpuPacket.INDEX_STATUS_HDR+3] & 0x7F);

        byte control = this.cvimData[TsiCpuPacket.SIZE_IPC_SIZE+TsiCpuPacket.INDEX_STATUS_HDR+0];
        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 = this.cvimData[TsiCpuPacket.SIZE_IPC_SIZE+TsiCpuPacket.INDEX_STATUS_HDR+1];
        tscAbnormalInfo.inSignalConflict = ((abnormal     ) & 0x01) == 0x01;   //모순상태;
        tscAbnormalInfo.inCenterComm     = ((abnormal >> 1) & 0x01) == 0x01;   //센터상태;
        tscAbnormalInfo.inScuComm        = ((abnormal >> 2) & 0x01) == 0x01;   //SCU 상태

        cycleElapsedTime = (int)(this.cvimData[TsiCpuPacket.SIZE_IPC_SIZE+TsiCpuPacket.INDEX_STATUS_HDR+2] & 0xFF);

        byte stts = this.cvimData[TsiCpuPacket.SIZE_IPC_SIZE+TsiCpuPacket.INDEX_STATUS_HDR+3];;
        signalStatusInfoCount = (int)(stts & 0x7F);
        //divFlag = (int)((stts >> 7) & 0x01);

        localTime = ByteUtils.getUnsignedInt(this.cvimData, TsiCpuPacket.SIZE_IPC_SIZE+TsiCpuPacket.INDEX_STATUS_HDR+4);

        int ii = TsiCpuPacket.SIZE_IPC_SIZE+TsiCpuPacket.INDEX_STATUS_DATA;

        for (int idx = 0; idx < signalStatusInfoCount; idx++) {
            TsiCvimStatus status = new TsiCvimStatus();

            //int dirAdd = (buffer.get(ii)) & 0x0F;                  //연등지
            int lightsType     = (int)((this.cvimData[ii  ] >> 4) & 0x0F);
            int lightsStatus   = (int)((this.cvimData[ii+1]     ) & 0x07);
            int reliability    = (int)((this.cvimData[ii+1] >> 7) & 0x01);
            boolean readyPedestrian = ((this.cvimData[ii+1] >> 6) & 0x01) == 0x01;
            boolean unProtected     = ((this.cvimData[ii+1] >> 3) & 0x01) == 0x01;
            int totalSeconds    = (int)(this.cvimData[ii+2] & 0xFF);
            int remainSeconds   = (int)(this.cvimData[ii+3] & 0xFF);
            int directionCode   = (int)(this.cvimData[ii+4] & 0xFF);

            status.setLightsType(eLightsType.getByValue(lightsType));               // 신호등정보 [직진,좌,보행]

            status.setLightsStatus(eLightsStatus.getByValue(lightsStatus));         //신호등상태
            status.setTimeReliability(eTimeReliability.getByValue(reliability));    //시간정보신뢰성
            status.setReadyPedestrianSignal(readyPedestrian);                       //보행자
            status.setUnProtectedSignal(unProtected);                               //비보호 상태

            status.setTotalSeconds(totalSeconds);       //표출시간
            status.setRemainingSeconds(remainSeconds);  //잔여시간
            status.setDirectionCode(directionCode);     //방향코드

            signalStatusInfos.add(status);

            ii += TsiCpuPacket.SIZE_STATUS_DATA;
        }

        date = new Date(localTime * 1000L);
        sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sdf.setTimeZone(java.util.TimeZone.getTimeZone("GMT+9"));
        tscDateTime = sdf.format(date);

        //log.error("Packet Received: NodeId: {}, {}", this.nodeId, TimeUtils.elapsedTimeStr(System.nanoTime()-nanoSeconds));
        return TcsNodeStatus.builder()
                ._id(this.nodeId)
                .nodeId(this.nodeId)
                .isConnect(true)
                .collectTime(collectTime)
                .tscDateTime(tscDateTime)
                .cycleElapsedTime(cycleElapsedTime)
                .tscControlInfo(tscControlInfo)
                .tscAbnormalInfo(tscAbnormalInfo)
                .signalStatusInfoCount(signalStatusInfoCount)
                .signalStatusInfos(signalStatusInfos)
                .build();
    }

}
