package kr.gmtc.comp.socket.udp;

import java.time.LocalDateTime;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ipworks.IPWorksException;
import ipworks.Mcast;
import ipworks.McastDataInEvent;
import ipworks.McastErrorEvent;
import ipworks.McastEventListener;
import ipworks.McastReadyToSendEvent;
import kr.gmtc.comp.socket.event.EncryptEventListener;
import kr.gmtc.comp.socket.type.ActiveModeType;
import kr.gmtc.comp.socket.type.NetStatType;
import kr.gmtc.comp.socket.utils.SocketUtil;
import kr.gmtc.comp.socket.vo.MsgObjVO;
import kr.gmtc.comp.socket.vo.NetStatVO;

public class UDPSocket {

    protected Logger logger;

    private final Mcast udpport;
    private final Thread sendThread;	

    private int				id;
	private int				tag;
	private String			name;
	private String			primaryIP;
	private String			secodaryIP;
	private int				port;
	private ActiveModeType  activeModeType;
	private boolean			lineMode;
	private String			inOut;
	private String		    multiCastGroup;
	private LocalDateTime	recvdt;
	private LocalDateTime	senddt;
	private boolean			use;
	private boolean			reconnUse;
	private int				interval;
	private boolean			doResend;
	private boolean			isRunning;

    private MsgObjVO reSendVO;
    private NetStatVO netStatVO;

    //private final Queue<MsgObjVO> recvQ;
    private final Queue<MsgObjVO> sendQ;

    private EncryptEventListener encryptListener;
    private EncryptEventListener decryptListener;
    private UDPEventListener	eventListener;

    //public UDPSocket(Queue<MsgObjVO> recvQ){
    public UDPSocket(){

        logger = LoggerFactory.getLogger(this.getClass());

        netStatVO = new NetStatVO();
    
        udpport = new Mcast();

        id            = -1;
        name          = "";
        primaryIP     = "";
        secodaryIP    = "";
        activeModeType = ActiveModeType.PRIMARY;
        lineMode      = true;
        multiCastGroup = "";
        recvdt        = LocalDateTime.now();
        senddt        = LocalDateTime.now();
        isRunning     = true;
        reconnUse     = true;
        use			  = true;
        interval      = 0;
        inOut 		  = "RECV";
        //this.recvQ    = recvQ;

        sendQ = new LinkedBlockingQueue<MsgObjVO>(100000);

        try {	
			udpport.setRuntimeLicense("31504A42564131535542524131535542414E44323132323200000000000000000000000000000000505A4E384E5758350000325242435038313258424E480000");

			udpport.addMcastEventListener(new McastEventListener() {
				
				@Override
				public void dataIn(McastDataInEvent e) {
					processRcvData(e);
				}

				@Override
				public void error(McastErrorEvent e) {}

				@Override
				public void readyToSend(McastReadyToSendEvent e) {}

				
			});
		} catch (Exception ex) {
			logger.error("[UDP Socket] EventListener error : " + ex.getMessage());
		}

        sendThread = new Thread(new Runnable() {			
			@Override
			public void run() {
				while(isRunning) {
					try {
						udpSendMsgQue();
						Thread.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}				
			}
		});		
		sendThread.setName("UDP-Socket-sendThread");
		sendThread.start();	
        
    }

    public int activeSocket(String ip, int port){
        if(udpport == null){
            logger.error("[UDP Socket] declare");
            return -1;
        }

        try {
            udpport.setActive(false);
            if(inOut.equals("SEND")) {

                udpport.setRemoteHost(ip);
                udpport.setRemotePort(port);
                udpport.setActive(true);

            }else{
                udpport.setActive(true);

                if( !(multiCastGroup == null || multiCastGroup.equals("")) )
                    udpport.setMulticastGroup(multiCastGroup);

                logger.info("[UDP Socket] MultiCalstGroup : " + multiCastGroup + " port:" + port);
            }

            logger.info("[UDP Socket] " + inOut + " : " + ip + ":" + port);

        }catch (IPWorksException e){
            logger.error("[UDP Socket] active fail : " + e.getMessage() );
            return -1;
        }

        return 0;
    }

    public boolean inActive(){

        try{
            udpport.setActive(false);
        } catch(IPWorksException e) {
            logger.error("[UDP Socket] inActive fail : " + e.getMessage() );
            return false;
        }

        return true;
    }

    public int changeActive(){
        if(udpport == null){
            logger.error("UDP socket declare");
            return -1;
        }

        String ip = "" ;

        switch (activeModeType) {
            case PRIMARY:
                ip = this.secodaryIP;
                activeModeType = ActiveModeType.SECONDARY;
                break;

            case SECONDARY:
                ip = this.primaryIP;
                activeModeType = ActiveModeType.PRIMARY;
                break;

        }

        int iActive = activeSocket(ip, port);

        return iActive;
    }
    
    public void processRcvData(McastDataInEvent data) {
		try {
			recvdt = LocalDateTime.now();

			MsgObjVO vo = new MsgObjVO();
					
			vo.setMsgSeq(0);
			vo.setRecvIP(udpport.getRemoteHost());
			vo.setRecvPort(udpport.getRemotePort());
			vo.setRecvID(id);
			vo.setRecvTag(tag);
			vo.setRecvDT(LocalDateTime.now());
			vo.setRecvServerPort(port);
			vo.setMsg(data.datagram);
	
			recvdt = LocalDateTime.now();
			
			if (decryptListener != null) {
				vo.setMsg(decryptListener.getByteArray(this.id, vo.getMsg()));
			}
			//recvQ.offer(vo);
            if (eventListener != null) {
                eventListener.udpDataIn(vo);
            }

            vo = null;
			
		} catch(Exception e) {
			logger.error(SocketUtil.getStatckTrace(e));
		}
	}

    private int udpSendMsgQue(){

        int resultCode = -1;

        if (!udpport.isActive()) {
			sendQ.clear();
			netStatVO.setStat(NetStatType.DISCONNECT);
			return resultCode;
		}

        if (sendQ == null) return -1;

        int cnt = sendQ.size();
        if(cnt > 1000){
            cnt = 1000;
        }

        for(int i = 0; i <= cnt-1; i++) {
            try{
                if( netStatVO.getStat() == NetStatType.NONE || netStatVO.getStat() == NetStatType.SEND_OK || !doResend){
                    MsgObjVO vo = sendQ.poll();
                    if (vo != null) {
						resultCode = sendMsg(vo);
						processSendResult(resultCode, vo, false);																					
					}
                }else{
                    if (reSendVO == null) {
						netStatVO.setStat(NetStatType.NONE);
						resultCode = 0;
					}				
					resultCode = sendMsg(reSendVO);
					processSendResult(resultCode, reSendVO, true);
                }
            }catch (Exception e) {
                logger.error("[UDP Socket] udpSendMsgQue error " + e.getMessage());
            }
        }
        return resultCode;
    }

    private int sendMsg(MsgObjVO vo) {
		int resultCode = -1;
		if (udpport.isActive()) {			
			try {
				
				if (encryptListener != null) {
					vo.setMsg(encryptListener.getByteArray(this.id, vo.getMsg()));
				}

				udpport.send(vo.getMsg());

				if (lineMode) {
					udpport.send("\r\n".getBytes());
				}

				resultCode = 0;
				
			} catch (IPWorksException e) {
				resultCode = e.getCode();

				logger.error("[UDP Socket]["+ udpport.getRemoteHost() +"]["+ String.valueOf(udpport.getRemotePort()) +"] (" + e.getCode() + ") " + e.getMessage() );
			}
		}
		return resultCode;		
	}

    private void processSendResult(int resultCode, MsgObjVO vo, boolean isReSend) {
		try {
			switch(resultCode) {
			
				case SocketUtil.NET_SEND_OK :
					netStatVO.setStat(NetStatType.SEND_OK);
					netStatVO.setWouldBlockFromTime(SocketUtil.defaultTime());
					netStatVO.setWouldBlockToTime(SocketUtil.defaultTime());
					netStatVO.setDescription("");
					netStatVO.setLastSendTime(LocalDateTime.now());
					netStatVO.setRetryCount(0);
					break;
				
				case SocketUtil.NET_WOULD_BLOCK :
					if (isReSend) {
						reSendVO = null;
					} else {
						reSendVO = vo;
					}
					
					netStatVO.setStat(NetStatType.WOULDBLOCK);
					if (netStatVO.getWouldBlockFromTime().isEqual(SocketUtil.defaultTime())) {
						netStatVO.setWouldBlockFromTime(LocalDateTime.now());
					}
					netStatVO.setWouldBlockToTime(LocalDateTime.now());
					netStatVO.setDescription("Operation Would Block");
					if(netStatVO.getLastSendTime().isEqual(SocketUtil.defaultTime())){
						netStatVO.setLastSendTime(LocalDateTime.now());
					}
					
					Thread.sleep(10);
					break;
				
				default :
					if (isReSend) {
						reSendVO = null;
					} else {
						reSendVO = vo;						
					}
					
					netStatVO.setStat(NetStatType.DISCONNECT);
					netStatVO.setWouldBlockFromTime(SocketUtil.defaultTime());
					netStatVO.setWouldBlockToTime(SocketUtil.defaultTime());
					netStatVO.setDescription("Socket Error");
					
					if (netStatVO.getRetryCount() == 0) {
						netStatVO.setRetryCount(netStatVO.getRetryCount() +1);
						netStatVO.setLastSendTime(LocalDateTime.now());
					}
					
					Thread.sleep(10);
					break;
			}
		} catch(Exception e) {
            logger.error("[UDP Socket] Process send result error : " + SocketUtil.getStatckTrace(e));
		}
	}


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

    public boolean isActive() {
        return udpport.isActive();
    }

    public String getCurrentIP() {
        return udpport.getRemoteHost();
    }

    public boolean equalsIP(String ip) {
        if (ip.equals(primaryIP) || ip.equals(secodaryIP)) {
            return true;
        } else {
            return false;
        }
    }

    public void addSendMsg(MsgObjVO vo) {
        if (vo != null && this.sendQ != null && udpport.isActive()) {
            this.sendQ.offer(vo);
        }
    }

    public String getActiveModeName() {
        return activeModeType.getName();
    }


    ///////////// Getter Setter ////////////////////////////
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getTag() {
        return tag;
    }

    public void setTag(int tag) {
        this.tag = tag;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPrimaryIP() {
        return primaryIP;
    }

    public void setPrimaryIP(String primaryIP) {
        this.primaryIP = primaryIP;
    }

    public String getSecodaryIP() {
        return secodaryIP;
    }

    public void setSecodaryIP(String secodaryIP) {
        this.secodaryIP = secodaryIP;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public ActiveModeType getActiveModeType() {
        return activeModeType;
    }

    public void setActiveModeType(ActiveModeType activeModeType) {
        this.activeModeType = activeModeType;
    }

    public boolean isLineMode() {
        return lineMode;
    }

    public void setLineMode(boolean lineMode) {
        this.lineMode = lineMode;
    }

    public String getInOut() {
        return inOut;
    }

    public void setInOut(String inOut) {
        this.inOut = inOut;
    }

    public String getMultiCastGroup() {
        return multiCastGroup;
    }

    public void setMultiCastGroup(String multiCastGroup) {
        this.multiCastGroup = multiCastGroup;
    }

    public LocalDateTime getRecvdt() {
        return recvdt;
    }

    public void setRecvdt(LocalDateTime recvdt) {
        this.recvdt = recvdt;
    }

    public LocalDateTime getSenddt() {
        return senddt;
    }

    public void setSenddt(LocalDateTime senddt) {
        this.senddt = senddt;
    }

    public boolean isUse() {
        return use;
    }

    public void setUse(boolean use) {
        this.use = use;
    }

    public boolean isReconnUse() {
        return reconnUse;
    }

    public void setReconnUse(boolean reconnUse) {
        this.reconnUse = reconnUse;
    }

    public int getInterval() {
        return interval;
    }

    public void setInterval(int interval) {
        this.interval = interval;
    }

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

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

    public void setEventListener(UDPEventListener eventListener) {
        this.eventListener = eventListener;
    }

    

    
	
}