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

import com.its.app.utils.SysUtils;
import com.its.vds.config.CommunicationConfig;
import com.its.vds.dto.TbVdsCtlrDto;
import com.its.vds.xnettcp.vds.handler.VdsServerIdleStateHandler;
import com.its.vds.xnettcp.vds.process.response.VdsResponse;
import com.its.vds.xnettcp.vds.process.response.impl.*;
import com.its.vds.xnettcp.vds.protocol.enums.eVdsOpCode;
import com.its.vds.xnettcp.vds.protocol.impl.VdsResFramePacket;
import com.its.vds.xnettcp.vds.protocol.impl.andong.VdsAndongResFrameHead;
import com.its.vds.xnettcp.vds.protocol.impl.andong.VdsAndongResFramePacket;
import io.netty.channel.ChannelHandlerContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Service;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;

@Slf4j
@RequiredArgsConstructor
@Service
public class TcpServerRecvDataProcess {

    private final CommunicationConfig communicationConfig;
    private final TcpServerRecvDataTask serverDataTask;

    private static final LinkedBlockingQueue<TcpServerRecvData> SERVER_DATA_QUEUE = new LinkedBlockingQueue<>(1000);
    private final ThreadPoolExecutor taskExecutor = (ThreadPoolExecutor)Executors.newFixedThreadPool(1);
    private int maxCord = Runtime.getRuntime().availableProcessors();

    public void run() {
        log.info("TcpServerRecvDataProcess.run: Start.");
        if (this.maxCord < 8) {
            this.maxCord = 8;
        }
        int executePool = this.maxCord;
        for (int ii = 0; ii < executePool; ii++) {
            log.info("TcpServerRecvDataProcess.Task: {}", ii);
            this.taskExecutor.execute(() -> {
                boolean isRunning = true;
                while (isRunning) {
                    try {
                        TcpServerRecvData serverData = TcpServerRecvDataProcess.SERVER_DATA_QUEUE.take();
                        this.serverDataTask.run(this, serverData);
                    }
                    catch (Exception e) {
                        log.error("TcpServerRecvDataProcess.Task: Exception: {}", e.getMessage());
                        Thread.currentThread().interrupt();
                        isRunning = false;
                    }
                }
            });
        }

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

    public void process(TcpServerRecvData data) {
        try {
            ChannelHandlerContext ctx = data.getCtx();
            TbVdsCtlrDto vdsObj = data.getObj();
            VdsResFramePacket resFramePacket = (VdsResFramePacket)data.getData();

            VdsAndongResFramePacket packet = new VdsAndongResFramePacket(resFramePacket.getPacket(), resFramePacket.getMsgLen());
            String opCodeValue = String.format("0x%02X", packet.getOpCode());

            MDC.put("id", vdsObj.getLogKey());

            VdsResponse response = null;
            eVdsOpCode opCode = eVdsOpCode.getValue(packet.getOpCode());

            /**
             * 제어기 명령 타임아웃 타스크 제거
             */
            vdsObj.removeRegisteredCommandsTimer(opCode);

            if (!checkStatus(vdsObj, packet.getHead())) {
                return;
            }

            switch(opCode) {
                case OP_VDS_SYNCHRONIZATION:
                    log.warn("OP_VDS_SYNCHRONIZATION 패킷은 응답이 없는 패킷임... 패킷 수신 오류...");
                    break;
                case OP_VDS_DATA_TRAFFIC:
                    response = new VdsResDataTraffic(vdsObj, packet);
                    break;
                case OP_VDS_DATA_SPEED:
                    response = new VdsResDataSpeed(vdsObj, packet);
                    break;
                case OP_VDS_DATA_LENGTH:
                    response = new VdsResDataLength(vdsObj, packet);
                    break;
                case OP_VDS_DATA_VOLUME:
                    response = new VdsResDataVolume(vdsObj, packet);
                    break;
                case OP_VDS_DATA_IMAGE:
                    response = new VdsResDataImage(vdsObj, packet);
                    break;
                case OP_VDS_DATA_VEHICLE:
                    response = new VdsResDataVehicle(vdsObj, packet);
                    break;

                case OP_VDS_STATUS_VALIDATION:
                    response = new VdsResStatusValidation(vdsObj, packet);
                    break;
                case OP_VDS_STATUS_RESET:
                    response = new VdsResStatusReset(vdsObj, packet);
                    break;
                case OP_VDS_STATUS_INITIALIZE:
                    response = new VdsResStatusInitialize(vdsObj, packet);
                    break;
                case OP_VDS_STATUS_RTC:
                    response = new VdsResStatusRtc(vdsObj, packet);
                    break;
                case OP_VDS_STATUS_TEMPERATURE:
                    response = new VdsResStatusTemperature(vdsObj, packet);
                    break;

                case OP_VDS_PARAM_DOWNLOADING:
                    response = new VdsResParamDownloading(vdsObj, packet);
                    break;
                case OP_VDS_PARAM_UPLOADING:
                    response = new VdsResParamUploading(vdsObj, packet);
                    break;

                case OP_VDS_CHECK_ONLINE:
                    response = new VdsResCheckOnline(vdsObj, packet);
                    break;
                case OP_VDS_CHECK_MEMORY:
                    response = new VdsResCheckMemory(vdsObj, packet);
                    break;
                case OP_VDS_CHECK_ECHO:
                    response = new VdsResCheckEcho(vdsObj, packet);
                    break;
                case OP_VDS_CHECK_SEQUENCE:
                    response = new VdsResCheckSequence(vdsObj, packet);
                    break;
                case OP_VDS_CHECK_VERSION:
                    response = new VdsResCheckVersion(vdsObj, packet);
                    break;

                case OP_VDS_SET_TEMPERATURE:
                    response = new VdsResSetTemperature(vdsObj, packet);
                    break;

                case OP_VDS_ACK:
                    response = new VdsResAck(vdsObj, packet);
                    break;
                case OP_VDS_NAK:
                    response = new VdsResNak(vdsObj, packet);
                    break;
                case OP_VDS_ABORT:
                    response = new VdsResAbort(vdsObj, packet);
                    break;
                default:
                    break;
            }

            if (response != null) {
                if (!response.process()) {
                    log.error("TcpServerRecvDataProcess.process: response error. VDS {}, {}, will be closed.", vdsObj.getVdsCtlrNmbr(), opCodeValue);
                    VdsServerIdleStateHandler.disconnectChannel(vdsObj, ctx.channel());
                }
            }
            else {
                log.error("TcpServerRecvDataProcess.process: Unknown Op Code Packet: VDS {}, {}, will be closed.", vdsObj.getVdsCtlrNmbr(), opCodeValue);
                VdsServerIdleStateHandler.disconnectChannel(vdsObj, ctx.channel());
            }

            MDC.remove(vdsObj.getLogKey());
            MDC.clear();
        } catch (Exception e) {
            log.error("TcpServerRecvDataProcess.process: Exception: {}", e.toString());
        }
    }

    protected boolean checkStatus(TbVdsCtlrDto vdsObj, VdsAndongResFrameHead head) {
        // 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 = head.getStatus1();
        byte stts2 = head.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:{}",
                vdsObj.getVdsCtlrNmbr(), 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";

        vdsObj.getStts().setVdsCtlrNmbr(vdsObj.getVdsCtlrNmbr());
        vdsObj.getStts().setUpdtDt(SysUtils.getSysTime());
        vdsObj.getStts().setCmncSttsCd("CMS0");

        vdsObj.getStts().setPrntPwerSttsCd(PRNT_PWER_STTS_CD);
        vdsObj.getStts().setCboxDoorSttsCd(CBOX_DOOR_STTS_CD);
        vdsObj.getStts().setFanSttsCd(FAN_STTS_CD);
        vdsObj.getStts().setHetrSttsCd(HETR_STTS_CD);
        vdsObj.getStts().setFrontDoorSttsCd(FRONT_DOOR_STTS_CD);
        vdsObj.getStts().setBackDoorSttsCd(BACK_DOOR_STTS_CD);
        vdsObj.getStts().setVideoInput(VIDEO_INPUT);

        return true;
    }

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

}
