package kr.gmtc.gw.dev.asderecv.controller;

import kr.gmtc.gw.comp.socket.udp.UDPEventListener;
import kr.gmtc.gw.comp.socket.udp.UDPManager;
import kr.gmtc.gw.comp.socket.utils.SocketUtil;
import kr.gmtc.gw.comp.socket.vo.MsgObjVO;
import kr.gmtc.gw.dev.asderecv.asde.rest.vo.ServiceAsdeData;
import kr.gmtc.gw.dev.asderecv.asde.utils.AsdeUtil;

import kr.gmtc.gw.dev.asderecv.thread.CustomThread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;

//@Component("controller")
public class MainController implements ApplicationListener<ContextClosedEvent> {

    @Value("${root}")
    private String path;

    @Resource(name = "packetQ")
    Queue<String> packetQ;

    @Resource(name = "sendQueue")
    HashMap<Integer, Queue<ServiceAsdeData>> sendQueue;

    @Resource(name = "serviceRunnig")
    private boolean serviceRunnig = false;

    @Value("${asde.service.queueCount}")
    private Integer scvQcount;

    @Value("${asde.service.clearQ.maxCount}")
    private Integer scvQmaxCount;

    @Value("${asde.service.clearQ.diffTime}")
    private Integer scvQdiffTime;

    @Value("${asde.service.clearQ.clearTime}")
    private Integer scvQclearTime;


    private boolean isRunning;
    protected UDPManager udpManager;
    private UDPEventListener udpListener;

    private ArrayBlockingQueue<MsgObjVO> recvQ;
    private ArrayBlockingQueue<MsgObjVO> sendQ;

    private Thread recvQThread;
    private Thread sendQThread;

    private CustomThread serviceQClearThread;


    protected Logger logger;

    protected MainController(@Value("${root}") String path){

        isRunning  = false;
        logger = LoggerFactory.getLogger(MainController.class);

        recvQ = new ArrayBlockingQueue<>(100000);
        sendQ = new ArrayBlockingQueue<MsgObjVO>(100000);

        udpListener = createUDPEventListener();
        udpManager  = new UDPManager(path, udpListener);

        recvQThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (isRunning) {
                    try {
                        int cnt = recvQ.size();
                        if (cnt > 1000) {
                            cnt = 1000;
                        }

                        for (int i = 0; i <= cnt - 1; i++) {
                            if (recvQ != null) {
                                MsgObjVO vo = recvQ.poll();
                                if (vo != null) {
                                    recvDataIn(vo);
                                    vo = null;
                                }
                            }
                        }
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        logger.error(SocketUtil.getStatckTrace(e));
                    }
                }
            }
        });

        sendQThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (isRunning) {
                    try {
                        int cnt = sendQ.size();
                        if (cnt > 1000) {
                            cnt = 1000;
                        }

                        for (int i = 0; i <= cnt - 1; i++) {
                            if (sendQ != null) {
                                MsgObjVO vo = sendQ.poll();
                                if (vo != null) {
                                    sendDataIn(vo);
                                    vo = null;
                                }
                            }
                        }
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        logger.error(SocketUtil.getStatckTrace(e));
                    }
                }
            }
        });

    }

    @Order(0)
    @EventListener(ApplicationReadyEvent.class)
    public void initialize() {
        try {

             // 전송용 Queue 생성
            createQueue();

            // 업무 스레드 생성, 대기 없이 무한반복
            serviceQClearThread = new CustomThread("serviceQClearThread", this, CustomThread.NO_SLEEP, this::serviceQClear, null, false);

        } catch(Exception e){
            logger.error("[MainController] Prepare Fail " + AsdeUtil.getStatckTrace(e));
            System.exit(1);
        }
    }

    @Order(1)
    @EventListener(ApplicationReadyEvent.class)
    public void start() {
        isRunning = true;
        udpManager.initializeUDPFromXML();

        udpManager.startClient();
        recvQThread.start();
        sendQThread.start();

    }


    public void stop() {
        isRunning = false;
    }

    //////////////////////////////////////////////////////////////////////

    protected UDPEventListener createUDPEventListener() {
        UDPEventListener listener = new UDPEventListener() {

            @Override
            public void udpDataIn(MsgObjVO vo) {
                // // TODO Auto-generated method stub
                // throw new UnsupportedOperationException("Unimplemented method 'udpDataIn'");

               //logger.info("RECV-DATA : " + vo.getMsg().toString());

                recvPacketAdd(vo);
            }

            @Override
            public void udpSendData(MsgObjVO vo) {
                // TODO Auto-generated method stub
                throw new UnsupportedOperationException("Unimplemented method 'udpSendData'");
            }

            @Override
            public void udpSendData(String ip, int port, MsgObjVO vo) {
                // TODO Auto-generated method stub
                throw new UnsupportedOperationException("Unimplemented method 'udpSendData'");
            }

            @Override
            public void connected(String ip, int port, int statusCode, String description) {
                // TODO Auto-generated method stub
                throw new UnsupportedOperationException("Unimplemented method 'connected'");
            }

            @Override
            public void disconnected(String ip, int port, int statusCode, String description) {
                // TODO Auto-generated method stub
                throw new UnsupportedOperationException("Unimplemented method 'disconnected'");
            }

            @Override
            public void connectionStatus(String ip, int port, String connEvent, int statusCode, String description) {
                // TODO Auto-generated method stub
                throw new UnsupportedOperationException("Unimplemented method 'connectionStatus'");
            }

        };
        return listener;
    }

    protected void recvPacketAdd(MsgObjVO vo) {
        if (vo.getMsg().length < 1) {
            return;
        }

        String recvDate = "";


        try {

            String msg = SocketUtil.byteArrayToHexString(vo.getMsg());

            recvDate = vo.getRecvDT().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));

            msg = msg.concat("*").concat(recvDate);

            packetQ.add(msg);

            //logger.debug("[UDPDataIn] : " + msg);

        } catch (Exception e) {

            logger.error("[UDPDataIn] Unknown Exception Occur " + e.getMessage() );

        }
    }

    private void recvDataIn(MsgObjVO vo) {
        try {
            recvPacketAdd(vo);
        } catch (Exception e) {
            logger.error(SocketUtil.getStatckTrace(e));
        }

    }

    private void sendDataIn(MsgObjVO vo) {
        try {
            udpManager.sendMsg(vo.getSendClientIP(), vo.getSendClientPort(), vo);
        } catch (Exception e) {
            logger.error(SocketUtil.getStatckTrace(e));
        }
    }

     // 설정에따라 REST용 서비스 큐 생성
    public void createQueue() {

        Class cls ;

        for(int idx=0; idx<scvQcount; idx++) {
   
            String className = "java.util.LinkedList";

            Queue<ServiceAsdeData> obj = null;

            try {

                cls = Class.forName(className);
                obj = (Queue<ServiceAsdeData>) cls.newInstance();

            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            sendQueue.put(idx, obj);

        }

    }

    private void serviceQClear() throws InterruptedException, IOException {

        String sRecvTime = "";

        ServiceAsdeData jsonData = new ServiceAsdeData();

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

        LocalDateTime dateTime;

        int iQcnt = 0;

        if(serviceRunnig) return;

        try {

            for(int idx=0; idx<scvQcount; idx++) {

                iQcnt = sendQueue.get(idx).size();

                if (iQcnt > scvQmaxCount  ) {

                    jsonData = sendQueue.get(idx).peek();

                    sRecvTime = jsonData.getRecptn_dt();

                    dateTime = LocalDateTime.parse(sRecvTime, formatter);

                    Duration diff = Duration.between(dateTime, LocalDateTime.now());

                    if( scvQclearTime > 0 && diff.toMillis() > scvQclearTime) { // 일정시간 미사용으로 판단
                        sendQueue.get(idx).clear();
                        logger.info("[MainController] serviceQClear : " + (idx +1) +"번 Queue 장기 미사용으로 초기화 (" + iQcnt +"건), 설정시간:" + scvQclearTime);
                    }
                    else if( diff.toMillis() > scvQdiffTime) { // 데이터 과적재

                        iQcnt = sendQueue.get(idx).size();
                        int iPollCnt = iQcnt - scvQmaxCount;

                        while (iQcnt - scvQmaxCount >= 0) {
                            sendQueue.get(idx).poll();
                            iQcnt--;
                        }

                        logger.info("[MainController] serviceQpoll : " + (idx +1) +"번 Queue 일부 버림 (" + iPollCnt +"건), 설정시간:" + scvQdiffTime + " 설정건수:"+ scvQmaxCount);
                    }
                }
            }


            Thread.sleep(1000);
        } catch (InterruptedException e) {
            logger.error("[MainController] serviceQClear-InterruptedException : " + AsdeUtil.getStatckTrace(e));

        } catch (Exception e) {
            logger.error("[MainController] serviceQClear-Exception : " + AsdeUtil.getStatckTrace(e) + ">> " + sRecvTime.toString() );
        }
    }

    @Override
    public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
        this.stop();
        logger.info("====================== SYSTEM STOPED ======================");
    }



    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

}
