package gmt.io.client;

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.event.GmtClientEventListener;
import gmt.io.client.vo.ClientInfoVO;
import gmt.logger.GmtLogManager;
import gmt.protocol.event.GmtEncryptEventListener;
import gmt.protocol.event.GmtTestEventListener;
import ipworks.IPWorksException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

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.Queue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;

public class GmtClientManager {
	
	private String path;
	private GmtLogManager logger;
	private Queue<MsgObjVO> recvQ;
	private Thread checkSocketThread;
	private GmtTestEventListener gmtTestEventListener;
	private HashMap<Integer, GmtClient> clients;
	private GmtClientEventListener listener;
	private GmtEncryptEventListener encryptListener;
	private GmtEncryptEventListener decryptListener;
	
	public GmtClientManager(Queue<MsgObjVO> recvQ, String path, GmtClientEventListener listener) {
//		GmtLogManager.setpath("");
		
		this.path     = path;
		this.logger   = GmtLogManager.getInstance();
		this.listener = listener;
		this.recvQ    = recvQ;
		
		if (this.recvQ == null) {
			this.recvQ = new LinkedBlockingQueue<>(100000);
		}		
		clients = new HashMap<Integer, GmtClient>();
		
		checkSocketThread = new Thread(new Runnable() {			
			@Override
			public void run() {
				LocalDateTime reconnDT = LocalDateTime.now();				
				LocalDateTime checkDT  = LocalDateTime.now();
				LocalDateTime initDT = LocalDateTime.now();
				Duration      duration;
				while(true) {					
					try {
						duration = Duration.between(reconnDT, LocalDateTime.now());
						if (duration.getSeconds() >= 10) {
							reconnDT = LocalDateTime.now();
							checkReconnect();							
						}

						// NOT IMPLEMENTED
//						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");
					}
				}				
			}						
		});
	}

	public void initializeClientFromXML() {
		if (path.equals("")) {
			path = System.getProperty("user.dir");
		}
			 
		File xmlFile = new File(path+"/cfg/client.xml");
		
		if (!xmlFile.exists()) {
			logger.writeLevelLog("[ClientManager] Load fail .. : "+ path+"\\cfg\\client.xml", LogLevelType.LOG_ERROR, "AllLog");
			return;			
		}
		
		logger.writeLevelLog("[ClientManager] Load file .. : "+ path+"\\cfg\\client.xml", LogLevelType.LOG_DEBUG, "AllLog");
		
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document doc = builder.parse(xmlFile);
			
			NodeList nodes = doc.getElementsByTagName("Client");
			
			for(int i = 0; i < nodes.getLength(); i++ ) {
				Node clientNode = 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;
				
				
				for(Node child = clientNode.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"));
						}
						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;
				}
				
				GmtClient client = new GmtClient(recvQ);
				client.setId(id);
				client.setTag(tag);
				client.setName(name);
				client.setPrimaryIP(primaryIP);
				client.setSecodaryIP(secondaryIP);
				client.setPort(port);
				client.setUse(use);
				client.setLineMode(lineMode);
				client.setReconnUse(reconnUse);
				client.setInterval(reconnInterval);
				client.setEventListener(listener);		
				client.setEncryptListener(encryptListener);
				client.setDecryptListener(decryptListener);
				
				if (!clients.containsKey(client.getId())) {
					
					String msg = "[CLIENT] ID : "+ String.valueOf(client.getId()) +" IP : "+ client.getPrimaryIP() +" PORT : "+ String.valueOf(client.getPort());
					logger.writeLevelLog(msg, LogLevelType.LOG_INFO, "AllLog");
					
					clients.put(client.getId(),  client);
				}			   	
			}		
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}		
	}
	
	private void checkReconnect() {
		for(GmtClient client : clients.values()) {
			if (client == null) {
				continue;
			}
			
			if (!client.isUse()) {
				continue;
			}
			
			boolean isReconn = false;
			LocalDateTime lastRecvDT = client.getRecvdt();
			Duration duration = Duration.between(lastRecvDT, LocalDateTime.now());
			
			String logMsg = "";
			if (!client.isConnected()) {
				isReconn = true;
			} else if(client.isReconnUse() && duration.getSeconds() >= client.getInterval()) {
				isReconn = true;
				logMsg = "Messages are not received for "+ String.valueOf(client.getInterval()) +" seconds";
			}
			
			if (isReconn) {
//				String logMsg = "";
				
				if (client.getActiveSocket() == ActiveModeType.PRIMARY) {
					logMsg = "["+ client.getSecodaryIP() +":"+ String.valueOf(client.getPort()) +"(P -> S)] "+client.getName() +" Change connected.."+ logMsg;
				} else {
					logMsg = "["+ client.getPrimaryIP()  +":"+ String.valueOf(client.getPort()) +"(S -> P)] "+client.getName() +" Change connected.."+ logMsg;
				}
				logger.writeLevelLog(logMsg, LogLevelType.LOG_INFO, "AllLog");
				
				GMTResultVO vo = client.changeConnection();
				
				if (vo.isSuccess()) {
					client.setRecvdt(LocalDateTime.now());					
					logMsg = "["+ client.getCurrentIP() +":"+ String.valueOf(client.getPort()) +
					         "("+ String.valueOf(client.getActiveSocket()) +")] "+
					         client.getName() +" Connected.. ";
				} else {
					logMsg = "["+ client.getCurrentIP() +":"+ String.valueOf(client.getPort()) +
					         "("+ String.valueOf(client.getActiveSocket()) +")] "+
					         client.getName() +" Connect fail.. "+ vo.getDescription();
				}
				logger.writeLevelLog(logMsg, LogLevelType.LOG_INFO, "AllLog");
			}			
		}		
	}
	
	private void checkSocket() {
	
		
	}
	
	public void startClient() {		
		for (GmtClient client : clients.values()) {
			if (client == null) {
				continue;
			}
			
			if (!client.isUse()) {
				continue;
			}
			
			GMTResultVO vo = client.connect(client.getPrimaryIP(), client.getPort()); 
			String msg = "";
			if (vo.isSuccess()) {
				msg = "[CLIENT][CONNECTED]["+ client.getPrimaryIP() +"]["+ String.valueOf(client.getPort()) +"] "+ vo.getDescription();
			} else {
				msg = "[CLIENT][ERROR]["+ client.getPrimaryIP() +"]["+ String.valueOf(client.getPort()) +"] "+ vo.getDescription();
			}
			logger.writeLevelLog(msg, LogLevelType.LOG_INFO, "AllLog");
		}
		if (checkSocketThread != null) {
			checkSocketThread.start();
		}
	}
	
	public void broadcastMsg(MsgObjVO vo) {
		for(GmtClient client : clients.values()) {
			if (client.equalsIP(vo.getRecvIP()) && client.getPort() == vo.getRecvPort()) {
				continue;
			}
			MsgObjVO newVO = vo.getClone();
			if (gmtTestEventListener != null && !gmtTestEventListener.filterTesta(newVO, client.getCurrentIP(), client.getPort())) {
				continue;
			}
			client.addSendMsg(newVO);
		}
	}
	
	public void sendMsg(String clientIP, int clientPort, MsgObjVO vo) {
		for(GmtClient client : clients.values()) {
			if (client.equalsIP(vo.getRecvIP()) && client.getPort() == vo.getRecvPort()) {
				continue;
			}
			
			if (client.equalsIP(clientIP) && client.getPort() == clientPort) {
				MsgObjVO newVO = vo.getClone();
				if (gmtTestEventListener != null && !gmtTestEventListener.filterTesta(newVO, client.getCurrentIP(), client.getPort())) {
					continue;
				}
				client.addSendMsg(newVO);
				break;
			}			
		}
	}
	
	public void sendMsg(int clientID, MsgObjVO vo) {
		GmtClient client = clients.get(clientID);
		
		if(client.equalsIP(vo.getRecvIP()) && client.getPort() == vo.getRecvPort()) {
			return;
		}
		
		if (client != null) {
			client.addSendMsg(vo);
		}		
	}
	
	public void initMonitor() {
		if(clients.isEmpty()) {
			return;
		}
		for(GmtClient client : clients.values()) {
			if(client == null) {
				return;
			}
			client.updateMonitorInfo("".getBytes(), MonitorUpdateType.UPDATE);
		}
	}
	
	
	public boolean getClientMonitorInfo(CopyOnWriteArrayList<ClientInfoVO> clientInfoList) throws IPWorksException {
		if(clientInfoList == null) {
			return false;
		}
		clientInfoList.clear();
		for(GmtClient client : clients.values()) {
			if(client == null) {
				return false;
			}
			/*client.updateMonitorInfo("", MonitorUpdateType.UPDATE);*/
			ClientInfoVO  tempClientInfoVO = new ClientInfoVO();
			tempClientInfoVO.setId(client.getId());
			tempClientInfoVO.setTag(client.getTag());
			tempClientInfoVO.setIp(client.getPrimaryIP());
			tempClientInfoVO.setsIp(client.getSecodaryIP());
			tempClientInfoVO.setPort(client.getPort());
			tempClientInfoVO.setName(client.getName());
			tempClientInfoVO.setiSUse(client.isUse());
			tempClientInfoVO.setActive(client.getActiveSocket());
			tempClientInfoVO.setMonitorInfo(client.getMonitorInfoVO());
			tempClientInfoVO.setLocalPort(client.getLocalPort());
			tempClientInfoVO.setRecordLength(client.getRecordLength());
			tempClientInfoVO.setInBufferSize(client.getInBuffSize());
			tempClientInfoVO.setOutBufferSize(client.getOutBuffSize());

			tempClientInfoVO.getMonitorInfo().setConnected(client.isConnected());
			tempClientInfoVO.getMonitorInfo().setSendQCount(client.getSendQCount());
			tempClientInfoVO.getMonitorInfo().setSendQBufferCount(client.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) throws IPWorksException {
		GmtClient client = this.clients.get(Integer.valueOf(id));		
		if (client == null) {
			return null;
		}
		
		ClientInfoVO  tempClientInfoVO = new ClientInfoVO();		
		tempClientInfoVO.setId(client.getId());
		tempClientInfoVO.setTag(client.getTag());
		tempClientInfoVO.setIp(client.getPrimaryIP());
		tempClientInfoVO.setsIp(client.getSecodaryIP());
		tempClientInfoVO.setPort(client.getPort());
		tempClientInfoVO.setName(client.getName());
		tempClientInfoVO.setiSUse(client.isUse());
		tempClientInfoVO.setActive(client.getActiveSocket());
		tempClientInfoVO.setMonitorInfo(client.getMonitorInfoVO());
		tempClientInfoVO.setLocalPort(client.getLocalPort());
		tempClientInfoVO.setRecordLength(client.getRecordLength());
		tempClientInfoVO.setInBufferSize(client.getInBuffSize());
		tempClientInfoVO.setOutBufferSize(client.getOutBuffSize());

		
		return tempClientInfoVO;
	}

	public Boolean getClientLineMode(int id) {
		GmtClient client = this.clients.get(id);
		if (client == null) {
			return null;
		}

		return client.isLineMode();
	}
	public boolean disconnect(int id) {
		boolean result = false;		
		try {
			GmtClient client = this.clients.get(id);
			if(client != null) {
				result = client.disconnect();
			}
		} catch(Exception e) {
			logger.writeLevelLog("Client disconnect fail : "+ GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog", true);
		}		
		return result;
	}
	
	public boolean disconnect(String ip, int port) {
		boolean result = false;		
		try {
			for(GmtClient client : this.clients.values()) {
				if( (ip.equals(client.getPrimaryIP()) || ip.equals(client.getSecodaryIP())) && client.getPort() == port ) {
					result = client.disconnect();
					break;
				}
			}
		} catch(Exception e) {
			logger.writeLevelLog("Client disconnect fail : "+ GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog", true);
		}		
		return result;
	}
}

