package gmt.io.udp;

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

import gmt.common.type.ActiveModeType;
import gmt.common.type.LogLevelType;
import gmt.common.type.MonitorUpdateType;
import gmt.common.type.NetStatType;
import gmt.common.type.SocketType;
import gmt.common.util.GmtUtils;
import gmt.common.vo.GMTResultVO;
import gmt.common.vo.MonitorInfoVO;
import gmt.common.vo.MsgObjVO;
import gmt.common.vo.NetStatVO;
import gmt.common.vo.SystemInfoVO;
import gmt.io.udp.event.GmtUDPEventListener;
import gmt.logger.GmtLogManager;
import gmt.protocol.event.GmtEncryptEventListener;
import ipworks.IPWorksException;
import ipworks.Mcast;
import ipworks.McastDataInEvent;
import ipworks.McastErrorEvent;
import ipworks.McastEventListener;
import ipworks.McastReadyToSendEvent;
import ipworks.Udpport;
import ipworks.UdpportDataInEvent;
import ipworks.UdpportErrorEvent;
import ipworks.UdpportEventListener;
import ipworks.UdpportReadyToSendEvent;

public class GmtUDPSocket {
	private int				id;
	private int				tag;
	private String			name;
	private String			primaryIP;
	private String			secodaryIP;
	private int				port;
	private ActiveModeType activeSocket;
	private boolean			lineMode;
	private String			inOut;
	private String		    multiCastGroup;
	private int				packetType;
	private int				recvMsgCnt;
	private LocalDateTime	recvdt;
	private LocalDateTime	senddt;
	private boolean			use;
	private boolean			reconnUse;
	private int				interval;
	private boolean			doResend;
	private boolean			isRunning;
	private MonitorInfoVO   monitorInfoVO;
	private NetStatVO		netStatVO;
	private MsgObjVO		reSendVO;
	private GmtLogManager   logger;
	
	//private final Udpport 	udpport;
	//private final Mcast     mcast;
	private final Mcast 	udpport;
	
	private final Queue<MsgObjVO>	recvQ;
	private final Queue<MsgObjVO>	sendQ;
	private final Thread			sendThread;	
	private GmtUDPEventListener	eventListener;
	private GmtEncryptEventListener encryptListener;
	private GmtEncryptEventListener decryptListener;

	
	public GmtUDPSocket(Queue<MsgObjVO> recvQ){
		
		id            = -1;
		tag           = 0;
		name          = "";
		primaryIP     = "";
		secodaryIP    = "";
		activeSocket  = ActiveModeType.PRIMARY;
		lineMode      = true;
		inOut 		  = "recv";
		multiCastGroup = "";
		packetType    = 0;
		recvMsgCnt    = 0;
		recvdt        = LocalDateTime.now();
		senddt        = LocalDateTime.now();
		use			= true;
		reconnUse     = true;
		interval      = 0;
		doResend      = false;
		isRunning     = true;
		netStatVO     = new NetStatVO();
		monitorInfoVO = new MonitorInfoVO();
		this.recvQ    = recvQ;
		
		logger = GmtLogManager.getInstance();		
		sendQ  = new LinkedBlockingQueue<MsgObjVO>(100000);		
		
		udpport = new Mcast();
		//mcast = new Mcast();
		
		try {	
			udpport.setRuntimeLicense("31504A42564131535542524131535542414E44323132323200000000000000000000000000000000505A4E384E5758350000325242435038313258424E480000");
			//udpport.addUdpportEventListener(new UdpportEventListener() {
			udpport.addMcastEventListener(new McastEventListener() {
//				@Override
//				public void dataIn(UdpportDataInEvent e) {
//			
//					processRcvData(e);
//					if (eventListener != null) {
//						eventListener.dataIn(port, udpport.getRemoteHost(), udpport.getRemotePort(), new String(e.datagram));
//					}
//				}
//
//				@Override
//				//public void error(UdpportErrorEvent e) {
//				public void error(McastErrorEvent e) {
//					if (eventListener != null) {
//						eventListener.error(udpport.getRemoteHost(), udpport.getRemotePort(), e.errorCode, e.description);
//					}	
//				}
//
//				// ReadyToSend 이벤트는 기본 TCP/IP 하위 시스템이 DataToSend 실패 후 데이터를 수락할 준비가 되었음을 나타냅니다.
//				@Override
//				public void readyToSend(UdpportReadyToSendEvent e) {
//					if (eventListener != null) {
//						eventListener.readyToSend();
//					}
//				}	
				
				@Override
				public void dataIn(McastDataInEvent e) {
					
					processRcvData(e);
					if (eventListener != null) {
						eventListener.dataIn(port, udpport.getRemoteHost(), udpport.getRemotePort(), new String(e.datagram));
					}
					
				}

				@Override
				public void error(McastErrorEvent e) {
					if (eventListener != null) {
						eventListener.error(udpport.getRemoteHost(), udpport.getRemotePort(), e.errorCode, e.description);
					}	
					
				}

				@Override
				public void readyToSend(McastReadyToSendEvent e) {
					if (eventListener != null) {
						eventListener.readyToSend();
					}
				
					
				}

				
			});
		} catch (Exception ex) {
			
			System.out.println("UDP EventListener error : " + ex.getMessage());
		}
		
		sendThread = new Thread(new Runnable() {			
			@Override
			public void run() {
				while(isRunning) {
					try {
						udpSendMsg();
						Thread.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}				
			}
		});		
		sendThread.setName("GmtClient-sendThread");
		sendThread.start();		
		
	} // GmtUDP end
	
	
	
	//public void processRcvData(UdpportDataInEvent data) {
	public void processRcvData(McastDataInEvent data) {
		try {
			recvdt = LocalDateTime.now();
			
			if (recvQ == null) {
				return;
			}
			
			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.setRecvSocketType(SocketType.UDP);
			//vo.setMsg(data.text);
			vo.setMsg(data.datagram);
	
			recvMsgCnt = recvMsgCnt +1;
			recvdt     = LocalDateTime.now();
			
			updateMonitorInfo(vo.getMsg(), MonitorUpdateType.RECV);
			
			if (decryptListener != null) {
				vo.setMsg(decryptListener.getByteArray(this.id, vo.getMsg()));
			}
			
			recvQ.offer(vo);	
			vo = null;
			
		} catch(Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "FileLog");
		}
	}
	
	public synchronized void updateMonitorInfo(byte[] msg, MonitorUpdateType monitorType) {
		try {
		switch(monitorType) {
			case SEND:
				monitorInfoVO.increseSendCount(1);
				monitorInfoVO.increseSendByte(msg.length);
				monitorInfoVO.setLastSendDT(LocalDateTime.now());
				break;
				
			case RECV:
				monitorInfoVO.increseRecvCount(1);
				monitorInfoVO.increseRecvByte(msg.length);
				monitorInfoVO.setLastRecvDT(LocalDateTime.now());
				break;
				
			case UPDATE:
				monitorInfoVO.settleUpMonitorInfo();
				break;
			}
		} catch(Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}
	
	private void udpSendMsg() {
		
		if (!udpport.isActive()) {
			sendQ.clear();
			netStatVO.setStat(NetStatType.DISCONNECT);
			return;
		}
		
		if (sendQ == null) {
			return;
		}
		
		int cnt = sendQ.size();						
		if (cnt > 1000) {
			cnt = 1000; 
		}
				
		for (int i = 0; i <= cnt-1; i++) {
			try {
				int resultCode = 0;
				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);
						return;
					}				
					resultCode = sendMsg(reSendVO);
					processSendResult(resultCode, reSendVO, true);
				}
			} catch(Exception e) {
				
			}
		}	
	}
	
	private int sendMsg(MsgObjVO vo) {
		int resultCode = -1;
		if (udpport.isActive()) {			
			try {
				
				if (encryptListener != null) {
					vo.setMsg(encryptListener.getByteArray(this.id, vo.getMsg()));
				}
				//udpport.setDataToSend(vo.getMsg());
				udpport.send(vo.getMsg());
				if (lineMode) {
					udpport.send("\r\n".getBytes());
				}

				resultCode = 0;
//			} catch (IPWorksException e) {
//				resultCode = e.getCode();
//				if (e.getCode() == GmtUtils.NET_WOULD_BLOCK) {
//					logger.writeLevelLog("[CLIENT]["+ udpport.getRemoteHost() +"]["+ String.valueOf(udpport.getRemotePort()) +"] Operation Would Block..", LogLevelType.LOG_ERROR, "AllLog");
//				}
//			}
				
			} catch (IPWorksException e) {
				resultCode = e.getCode();
				logger.writeLevelLog("[UDP Socket]["+ udpport.getRemoteHost() +"]["+ String.valueOf(udpport.getRemotePort()) +"] (" + e.getCode() + ") " + e.getMessage() , LogLevelType.LOG_ERROR, "AllLog");
			}
		}
		return resultCode;		
	}
	
	
	private void processSendResult(int resultCode, MsgObjVO vo, boolean isReSend) {
		try {
			switch(resultCode) {
			
				case GmtUtils.NET_SEND_OK :
					netStatVO.setStat(NetStatType.SEND_OK);
					netStatVO.setWouldBlockFromTime(GmtUtils.defaultTime());
					netStatVO.setWouldBlockToTime(GmtUtils.defaultTime());
					netStatVO.setDescription("");
					netStatVO.setLastSendTime(LocalDateTime.now());
					netStatVO.setRetryCount(0);
					updateMonitorInfo(vo.getMsg(), MonitorUpdateType.SEND);
					break;
				
//				case GmtUtils.NET_WOULD_BLOCK :
//					if (isReSend) {
//						reSendVO = null;
//					} else {
//						reSendVO = vo;
//					}
//					
//					netStatVO.setStat(NetStatType.WOULDBLOCK);
//					if (netStatVO.getWouldBlockFromTime().isEqual(GmtUtils.defaultTime())) {
//						netStatVO.setWouldBlockFromTime(LocalDateTime.now());
//					}
//					netStatVO.setWouldBlockToTime(LocalDateTime.now());
//					netStatVO.setDescription("Operation Would Block");
//					if(netStatVO.getLastSendTime().isEqual(GmtUtils.defaultTime())){
//						netStatVO.setLastSendTime(LocalDateTime.now());
//					}
//					
//					Thread.sleep(10);
//					break;
				
				default :
					if (isReSend) {
						reSendVO = null;
					} else {
						reSendVO = vo;						
					}
					
					netStatVO.setStat(NetStatType.DISCONNECT);
					netStatVO.setWouldBlockFromTime(GmtUtils.defaultTime());
					netStatVO.setWouldBlockToTime(GmtUtils.defaultTime());
					netStatVO.setDescription("UDP Error - code="+ resultCode );
					
					if (netStatVO.getRetryCount() == 0) {
						netStatVO.setRetryCount(netStatVO.getRetryCount() +1);
						netStatVO.setLastSendTime(LocalDateTime.now());
					}
					
					Thread.sleep(10);
					break;
			}
		} catch(Exception e) {
			logger.writeLevelLog("[UDP] Process send result error : ", LogLevelType.LOG_ERROR, "AllLog");
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}
	
	
	public GMTResultVO activeSocket(String ip, int port) {				
		GMTResultVO vo = new GMTResultVO();
		
		if (udpport == null) {
			vo.setSuccess(false);
			vo.setDescription("UDP socket declare");
			return vo;
		}
		
		try {
			udpport.setActive(false);
//			ipport.setTimeout(10);		
//			ipport.setLinger(true);
//			
//			if (lineMode) {
//				ipport.setEOL("\r\n".getBytes());
//			} else {
//				ipport.setEOL("");
//			}
			
			if(inOut.equals("send")) {
				udpport.setRemoteHost(ip);
				udpport.setRemotePort(port);
				
			}else {
				udpport.setLocalPort(port);
			}
			
			//udpport.config("MaxPacketSize=1000000");
			
			
			//udpport.config("UseConnection");
			udpport.setActive(true);
			
			udpport.setMulticastGroup(multiCastGroup);
			logger.writeLevelLog("[UDP] multiCastGroup : " + multiCastGroup + " port:"+port, LogLevelType.LOG_ERROR, "AllLog");
			
			//test code
			//udpport.send("\r\nGMT active OK".getBytes());
			
		} catch (IPWorksException e) {
			vo.setSuccess(false);
			vo.setDescription("UDP active fail : " + e.getMessage());
			return vo;
		}
						
		vo.setSuccess(true);
		vo.setStatusCode("0");
		vo.setDescription("OK");
		
		return vo;
	}
	
	
	// Client : disconnect
	public boolean deActive() {		
		try {
			udpport.setActive(false);
		} catch (IPWorksException e) {			
			e.printStackTrace();
			return false;
		}
		
		return true;		
	}
	
	
	public void addSendMsg(MsgObjVO vo) {
		if (vo != null && sendQ != null && udpport.isActive()) {
			sendQ.offer(vo);
		}
	}
	
	// Client : changeConnection
	public GMTResultVO reActive() {
		String ip   = "";
		
		GMTResultVO vo = new GMTResultVO();
		
		if (udpport == null) {
			vo.setSuccess(false);
			vo.setDescription("UDP socket declare");
			return vo;
		}
		
		switch(activeSocket) {
		case PRIMARY : 
			ip = this.secodaryIP;
			activeSocket = ActiveModeType.SECONDARY;
			break;
			
			
		case SECONDARY : 
			ip = this.primaryIP;
			activeSocket = ActiveModeType.PRIMARY;
			break;
			
		}
		
		try {
			udpport.setActive(false);
			
			if(inOut.equals("send")) {
				udpport.setRemoteHost(ip);
				udpport.setRemotePort(port);
				
			}else {
				udpport.setLocalPort(port);
			}
			
			udpport.setActive(true);
			
			udpport.setMulticastGroup(multiCastGroup);
			logger.writeLevelLog("[UDP] re-active multiCastGroup : " + multiCastGroup + " port:"+port, LogLevelType.LOG_ERROR, "AllLog");
			
			vo.setSuccess(true);
			vo.setDescription("re active success");
			
			//test code
			//udpport.send("\r\nGMT Re-active OK".getBytes());
			
		} catch (IPWorksException e) {
			vo.setSuccess(false);
			vo.setStatusCode(String.valueOf(e.getCode()));
			vo.setDescription(e.getMessage());
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
		
		return vo;
	}	
	
	public boolean equalsIP(String ip) {
		if (ip.equals(primaryIP) || ip.equals(secodaryIP)) {
			return true;
		} else {
			return false;
		}
	}
	
	public String getCurrentIP() {
		return udpport.getRemoteHost();
	}
	
	public boolean isActive() {
		return udpport.isActive();
	}
	
	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;
		sendThread.setName("GmtClient-sendThread:" + port);
	}

	public int getLocalPort() {
		return this.udpport.getLocalPort();
	}
	
	public ActiveModeType getActiveSocket() {
		return activeSocket;
	}

	public void setActiveSocket(ActiveModeType activeSocket) {
		this.activeSocket = activeSocket;
	}

	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 int getPacketType() {
		return packetType;
	}

	public void setPacketType(int packetType) {
		this.packetType = packetType;
	}
	
	public boolean isUse() {
		return use;
	}

	public void setUse(boolean use) {
		this.use = use;
	}
	
	public int getRecvMsgCnt() {
		return recvMsgCnt;
	}

	public void setRecvMsgCnt(int recvMsgCnt) {
		this.recvMsgCnt = recvMsgCnt;
	}

	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 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 boolean isDoResend() {
		return doResend;
	}

	public void setDoResend(boolean doResend) {
		this.doResend = doResend;
	}
	
	public void setEventListener(GmtUDPEventListener eventListener) {
		this.eventListener = eventListener;
	}

	public boolean isRunning() {
		return isRunning;
	}

	public void setRunning(boolean isRunning) {
		this.isRunning = isRunning;
	}
	
	public synchronized MonitorInfoVO getMonitorInfoVO() {
		return monitorInfoVO;
	}
	
	public int getSendQCount() {
		return sendQ.size();
	}
	
	public int getSendBufferCount() {
		return new SystemInfoVO().getBufSize();
	}
	
	public int getRecvQCount() {
		return recvQ.size();
	}
	
	public void setEncryptListener(GmtEncryptEventListener encryptListener) {
		this.encryptListener = encryptListener;
	}
	
	public void setDecryptListener(GmtEncryptEventListener decryptListener) {
		this.decryptListener = decryptListener;
	}

}