package kr.gmtc.comp.socket.udp;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import kr.gmtc.comp.socket.event.EncryptEventListener;
import kr.gmtc.comp.socket.type.ActiveModeType;
import kr.gmtc.comp.socket.utils.SocketUtil;
import kr.gmtc.comp.socket.vo.MsgObjVO;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.concurrent.ArrayBlockingQueue;

public class UDPManager {
    private String path;
    private Logger logger;
    
    private ArrayBlockingQueue<MsgObjVO> sendQ;
    private Thread checkSocketThread;
    
    private HashMap<Integer, UDPSocket> udpSockets;
    private UDPEventListener listener;
    private boolean isRunning;

    private EncryptEventListener encryptListener;
	private EncryptEventListener decryptListener;


    public UDPManager(String path, UDPEventListener listener) {

        this.path     = path;
        this.logger   = LoggerFactory.getLogger(UDPManager.class);

		sendQ = new ArrayBlockingQueue<MsgObjVO>(100000);
		
        this.isRunning  = true;
        
        udpSockets = new HashMap<Integer, UDPSocket>();

        checkSocketThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while(isRunning) {
                    try {
                    	checkReconnect();
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        logger.error(SocketUtil.getStatckTrace(e));
                    }
                }
            }
        });

        checkSocketThread.setName("UDPManager-checkSocketThread");

        // 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));
        //             }
        //         }
        //     }
        //});
        
        initializeUDPFromXML();
    }
    
    public void start() {
        isRunning = true;
        logger.info("====================== SYSTEM Start ======================");
        
        startClient();
        // recvQThread.start();
        // sendQThread.start();
        
	}
    
    public void stop() {
        isRunning = false;
        
    }


    public void initializeUDPFromXML() {
        if (path.equals("")) {
            path = System.getProperty("user.dir");
        }

        File xmlFile = new File(path+"/cfg/udpsocket.xml");

        if (!xmlFile.exists()) {
            logger.error("[UDPManager] Load fail .. : "+ path+"\\cfg\\udpsocket.xml");
            return;
        }

        logger.info("[UDPManager] Load file .. : "+ path+"\\cfg\\udpsocket.xml");

        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.parse(xmlFile);

            NodeList nodes = doc.getElementsByTagName("UdpSocket");

            for(int i = 0; i < nodes.getLength(); i++ ) {
                Node udpSocketNode = nodes.item(i);


                int     id             = -1;
                int     tag            = 0;
                boolean use            = false;
                String  primaryIP      = "";
                String  secondaryIP    = "";
                int     port           = 0;
                String  name           = "";
                boolean lineMode       = true;
                boolean reconnUse      = true;
                int     reconnInterval = 60;
                String  inout          = "";
                String  mcastgroup     = "";

                for(Node child = udpSocketNode.getFirstChild(); child != null; child = child.getNextSibling()) {
                    if (child.getNodeType() != Node.ELEMENT_NODE) {
                        continue;
                    }
                    try {
                        Element childElement = (Element) child;

                        if(childElement.getNodeName().equals("Connection")) {
                            id 			= SocketUtil.strToIntDef(childElement.getAttribute("ID"), -1);
                            tag 		= SocketUtil.strToIntDef(childElement.getAttribute("TAG"), -1);
                            primaryIP 	= childElement.getAttribute("PIP");
                            secondaryIP = childElement.getAttribute("SIP");
                            port        = SocketUtil.strToIntDef(childElement.getAttribute("PORT"), 0);
                            use 		= Boolean.parseBoolean(childElement.getAttribute("USE"));
                            name        = childElement.getAttribute("Name");
                        }
                        else if(child.getNodeName().equals("Option")) {
                            lineMode    = Boolean.parseBoolean(childElement.getAttribute("LineMode"));
                            inout    = childElement.getAttribute("InOut");
                            mcastgroup = childElement.getAttribute("McastGrp");
                        }
                        else if(child.getNodeName().equals("Reconnect")) {
                            reconnUse      = Boolean.parseBoolean(childElement.getAttribute("ReconnUSE"));
                            reconnInterval = SocketUtil.strToIntDef(childElement.getAttribute("Interval"), 60);
                        }
                    } catch (Exception e) {
                        logger.error(SocketUtil.getStatckTrace(e));
                        continue;
                    }
                } 

                if (!use || primaryIP.equals("") || port == 0 || id == -1)  {
                    continue;
                }

                UDPSocket udpSocket = new UDPSocket();
                udpSocket.setId(id);
                udpSocket.setName(name);
                udpSocket.setPrimaryIP(primaryIP);
                udpSocket.setSecodaryIP(secondaryIP);
                udpSocket.setPort(port);
                udpSocket.setUse(use);
                udpSocket.setLineMode(lineMode);
                udpSocket.setInOut(inout);
                udpSocket.setMultiCastGroup(mcastgroup);
                udpSocket.setReconnUse(reconnUse);
                udpSocket.setInterval(reconnInterval);
                udpSocket.setEventListener(listener);		
				udpSocket.setEncryptListener(encryptListener);
				udpSocket.setDecryptListener(decryptListener);

                if (!udpSockets.containsKey(udpSocket.getId())) {

                    String msg = "[UDP Socket] ID : "+ String.valueOf(udpSocket.getId()) +" IP : "+ udpSocket.getPrimaryIP() +" PORT : "+ String.valueOf(udpSocket.getPort());
                    logger.info(msg);

                    udpSockets.put(udpSocket.getId(),  udpSocket);
                }
            }
        } catch (Exception e) {
            logger.error(SocketUtil.getStatckTrace(e));
        }
    }

    public void startClient() {
        for (UDPSocket udpSocket : udpSockets.values()) {
            if (udpSocket == null) {
                continue;
            }

            if (!udpSocket.isUse()) {
                continue;
            }

            int isActive = udpSocket.activeSocket(udpSocket.getPrimaryIP(), udpSocket.getPort());
            String msg = "";
            if (isActive == 0) {
                msg = "[UDP Manager][CONNECTED - " + udpSocket.getActiveModeName() + "]["+ udpSocket.getPrimaryIP() +"]["+ String.valueOf(udpSocket.getPort()) +"] ";
            } else {
                msg = "[UDP Manager][ERROR- " + udpSocket.getActiveModeName() + "]["+ udpSocket.getPrimaryIP() +"]["+ String.valueOf(udpSocket.getPort()) +"] ";
            }
            logger.info(msg);
        }
        if (checkSocketThread != null) {
            checkSocketThread.start();
        }
    }


    private void checkReconnect() {
        for(UDPSocket udpSocket : udpSockets.values()) {
            if (udpSocket == null) {
                continue;
            }
            
            boolean isReconn = false;
            LocalDateTime lastRecvDT = udpSocket.getRecvdt();
            Duration duration = Duration.between(lastRecvDT, LocalDateTime.now());

            String logMsg = "";
            if (!udpSocket.isActive()) {
                isReconn = true;
            } else if(udpSocket.isReconnUse() && duration.getSeconds() >= udpSocket.getInterval()) {
                isReconn = true;
                logMsg = "[UDP Manager] Messages are not received for "+ String.valueOf(udpSocket.getInterval()) +" seconds";
            }

            if (isReconn) {
//				String logMsg = "";
            	
            	ActiveModeType activeSocket = udpSocket.getActiveModeType();
                String sActiveName = udpSocket.getActiveModeName();

                if (activeSocket == ActiveModeType.PRIMARY) {
                    logMsg = "[UDP Manager]["+ sActiveName +"-"+ udpSocket.getSecodaryIP() +":"+ String.valueOf(udpSocket.getPort()) +"(P -> S)] "+udpSocket.getName() +" Change connected.."+ logMsg;
                } else {
                    logMsg = "[UDP Manager]["+ sActiveName +"-"+ udpSocket.getPrimaryIP()  +":"+ String.valueOf(udpSocket.getPort()) +"(S -> P)] "+udpSocket.getName() +" Change connected.."+ logMsg;
                }

                logger.info(logMsg);

                int isActive = udpSocket.changeActive();

                if (isActive == 0) {
                    udpSocket.setRecvdt(LocalDateTime.now());
                    
                    logMsg = "["+ udpSocket.getCurrentIP() +":"+ String.valueOf(udpSocket.getPort()) +
                            "("+ String.valueOf(udpSocket.getActiveModeType()) +")] "+
                            udpSocket.getName() +" Connected.. ";
                } else {
                    logMsg = "["+ udpSocket.getCurrentIP() +":"+ String.valueOf(udpSocket.getPort()) +
                            "("+ String.valueOf(udpSocket.getActiveModeType()) +")] "+
                            udpSocket.getName() +" Connect fail.. ";
                }
                logger.info(logMsg);
            }
        }
    }

    public boolean disconnect(int id) {
        boolean result = false;

        try {
            UDPSocket udpSocket = this.udpSockets.get(id);
            if (udpSocket != null) {
                result = udpSocket.inActive();
            }
        } catch(Exception e) {
            logger.error("[GmtUDPManager] udp socket deactive fail : "+ SocketUtil.getStatckTrace(e));
        }

        return result;
    }

    public boolean disconnect(String ip, int port) {
        boolean result = false;

        try {
            for(UDPSocket udpSocket : this.udpSockets.values()) {
                if( (ip.equals(udpSocket.getPrimaryIP()) || ip.equals(udpSocket.getSecodaryIP())) && udpSocket.getPort() == port ) {
                    result = udpSocket.inActive();
                    break;
                }
            }
        } catch(Exception e) {
            logger.error("[GmtUDPManager] udp socket deactive fail : "+ SocketUtil.getStatckTrace(e) );
        }

        return result;
    }
    
    
//     private void recvDataIn(MsgObjVO vo) {
//         try {
// //            udpDataIn(vo);
            
// //            if (udpListener != null) {
// //            	udpListener.udpDataIn(vo);
// //            }
            
//         } catch (Exception e) {
//             logger.error(SocketUtil.getStatckTrace(e));
//         }

//     }



    public void broadcastMsg(MsgObjVO vo) {
        for (UDPSocket udpSocket : udpSockets.values()) {
            if (udpSocket.equalsIP(vo.getRecvIP()) && udpSocket.getPort() == vo.getRecvPort()) {
                continue;
            }
            MsgObjVO newVO = vo.getClone();

            udpSocket.addSendMsg(newVO);
        }
    }
    
    public void sendMsg(String clientIP, int clientPort, MsgObjVO vo) {
        for (UDPSocket udpSocket : udpSockets.values()) {
            if (udpSocket.equalsIP(vo.getRecvIP()) && udpSocket.getPort() == vo.getRecvPort()) {
                continue;
            }

            if (udpSocket.equalsIP(clientIP) && udpSocket.getPort() == clientPort) {
                MsgObjVO newVO = vo.getClone();

                udpSocket.addSendMsg(newVO);
                break;
            }
        }
    }


    public void setEncryptListener(EncryptEventListener encryptListener) {
		this.encryptListener = encryptListener;
	}
	
	public void setDecryptListener(EncryptEventListener decryptListener) {
		this.decryptListener = decryptListener;
	}

}
