package gmt.controller;

import gmt.common.type.LogLevelType;
import gmt.common.type.SendModeType;
import gmt.common.util.GmtUtils;
import gmt.common.vo.MsgObjVO;
import gmt.io.client.GmtClientManager;
import gmt.io.client.event.GmtClientEventListener;
import gmt.io.client.vo.ClientInfoVO;
import gmt.io.server.GmtServerManager;
import gmt.io.server.event.GmtServerEventListener;
import gmt.io.server.vo.ServerInfoVO;
import gmt.io.server.vo.ServiceClientVO;
import gmt.io.udp.GmtUDPManager;
import gmt.io.udp.event.GmtUDPEventListener;
import gmt.logger.GmtLogManager;
import gmt.protocol.event.GmtTestEventListener;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

public abstract class GmtController {
	private String path;
	private boolean isRunning;
	private Thread recvQThread;
	private Thread sendQThread;
	private Thread initMonitorThread;
	private ArrayBlockingQueue<MsgObjVO> recvQ;
	private ArrayBlockingQueue<MsgObjVO> sendQ;
	protected GmtClientManager clientManager;
	protected GmtServerManager serverManager;
	private GmtServerEventListener serverListener;
	private GmtTestEventListener testListener;
	private GmtClientEventListener clientListener;
	protected GmtLogManager logger;
	public CopyOnWriteArrayList<ServerInfoVO> serverInfoList = new CopyOnWriteArrayList<ServerInfoVO>();
	public CopyOnWriteArrayList<ClientInfoVO> clientInfoList = new CopyOnWriteArrayList<ClientInfoVO>();
	public ConcurrentHashMap<String, ServiceClientVO> serverScInfoList = new ConcurrentHashMap<String, ServiceClientVO>();
	public boolean clientAccess = false;
	public boolean serverAccess = false;
	public boolean serverScAccess = false;
		
	// UDP추가
	protected GmtUDPManager udpManager;
	private GmtUDPEventListener udpListener;
		
	protected GmtController(String path) {
		this.path  = path;
		isRunning  = false;
		GmtLogManager.setpath(path);
		logger = GmtLogManager.getInstance();
		recvQ = new ArrayBlockingQueue<>(100000);
		sendQ = new ArrayBlockingQueue<MsgObjVO>(100000);

		clientListener = createClientEventListener();
		clientManager  = new GmtClientManager(recvQ, path, clientListener);

		serverListener = createServerEventListener();
		serverManager  = new GmtServerManager(recvQ, path, serverListener);

		// UDP추가
		udpListener = createUDPEventListener();
		udpManager  = new GmtUDPManager(recvQ, path, udpListener);
				
		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.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
					} 
				} 
			}
		});

		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.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
					}
				}
			}
		});
		 
		initMonitorThread = new Thread(new Runnable() {
			@Override
			public void run() {
				LocalDateTime monitorUpdateDT = GmtUtils.defaultTime();
				LocalDateTime monitorInfoUpdateDT = GmtUtils.defaultTime();
				Duration duration;
				while (isRunning) {
					try {
						duration = Duration.between(monitorUpdateDT, LocalDateTime.now());
						
						
						if (duration.toMillis() >= 60 * 1000) {
							monitorUpdateDT = LocalDateTime.now();
							initMonitor();
						}
						Thread.sleep(10);
						
						duration = Duration.between(monitorInfoUpdateDT, LocalDateTime.now());
						
						if (duration.toMillis() >= 2 * 1000) {
							monitorInfoUpdateDT = LocalDateTime.now();
							initMonitorInfo();
						}
						
					} catch (Exception e) {
						logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
					} finally {
						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {
							logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
						}
					}
				}
			}
		});
	}
	
	protected abstract void serialDataIn(MsgObjVO vo);
	protected abstract void serverDataIn(MsgObjVO vo);
	protected abstract void clientDataIn(MsgObjVO vo);
	protected abstract GmtClientEventListener createClientEventListener();
	protected abstract GmtServerEventListener createServerEventListener();

	// UDP추가
	protected abstract void udpDataIn(MsgObjVO vo);
	protected abstract GmtUDPEventListener createUDPEventListener();
	
	private void recvDataIn(MsgObjVO vo) {
		
		try {
			switch (vo.getRecvSocketType()) {
			case CLIENT:
				clientDataIn(vo);
				break;
			case SERVER:
				serverDataIn(vo);
				break;
			case SERIAL:
				serialDataIn(vo);
				break;
			case UDP: // UDP추가
				udpDataIn(vo);
				break;		
			case INTERNAL:
				break;
			case NONE:
				break;
			default:
				break;
			}
		} catch (Exception e) {
			e.printStackTrace();
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}

	private void sendDataIn(MsgObjVO vo) {
		try {
			switch (vo.getSendModeType()) {
			case BROADCAST_ALL_SERVER: 
				serverManager.broadcastMsg(vo);
				break;
			case BROADCAST_ALL_SERVER_TO_SC:
				serverManager.broadcastMsg(vo.getServiceClientIP(), vo);
				break;
			case BROADCAST_SERVER:
				serverManager.broadcastServer(vo.getSendServerPort(), vo);
				break;
			case BROADCAST_SERVER_EXECEPT_IP: 
				serverManager.broadcastServerExceptSC(vo.getExceptIP(), vo.getSendServerPort(), vo);
				break;
			case SEND_BY_SERVER: 
				serverManager.sendServer(vo.getServiceClientIP(), vo.getSendServerPort(), vo);
				break;
			case SEND_BY_SERVER_TO_SC: 
				serverManager.sendServer(vo.getServiceClientIP(), vo.getServiceClientPort(), vo.getSendServerPort(), vo);
				break;
			case BROADCAST_CLIENT: 
				clientManager.broadcastMsg(vo);
				break;
			case SEND_BY_CLIENT: 
				clientManager.sendMsg(vo.getSendClientIP(), vo.getSendClientPort(), vo);
				break;
			case SEND_BY_CLIENT_OF_ID: 
				clientManager.sendMsg(vo.getSendID(), vo);
				break;
			case BROADCAST_SERIAL: 
				break;
			case SEND_BY_SERIAL: 
				break;
			default:
				break;
			}
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}

	protected void broadcastAllServer(MsgObjVO vo) {
		try {
			if (vo != null && !vo.getMsg().equals("") && sendQ != null) {
				vo.setSendModeType(SendModeType.BROADCAST_ALL_SERVER);
				sendQ.offer(vo);
			}
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}
	
	protected void broadcastAllServerToSC(String destIP, MsgObjVO vo) {
		try {
			if (vo != null && !vo.getMsg().equals("") && sendQ != null) {
				vo.setSendModeType(SendModeType.BROADCAST_ALL_SERVER_TO_SC);
				vo.setServiceClientIP(destIP);
				sendQ.offer(vo);
			}
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}		
	}

	protected void broadcastServer(int serverPort, MsgObjVO vo) {
		try {
			if (vo != null && !vo.getMsg().equals("") && sendQ != null) {
				vo.setSendModeType(SendModeType.BROADCAST_SERVER);
				vo.setSendServerPort(serverPort);
				sendQ.offer(vo);
			}
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}

	protected void broadcastServerExceptIP(int serverPort, String exceptIP, MsgObjVO vo) {
		try {
			if (vo != null && !vo.getMsg().equals("") && sendQ != null) {
				MsgObjVO encVO = vo.getClone();
				encVO.setSendModeType(SendModeType.BROADCAST_SERVER_EXECEPT_IP);
				encVO.setSendServerPort(serverPort);
				encVO.setExceptIP(exceptIP);
				sendQ.offer(encVO);
			}
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}

	protected void sendServer(int serverPort, String serviceClientIP, MsgObjVO vo) {
		try {
			if (vo != null && !vo.getMsg().equals("") && sendQ != null) {
				vo.setSendModeType(SendModeType.SEND_BY_SERVER);
				vo.setSendServerPort(serverPort);
				vo.setServiceClientIP(serviceClientIP);
				sendQ.offer(vo);
			}
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}

	protected void sendServerToSC(int serverPort, String serviceClientIP, int serviceClientPort, MsgObjVO vo) {
		try {
			if (vo != null && !vo.getMsg().equals("") && sendQ != null) {
				vo.setSendModeType(SendModeType.SEND_BY_SERVER_TO_SC);
				vo.setSendServerPort(serverPort);
				vo.setServiceClientIP(serviceClientIP);
				vo.setServiceClientPort(serviceClientPort);
				sendQ.offer(vo);
			}
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}

	protected void broadcastClient(MsgObjVO vo) {
		try {
			if (vo != null && !vo.getMsg().equals("") && sendQ != null) {
				vo.setSendModeType(SendModeType.BROADCAST_CLIENT);
				sendQ.offer(vo);
			}
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}

	protected void sendClient(String clientIP, int clientPort, MsgObjVO vo) {
		try {
			if (vo != null && !vo.getMsg().equals("") && sendQ != null) {
				vo.setSendModeType(SendModeType.SEND_BY_CLIENT);
				vo.setSendClientIP(clientIP);
				vo.setSendClientPort(clientPort);
				sendQ.offer(vo);
			}
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}

	protected void sendClientID(int clientID, MsgObjVO vo) {
		try {
			if (vo != null && !vo.getMsg().equals("") && sendQ != null) {
				vo.setSendModeType(SendModeType.SEND_BY_CLIENT_OF_ID);
				vo.setSendID(clientID);
				sendQ.offer(vo);
			}
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}

	protected void BroadcastSerial(MsgObjVO vo) {
		try {
			if (vo != null && !vo.getMsg().equals("") && sendQ != null) {
				vo.setSendModeType(SendModeType.BROADCAST_SERIAL);
				sendQ.offer(vo);
			}
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}

	protected void SendSerial(int serialPort, MsgObjVO vo) {
		try {
			if (vo != null && !vo.getMsg().equals("") && sendQ != null) {
				vo.setSendModeType(SendModeType.SEND_BY_SERIAL);
				vo.setSerialPort(serialPort);
				sendQ.offer(vo);
			}
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}
	
	// UDP추가
	protected void broadcastUDP(MsgObjVO vo) {
		try {
			if (vo != null && !vo.getMsg().equals("")&& vo.getMsg() != null && sendQ != null) {
				vo.setSendModeType(SendModeType.BROADCAST_UDP);
				sendQ.offer(vo);
			}
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}	
	
	protected void sendUDP(String clientIP, int clientPort, MsgObjVO vo) {
		try {
			if (vo != null && !vo.getMsg().equals("")&& vo.getMsg() != null && sendQ != null) {
				vo.setSendModeType(SendModeType.SEND_BY_UDP);
				vo.setSendClientIP(clientIP);
				vo.setSendClientPort(clientPort);
				sendQ.offer(vo);
			}
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}

	protected void sendUDPID(int clientID, MsgObjVO vo) {
		try {
			if (vo != null && !vo.getMsg().equals("")&& vo.getMsg() != null && sendQ != null) {
				vo.setSendModeType(SendModeType.SEND_BY_UDP_OF_ID);
				vo.setSendID(clientID);
				sendQ.offer(vo);
			}
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}

	public void start() {
		try {
			isRunning = true;
			
			clientManager.initializeClientFromXML();
			serverManager.initializeServerFromXML();
			serverManager.initAllowClientXML();
	
			recvQThread.start();
			sendQThread.start();
			initMonitorThread.start();
			
			clientManager.startClient();
			serverManager.startServer();
			
			// UDP추가
			udpManager.initializeUDPFromXML();
			udpManager.startClient();	
			
		} catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}

	public void stop() {
		isRunning = false;
	}

	public int getRecvQCount() {
		return recvQ.remainingCapacity();
	}

	public int getSendQCount() {
		return sendQ.remainingCapacity();
	}

	public int getServiceConnectionCount() {
		return serverManager.getServiceClientCount();
	}

	public boolean isRunning() {
		return isRunning;
	}

	public void setRunning(boolean isRunning) {
		this.isRunning = isRunning;
	}

	public int getRcvBuffer() {
		return recvQ.size();
	}

	public int getSndBuffer() {
		return sendQ.size();
	}

	public boolean getServerMonitorInfo(int serverPort, ServerInfoVO serverInfoVO) {
		boolean result = false;
		if(serverInfoVO == null) {
			return result;
		}
		try{
			if(serverManager.getServerMonitorInfo(serverPort, serverInfoVO)) {
				result = true;
			}
		}catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
		
		return result;
	}
	
	public boolean getServerMonitorInfo(CopyOnWriteArrayList<ServerInfoVO> serverInfoList) {
		boolean result = false;
		if(serverInfoList == null) {
			return result;
		}
		try {
			if(serverManager.getServerMonitorInfo(true, serverInfoList)) {
				result = true;
			}
		}catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}	
		return result;
	}
	
	public boolean getServiceClientMonitorInfo(boolean init, ConcurrentHashMap<String, ServiceClientVO> serverScInfoList) {
		boolean result = false;
		if(serverScInfoList == null) {
			return result;
		}
		try {
			if(serverManager.getSCMonitorInfo(init, serverScInfoList)) {
				result = true;
			}
		}catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}	
		return result;
	}
	
	public boolean getClientMonitorInfo(CopyOnWriteArrayList<ClientInfoVO> clientInfoList) {
		boolean result = false;
		if(clientInfoList == null) {
			return result;
		}
		try {
			if(clientManager.getClientMonitorInfo(clientInfoList)) {
				result = true;
			}
		}catch (Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}	
		
		return result;
	}
	
	public double getRecvQPercent() {
		int capacity = recvQ.remainingCapacity()+ recvQ.size();
		double recvQpercent = (double)((double)(capacity - recvQ.size())/(double)capacity)*100;
		recvQpercent = Double.parseDouble(String.format("%.2f", recvQpercent));
		return recvQpercent;
	}
	
	public double getSendQPercent() {
		int capacity = sendQ.remainingCapacity()+ sendQ.size();
		double sendQpercent = (double)((double)(capacity - sendQ.size())/(double)capacity)*100;
		sendQpercent = Double.parseDouble(String.format("%.2f", sendQpercent));
		return sendQpercent;
	}
	
	public int getCurrnetRecvQCount() {
		return recvQ.size();
	}

	public int getCurrnetSendQCount() {
		return sendQ.size();
	}
	
	public void initMonitor() {
		serverManager.initMonitor();
		clientManager.initMonitor();
	}
	
	public void initMonitorInfo() {
		clientAccess = false;
		serverAccess = false;
		serverScAccess = false; 
		clientAccess = getClientMonitorInfo(clientInfoList);
		serverAccess = getServerMonitorInfo(serverInfoList);
		serverScAccess = getServiceClientMonitorInfo(true, serverScInfoList);
	}

	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}
}
