package gmt.io.udp;

import java.io.File;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Queue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import gmt.common.type.ActiveModeType;
import gmt.common.type.LogLevelType;
import gmt.common.type.MonitorUpdateType;
import gmt.common.util.GmtUtils;
import gmt.common.vo.GMTResultVO;
import gmt.common.vo.MsgObjVO;
import gmt.io.client.GmtClient;
import gmt.io.client.vo.ClientInfoVO;
import gmt.io.udp.event.GmtUDPEventListener;
import gmt.logger.GmtLogManager;
import gmt.protocol.event.GmtEncryptEventListener;
import gmt.protocol.event.GmtTestEventListener;

public class GmtUDPManager {
	
	private String path;
	private GmtLogManager logger;
	private Queue<MsgObjVO> recvQ;
	private Thread checkSocketThread;
	private GmtTestEventListener gmtTestEventListener;
	private HashMap<Integer, GmtUDPSocket> udpSockets;
	private GmtUDPEventListener listener;
	private GmtEncryptEventListener encryptListener;
	private GmtEncryptEventListener decryptListener;
	protected boolean running;
	
	
	public GmtUDPManager(Queue<MsgObjVO> recvQ, String path, GmtUDPEventListener listener) {
		
		this.path     = path;
		this.logger   = GmtLogManager.getInstance();
		this.listener = listener;
		this.recvQ    = recvQ;
		this.running  = true;
		
		if (this.recvQ == null) {
			this.recvQ = new LinkedBlockingQueue<>(100000);
		}		
		
		udpSockets = new HashMap<Integer, GmtUDPSocket>();
		
		checkSocketThread = new Thread(new Runnable() {			
			@Override
			public void run() {
				LocalDateTime reconnDT = LocalDateTime.now();				
				LocalDateTime checkDT  = LocalDateTime.now();
				LocalDateTime initDT = LocalDateTime.now();
				Duration      duration;
				while(running) {					
					try {
						duration = Duration.between(reconnDT, LocalDateTime.now());
						if (duration.getSeconds() >= 10) {
							reconnDT = LocalDateTime.now();
							checkReconnect();							
						}
						
						duration = Duration.between(checkDT, LocalDateTime.now());
						if (duration.getSeconds() >= 1) {
							checkDT = LocalDateTime.now();
							//checkSocket();
						}
						
						Thread.sleep(100);
						
					} catch (InterruptedException e) {
						logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
					}
				}				
			}						
		});
		
		checkSocketThread.setName("GmtUDPManager-checkSocketThread");
		
	}
	
	
	public void initializeUDPFromXML() {
		if (path.equals("")) {
			path = System.getProperty("user.dir");
		}
			 
		File xmlFile = new File(path+"/cfg/udpsocket.xml");
		
		if (!xmlFile.exists()) {
			logger.writeLevelLog("[UDPManager] Load fail .. : "+ path+"\\cfg\\udpsocket.xml", LogLevelType.LOG_ERROR, "AllLog");
			return;			
		}
		
		logger.writeLevelLog("[UDPManager] Load file .. : "+ path+"\\cfg\\udpsocket.xml", LogLevelType.LOG_DEBUG, "AllLog");
		
		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 			= GmtUtils.strToIntDef(childElement.getAttribute("ID"), -1);
							tag 		= GmtUtils.strToIntDef(childElement.getAttribute("TAG"), -1);
							primaryIP 	= childElement.getAttribute("PIP");
							secondaryIP = childElement.getAttribute("SIP");
							port        = GmtUtils.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 = GmtUtils.strToIntDef(childElement.getAttribute("Interval"), 60);
						}
					} catch (Exception e) {
						logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
						continue;
					}
				}
					
				if (!use || primaryIP.equals("") || port == 0 || id == -1)  {
					continue;
				}
				
				GmtUDPSocket udpSocket = new GmtUDPSocket(recvQ);
				udpSocket.setId(id);
				udpSocket.setTag(tag);
				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.writeLevelLog(msg, LogLevelType.LOG_INFO, "AllLog");
					
					udpSockets.put(udpSocket.getId(),  udpSocket);
				}			   	
			}		
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}		
	}
	
	
	private void checkReconnect() {
		for(GmtUDPSocket udpSocket : udpSockets.values()) {
			if (udpSocket == null) {
				continue;
			}
			
			if (!udpSocket.isUse()) {
				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 = "Messages are not received for "+ String.valueOf(udpSocket.getInterval()) +" seconds";
			}
			
			if (isReconn) {
//				String logMsg = "";
				
				if (udpSocket.getActiveSocket() == ActiveModeType.PRIMARY) {
					logMsg = "["+ udpSocket.getSecodaryIP() +":"+ String.valueOf(udpSocket.getPort()) +"(P -> S)] "+udpSocket.getName() +" Change connected.."+ logMsg;
				} else {
					logMsg = "["+ udpSocket.getPrimaryIP()  +":"+ String.valueOf(udpSocket.getPort()) +"(S -> P)] "+udpSocket.getName() +" Change connected.."+ logMsg;
				}
				logger.writeLevelLog(logMsg, LogLevelType.LOG_INFO, "AllLog");
				
				GMTResultVO vo = udpSocket.reActive();
				
				if (vo.isSuccess()) {
					udpSocket.setRecvdt(LocalDateTime.now());					
					logMsg = "["+ udpSocket.getCurrentIP() +":"+ String.valueOf(udpSocket.getPort()) +
					         "("+ String.valueOf(udpSocket.getActiveSocket()) +")] "+
					         udpSocket.getName() +" Connected.. ";
				} else {
					logMsg = "["+ udpSocket.getCurrentIP() +":"+ String.valueOf(udpSocket.getPort()) +
					         "("+ String.valueOf(udpSocket.getActiveSocket()) +")] "+
					         udpSocket.getName() +" Connect fail.. "+ vo.getDescription();
				}
				logger.writeLevelLog(logMsg, LogLevelType.LOG_INFO, "AllLog");
			}			
		}		
	}
	
	public void startClient() {		
		for (GmtUDPSocket udpSocket : udpSockets.values()) {
			if (udpSocket == null) {
				continue;
			}
			
			if (!udpSocket.isUse()) {
				continue;
			}
			
			GMTResultVO vo = udpSocket.activeSocket(udpSocket.getPrimaryIP(), udpSocket.getPort()); 
			String msg = "";
			if (vo.isSuccess()) {
				msg = "[UDP Socket][CONNECTED]["+ udpSocket.getPrimaryIP() +"]["+ String.valueOf(udpSocket.getPort()) +"] "+ vo.getDescription();
			} else {
				msg = "[UDP Socket][ERROR]["+ udpSocket.getPrimaryIP() +"]["+ String.valueOf(udpSocket.getPort()) +"] "+ vo.getDescription();
			}
			logger.writeLevelLog(msg, LogLevelType.LOG_INFO, "AllLog");
		}
		if (checkSocketThread != null) {
			checkSocketThread.start();
		}
	}
	
	public void broadcastMsg(MsgObjVO vo) {
		for (GmtUDPSocket udpSocket : udpSockets.values()) {
			if (udpSocket.equalsIP(vo.getRecvIP()) && udpSocket.getPort() == vo.getRecvPort()) {
				continue;
			}
			MsgObjVO newVO = vo.getClone();
			if (gmtTestEventListener != null && !gmtTestEventListener.filterTesta(newVO, udpSocket.getCurrentIP(), udpSocket.getPort())) {
				continue;
			}
			udpSocket.addSendMsg(newVO);
		}
	}
	
	public void sendMsg(String clientIP, int clientPort, MsgObjVO vo) {
		for (GmtUDPSocket udpSocket : udpSockets.values()) {
			if (udpSocket.equalsIP(vo.getRecvIP()) && udpSocket.getPort() == vo.getRecvPort()) {
				continue;
			}
			
			if (udpSocket.equalsIP(clientIP) && udpSocket.getPort() == clientPort) {
				MsgObjVO newVO = vo.getClone();
				if (gmtTestEventListener != null && !gmtTestEventListener.filterTesta(newVO, udpSocket.getCurrentIP(), udpSocket.getPort())) {
					continue;
				}
				udpSocket.addSendMsg(newVO);
				break;
			}			
		}
	}
	
	public void sendMsg(int clientID, MsgObjVO vo) {
		GmtUDPSocket udpSocket = udpSockets.get(clientID);
		
		if(udpSocket.equalsIP(vo.getRecvIP()) && udpSocket.getPort() == vo.getRecvPort()) {
			return;
		}
		
		if (udpSocket != null) {
			udpSocket.addSendMsg(vo);
		}		
	}
	
	public void initMonitor() {
		if(udpSockets.isEmpty()) {
			return;
		}
		for (GmtUDPSocket udpSocket : udpSockets.values()) {
			if(udpSocket == null) {
				return;
			}
			udpSocket.updateMonitorInfo("".getBytes(), MonitorUpdateType.UPDATE);
		}
	}
	
	public boolean getClientMonitorInfo(CopyOnWriteArrayList<ClientInfoVO> clientInfoList) {
		if(clientInfoList == null) {
			return false;
		}
		clientInfoList.clear();
		for (GmtUDPSocket udpSocket : udpSockets.values()) {
			if(udpSocket == null) {
				return false;
			}
			
			
			ClientInfoVO  tempClientInfoVO = new ClientInfoVO();
			tempClientInfoVO.setId(udpSocket.getId());
			tempClientInfoVO.setTag(udpSocket.getTag());
			tempClientInfoVO.setIp(udpSocket.getPrimaryIP());
			tempClientInfoVO.setsIp(udpSocket.getSecodaryIP());
			tempClientInfoVO.setPort(udpSocket.getPort());
			tempClientInfoVO.setName(udpSocket.getName());
			tempClientInfoVO.setiSUse(udpSocket.isUse());
			tempClientInfoVO.setActive(udpSocket.getActiveSocket());
			tempClientInfoVO.setMonitorInfo(udpSocket.getMonitorInfoVO());
			tempClientInfoVO.setLocalPort(udpSocket.getLocalPort());
			
			tempClientInfoVO.getMonitorInfo().setConnected(udpSocket.isActive());
			tempClientInfoVO.getMonitorInfo().setSendQCount(udpSocket.getSendQCount());
			tempClientInfoVO.getMonitorInfo().setSendQBufferCount(udpSocket.getSendBufferCount());
			
			clientInfoList.add(tempClientInfoVO);
		}
		return true;
	}
	
	
	public void setFilteringListener(GmtTestEventListener listener) {
		if (listener != null ) {
			this.gmtTestEventListener = listener;
		}
	}
	
	public void setEncryptListener(GmtEncryptEventListener encryptListener) {
		this.encryptListener = encryptListener;
	}
	
	public void setDecryptListener(GmtEncryptEventListener decryptListener) {
		this.decryptListener = decryptListener;
	}
	
	public ClientInfoVO getClientInfo(String id) {
		
		GmtUDPSocket udpSocket = this.udpSockets.get(Integer.valueOf(id));
		if (udpSocket == null) {
			return null ;
		}
		
		ClientInfoVO tempClientInfoVO = new ClientInfoVO();
		tempClientInfoVO.setId(udpSocket.getId());
		tempClientInfoVO.setTag(udpSocket.getTag());
		tempClientInfoVO.setIp(udpSocket.getPrimaryIP());
		tempClientInfoVO.setsIp(udpSocket.getSecodaryIP());
		tempClientInfoVO.setPort(udpSocket.getPort());
		tempClientInfoVO.setName(udpSocket.getName());
		tempClientInfoVO.setiSUse(udpSocket.isUse());
		tempClientInfoVO.setActive(udpSocket.getActiveSocket());
		tempClientInfoVO.setMonitorInfo(udpSocket.getMonitorInfoVO());
		tempClientInfoVO.setLocalPort(udpSocket.getLocalPort());
		
		return tempClientInfoVO;
	
	}
	
	
	public boolean disconnect(int id) {
		boolean result = false;
		
		try {
			GmtUDPSocket udpSocket = this.udpSockets.get(id);
			if (udpSocket != null) {
				result = udpSocket.deActive();
			} 
		} catch(Exception e) {
			logger.writeLevelLog("udp socket deactive fail : "+ GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog", true);
		}
		
		return result;
	}
	
	public boolean disconnect(String ip, int port) {
		boolean result = false;
		
		try {
			for(GmtUDPSocket udpSocket : this.udpSockets.values()) {
				if( (ip.equals(udpSocket.getPrimaryIP()) || ip.equals(udpSocket.getSecodaryIP())) && udpSocket.getPort() == port ) {
					result = udpSocket.deActive();
					break;
				}
			}
		} catch(Exception e) {
			logger.writeLevelLog("udp socket deactive fail : "+ GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog", true);
		}	
		
		return result;
	}
	
	public void shutdown() {
		this.running = false;
	}

}

