package com.its.pis.xnettcp.vds.process;

import com.its.app.AppUtils;
import com.its.app.utils.SysUtils;
import com.its.pis.config.ThreadPoolInitializer;
import com.its.pis.entity.TbVdsCtlr;
import com.its.pis.xnettcp.vds.protocol.VdsProtocol;
import com.its.pis.xnettcp.vds.protocol.VdsResFramePacket;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;

@Slf4j
@Service
public class VdsDataProcess {

    public static LinkedBlockingQueue<VdsData> SERVER_DATA_QUEUE = new LinkedBlockingQueue<>(1000);
    private ThreadPoolExecutor taskExecutor = (ThreadPoolExecutor)Executors.newFixedThreadPool(1);
    int MAX_CORE = Runtime.getRuntime().availableProcessors();

    @PostConstruct
    private void init() {
    }

    public void run() {
        log.info("VdsDataProcess.run: Start.");
        if (this.MAX_CORE < 8) {
            this.MAX_CORE = 8;
        }
        ThreadPoolInitializer poolInitializer = (ThreadPoolInitializer) AppUtils.getBean(ThreadPoolInitializer.class);
        int executePool = Math.max(this.MAX_CORE, poolInitializer.getWork());
        for (int ii = 0; ii < executePool; ii++) {
            log.info("VdsDataProcess.Task: {}", ii);
            this.taskExecutor.execute(() -> {
                while (true) {
                    VdsData serverData = null;
                    try {
                        serverData = VdsDataProcess.SERVER_DATA_QUEUE.take();
                        VdsDataTask handler = (VdsDataTask) AppUtils.getBean(VdsDataTask.class);
                        handler.run(this, serverData);
                    } catch (InterruptedException e) {
                        log.error("VdsDataProcess.Task: Received data null");
                    }
                }
            });
        }

        log.info("VdsDataProcess.run: ..End.");
    }

    public void process(VdsData data) {

        String ipAddress = data.getIpAddress();
        TbVdsCtlr obj = data.getObj();
        if (obj == null) {
            log.error("VdsDataProcess.process: [{}] Unknown ip address data packet", data.getIpAddress());
            return;
        }

        MDC.put("id", obj.getLogKey());
        try {
            VdsResFramePacket framePacket = data.getData();
            byte opCode = framePacket.getOpCode();
            int groupNo = framePacket.getHead().getGroupNo();
            int ctrlNo  = framePacket.getHead().getControllerNo();
            if (groupNo != obj.getGROUP_NO() || ctrlNo != obj.getVDS_CTLR_LOCAL_NO()) {
                log.error("VdsDataProcess.process: ID: {}, ChannelNo, Controller No Miss Matched: [{}]-[{}], [{}]-[{}]",
                        obj.getVDS_CTLR_NMBR(), groupNo, obj.getGROUP_NO(), ctrlNo, obj.getVDS_CTLR_LOCAL_NO());
                //return;
            }
            log.info("VdsDataProcess.process: {}, {}, ThreadId: {}", ipAddress, VdsProtocol.getOpCodeName(opCode), Thread.currentThread().getId());
            if (!checkStatus(obj, framePacket)) {
                return;
            }

            JobProtocol jobProtocol = null;
            switch (opCode) {
                case VdsProtocol.vds_Temperature:   //    = (byte)0x1E;   // 제어기 상태 함체온도 및 입출력전압 요청 #30[0x1E] 센터→로컬 함체온도/입출력전압
                    jobProtocol = new Job_Temperature(framePacket);
                    break;
                case VdsProtocol.vds_Data:          //    = (byte)0x04;   // VDS 데이터 요구 (Data Request) 데이터 요구 #4[0x04] 센터→로컬 교통data(교통량,점유율,차량길이,속도)
                    jobProtocol = new Job_Data(framePacket);
                    break;

                case VdsProtocol.vds_Traffic:       //    = (byte)0x07;   // VDS 데이터 요구 (Data Request) 누적 교통량 데이터 요구 #7[0x07] 센터→로컬 교통량data
                    jobProtocol = new Job_Traffic(framePacket);
                    break;
                case VdsProtocol.vds_ACK:           //    = (byte)0x70;   // 센터의 명령을 현장제어기가 정상적으로 수행을 완료했으나, 전송할 결과가 없을 때 클라이언트가 전송하는 Packet임.
                    jobProtocol = new Job_ACK(framePacket);
                    break;

                case VdsProtocol.vds_Speed:         //    = (byte)0x05;   // VDS 데이터 요구 (Data Request) 속도 데이터 요구 #5[0x05] 센터→로컬 특정차로 속도data
                    jobProtocol = new Job_Speed(framePacket);
                    break;

                case VdsProtocol.vds_Image:         //    = (byte)0x16;   // VDS 데이터 요구 (Data Request) 화상이미지 요구 #22[0x16] 센터→로컬 이미지data
                    jobProtocol = new Job_Image(framePacket);
                    break;
                case VdsProtocol.vds_Vehicle:       //    = (byte)0x17;   // VDS 데이터 요구 (Data Request) 개별차량 데이터 요구 #23[0x17] 센터→로컬 개별차량 data
                    jobProtocol = new Job_Vehicle(framePacket);
                    break;
                case VdsProtocol.vds_Length:        //    = (byte)0x06;   // VDS 데이터 요구 (Data Request) 차량 길이 데이터 요구 #6[0x06] 센터→로컬 특정차로 차량길이data
                    jobProtocol = new Job_Length(framePacket);
                    break;

                case VdsProtocol.vds_NAK:           //    = (byte)0x71;   // 서버의 명령에 오류가 있을 경우 클라이언트가 전송하는 Packet임.
                    jobProtocol = new Job_NAK(framePacket);
                    break;

                case VdsProtocol.vds_Validation:    //    = (byte)0x0B;   // 제어기 상태 제어기 Validation #11[0x0B] 센터→로컬 H/W check 결과
                    jobProtocol = new Job_Validation(framePacket);
                    break;

//                case VdsProtocol.vds_Reset:         //    = (byte)0x0C;   // 제어기 상태 제어기 Reset #12[0x0C] 센터→로컬 ACK/NAK
//                    jobProtocol = new Job_Reset(framePacket);
//                    break;
//                case VdsProtocol.vds_Initialize:    //    = (byte)0x0D;   // 제어기 상태 제어기 Initialize #13[0x0D] 센터→로컬 ACK/NAK
//                    jobProtocol = new Job_Initialize(framePacket);
//                    break;
//                case VdsProtocol.vds_Downloading:   //    = (byte)0x0E;   // 파라미터 Download 파라미터 Downloading #14[0x0E] 센터→로컬 ACK/NAK
//                    jobProtocol = new Job_Downloading(framePacket);
//                    break;

                case VdsProtocol.vds_Uploading:     //    = (byte)0x0F;   // 파라미터 Upload 파라미터 Uploading #15[0x0F] 센터→로컬 파라미터값
                    jobProtocol = new Job_Uploading(framePacket);
                    break;
                case VdsProtocol.vds_Online:        //    = (byte)0x11;   // 온라인 상태조사 온라인 상태조사 #17[0x11] 센터→로컬 Passed Time
                    jobProtocol = new Job_Online(framePacket);
                    break;
//                case VdsProtocol.vds_CheckMemory:   //    = (byte)0x12;   // 원격진단 메모리 Check #18[0x12] 센터→로컬 ACK/NAK
//                    jobProtocol = new Job_CheckMemory(framePacket);
//                    break;

//                case VdsProtocol.vds_RTC:           //    = (byte)0x18;   // 제어기 상태 RTC 변경 #24[0x18] 센터→로컬 ACK/NAK
//                    jobProtocol = new Job_RTC(framePacket);
//                    break;
               case VdsProtocol.vds_CheckEcho:     //    = (byte)0x13;   // 원격진단 Echo Check #19[0x13] 센터→로컬 Echo메시지
                    jobProtocol = new Job_CheckEcho(framePacket);
                    break;
                case VdsProtocol.vds_CheckSeq:      //    = (byte)0x14;   // 원격진단 순번 Check #20[0x14] 센터→로컬 수치처리 결과값
                    jobProtocol = new Job_CheckSeq(framePacket);
                    break;
                case VdsProtocol.vds_Version:       //    = (byte)0x15;   // 원격진단 VDS Version 요청 #21[0x15] 센터→로컬 version정보
                    jobProtocol = new Job_Version(framePacket);
                    break;
                case VdsProtocol.vds_Usage:         //    = (byte)0x19;   // 기타 사용/사용불가 설정 #25[0x19] 운영단말→수집서버 사용여부 설정결과값
                    jobProtocol = new Job_Usage(framePacket);
                    break;
//                case VdsProtocol.vds_SetRunTemp:    //    = (byte)0x20;   // 기타 Fan/Heater 동작온도설정 #26[0x20] 센터→로컬 ACK/NAK
//                    jobProtocol = new Job_SetRunTemp(framePacket);
//                    break;
                case VdsProtocol.vds_Abort:         //    = (byte)0x72;   // 클라이언트(서버)가 전송하는 데이터가 필요 없을 경우 서버(클라이언트)가 클라이언트(서버)에게 현재 전송중인 데이터를 중단하도록 하는 명령임
                    jobProtocol = new Job_Abort(framePacket);
                    break;
                case VdsProtocol.vds_RePosition:    //    = (byte)0x73;   // 클라이언트가 전송하는 데이터의 특정 위치부터 다시 전송하도록 하는 명령임.
                    jobProtocol = new Job_RePosition(framePacket);
                    break;
            }

            if (jobProtocol != null) {
                jobProtocol.parse();
            }
        } finally {
            MDC.remove(obj.getLogKey());
            MDC.clear();
        }
    }

    protected boolean checkStatus(TbVdsCtlr obj, VdsResFramePacket packet) {
        String opCodeName = VdsProtocol.getOpCodeName(packet.getHead().getOpCode());

        /*
         * VDS Status Update
         */
        // 0 INVALID REQUEST                Host 로 부터 수신한 메시지의 내용이 비정상 적일 때 (비정상 시 : 1)
        // 1 RESPONSE DATA NOT READY        Host 가 요청한 데이터가 준비되지 않은 경우 발생 (미준비 시 : 1)
        // 2 UNKNOWN MESSAGE TYPE(OP-Cede)  OP-Code 의 내용이 비정상일 때 (비정상   시 : 1)
        // 3 LONG POWER FAIL                2초를 초과한 시간 동안 Power Fail 이 발생한 경우 (fail 시 : 1)

        // 4 SHORT POWER FAIL               2초 이하의 Power Fail 이 발생한 경우 (fail 시 : 1)
        // 5 INVALID DATA FILED             (invalid 시 : 1)
        // 6 기본 파라미터                  파라미터 Ram 이 기본값을   가지고 있어, Host 로부터 파라미터 Down-Load 가 필요한 경우 (download 필요한   경우 : 1)
        // 7 전역 주소 수신                 Host 로부터 전역주소  ‘0xFFFFFFFF'를 수신한 경우 (전역주소를 수신했을 때 : 1),(단, 동기화 명령은 제외)

        // 8 자동 SYNC                      Host 로 부터 Sync 명령을 받지 못한 경우, 일정시간 이후 (기본 1초) 제어기 스스로 동기화하는 경우 (auto sync 시 : 1)
        // 9 앞문개방                       앞문이 개방된 경우 (개방시 : 1)
        //10 뒷문개방                       뒷문이 개방된 경우 (개방시 : 1)
        //11 Fan 작동                       펜이 작동하는 경우 (작동시 : 1)

        //12 히터 작동                      히터가 작동하는 경우 (작동시 : 1)
        //13 제어기 RESET                   제어기 스스로 Reset 한   경우 (reset 시 : 1)
        //14 ACK (NORMAL = 0)               사용안함 (default=0)
        //15 Video Input 여부 (NORMAL = 0)  영상검지기 제어기로 카메라 영상입력이 없을 때 : 1

        byte stts1 = packet.getHead().getStatus1();
        byte stts2 = packet.getHead().getStatus2();

        int Invalid         = SysUtils.getBitValue(stts1, 0);
        int Ready           = SysUtils.getBitValue(stts1, 1);
        int longPower       = SysUtils.getBitValue(stts1, 3);
        int shortPower      = SysUtils.getBitValue(stts1, 4);

        int FrontDoorOpen   = SysUtils.getBitValue(stts2, 1);
        int BackDoorOpen    = SysUtils.getBitValue(stts2, 2);
        int Fan             = SysUtils.getBitValue(stts2, 3);
        int Heater          = SysUtils.getBitValue(stts2, 4);
        int VideoInput      = SysUtils.getBitValue(stts2, 7);

        log.info("[{}]. RECV: checkStatus. Invalid:{}, Ready:{}, FrontDoor:{}, BackDoor:{}, Fan:{}, Heater:{}, Video:{}",
                obj.getVDS_CTLR_ID(), Invalid, Ready, FrontDoorOpen, BackDoorOpen, Fan, Heater, VideoInput);

        String PRNT_PWER_STTS_CD  = (longPower == 0 && shortPower == 0) ? "PWS0" : "PWS1";
        String CBOX_DOOR_STTS_CD  = (FrontDoorOpen == 0 && BackDoorOpen == 0) ? "CDS0" : "CDS1";
        String FRONT_DOOR_STTS_CD = FrontDoorOpen == 1 ? "CDS1" : "CDS0";
        String BACK_DOOR_STTS_CD  = BackDoorOpen  == 1 ? "CDS1" : "CDS0";
        String FAN_STTS_CD        = Fan           == 1 ? "PAS0" : "PAS1";
        String HETR_STTS_CD       = Heater        == 1 ? "HTS0" : "HTS1";
        String VIDEO_INPUT	      = VideoInput    == 1 ? "VDI1" : "VDI0";

        obj.getStts().setVDS_CTLR_NMBR(obj.getVDS_CTLR_NMBR());
        obj.getStts().setUPDT_DT(SysUtils.getSysTime());
        obj.getStts().setCMNC_STTS_CD("CMS0");

        obj.getStts().setPRNT_PWER_STTS_CD(PRNT_PWER_STTS_CD);
        obj.getStts().setCBOX_DOOR_STTS_CD(CBOX_DOOR_STTS_CD);
        obj.getStts().setFAN_STTS_CD(FAN_STTS_CD);
        obj.getStts().setHETR_STTS_CD(HETR_STTS_CD);
        obj.getStts().setFRONT_DOOR_STTS_CD(FRONT_DOOR_STTS_CD);
        obj.getStts().setBACK_DOOR_STTS_CD(BACK_DOOR_STTS_CD);
        obj.getStts().setVIDEO_INPUT(VIDEO_INPUT);

        //return (Invalid == 0 && Ready == 0);
        return true;
    }

    /*
    *  작업큐에 데이터 추가
     */
    public void add(VdsData data) {
        try {
            //offer => full -> return
            //add   => full -> wait
            //큐가 차더라도 바로 리턴함.
            if (!SERVER_DATA_QUEUE.offer(data)) {
                log.error("VdsDataProcess-QueueFull: {}", SERVER_DATA_QUEUE.size());
            }
        } catch (Exception e) {
            log.error("VdsDataProcess-QueueAddError: {}", e.getMessage(), e);
        }
    }

}
