package gmt.io.client;

import gmt.common.type.*;
import gmt.common.util.GmtUtils;
import gmt.common.vo.*;
import gmt.io.client.event.GmtClientEventListener;
import gmt.logger.GmtLogManager;
import gmt.protocol.event.GmtEncryptEventListener;
import ipworks.*;

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

public class GmtClient {
	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 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 Ipport 			ipport;
	private final Queue<MsgObjVO>	recvQ;
	private final Queue<MsgObjVO>	sendQ;
	private final Thread			sendThread;	
	private GmtClientEventListener	eventListener;
	private GmtEncryptEventListener encryptListener;
	private GmtEncryptEventListener decryptListener;
		
	public GmtClient(Queue<MsgObjVO> recvQ){
		id            = -1;
		tag           = 0;
		name          = "";
		primaryIP     = "";
		secodaryIP    = "";
		activeSocket  = ActiveModeType.PRIMARY;
		lineMode      = true;
		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;
		
//		GmtLogManager.setpath("");
		logger = GmtLogManager.getInstance();		
		sendQ  = new LinkedBlockingQueue<MsgObjVO>(100000);		
		ipport = new Ipport();
		try {			
			ipport.setRuntimeLicense("31504A42564131535542524131535542414E44323132323200000000000000000000000000000000505A4E384E5758350000325242435038313258424E480000");
			ipport.addIpportEventListener(new IpportEventListener() {				
				@Override
				public void readyToSend(IpportReadyToSendEvent e) {					
					if (eventListener != null) {
						eventListener.readyToSend();
					}
				}
				
				@Override
				public void error(IpportErrorEvent e) {					
					if (eventListener != null) {
						eventListener.error(ipport.getRemoteHost(), ipport.getRemotePort(), e.errorCode, e.description);
					}					
				}
				
				@Override
				public void disconnected(IpportDisconnectedEvent e) {
					if (eventListener != null) {							
						eventListener.disconnected(ipport.getRemoteHost(), ipport.getRemotePort(), e.statusCode, e.description);
					}					
				}
				
				@Override
				public void dataIn(IpportDataInEvent e) {
					processRcvData(e);
				}
				
				@Override
				public void connectionStatus(IpportConnectionStatusEvent e) {
					if (eventListener != null) {
						eventListener.connectionStatus(ipport.getRemoteHost(), ipport.getRemotePort(), e.connectionEvent, e.statusCode, e.description);
					}
				}
				
				@Override
				public void connected(IpportConnectedEvent e) {
					if (eventListener != null) {
						eventListener.connected(ipport.getRemoteHost(), ipport.getRemotePort(), e.statusCode, e.description);
					}
				}
				
				@Override
				public void SSLStatus(IpportSSLStatusEvent e) {
					if (eventListener != null) {
						eventListener.SSLStatus();
					}
				}
				
				@Override
				public void SSLServerAuthentication(IpportSSLServerAuthenticationEvent e) {
					if (eventListener != null) {
						eventListener.SSLServerAuthentication();
					}
				}
			});
		} catch (Exception ex) {
			
			System.out.println("21 : " + ex.getMessage());
		}
		
		sendThread = new Thread(new Runnable() {			
			@Override
			public void run() {
				while(isRunning) {
					try {
						clientSendMsg();
						Thread.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}				
			}
		});		
		sendThread.start();		
	}
	
	public void processRcvData(IpportDataInEvent data) {
		try {
			recvdt = LocalDateTime.now();
			
			if (recvQ == null) {
				return;
			}
			
			MsgObjVO vo = new MsgObjVO();
					
			vo.setMsgSeq(0);
			vo.setRecvIP(ipport.getRemoteHost());
			vo.setRecvPort(ipport.getRemotePort());
			vo.setRecvID(id);
			vo.setRecvTag(tag);
			vo.setRecvDT(LocalDateTime.now());
			vo.setRecvServerPort(port);
			vo.setRecvSocketType(SocketType.CLIENT);
			vo.setMsg(data.text.clone());
	
			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);			
		} catch(Exception e) {
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "FileLog");
		}
	}
	
	private void clientSendMsg() {
		
		if (!ipport.isConnected()) {
			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 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("Socket Error");
					
					if (netStatVO.getRetryCount() == 0) {
						netStatVO.setRetryCount(netStatVO.getRetryCount() +1);
						netStatVO.setLastSendTime(LocalDateTime.now());
					}
					
					Thread.sleep(10);
					break;
			}
		} catch(Exception e) {
			logger.writeLevelLog("[CLIENT] Process send result error : ", LogLevelType.LOG_ERROR, "AllLog");
			logger.writeLevelLog(GmtUtils.getStatckTrace(e), LogLevelType.LOG_ERROR, "AllLog");
		}
	}
	
	private int sendMsg(MsgObjVO vo) {
		int resultCode = -1;
		if (ipport.isConnected()) {			
			try {
				
				if (encryptListener != null) {
					vo.setMsg(encryptListener.getByteArray(this.id, vo.getMsg()));
				}
				ipport.setDataToSend(vo.getMsg());
				
				if (lineMode) {
					ipport.setDataToSend("\r\n".getBytes());
				}
				resultCode = 0;
			} catch (IPWorksException e) {
				resultCode = e.getCode();
				if (e.getCode() == GmtUtils.NET_WOULD_BLOCK) {
					logger.writeLevelLog("[CLIENT]["+ ipport.getRemoteHost() +"]["+ String.valueOf(ipport.getRemotePort()) +"] Operation Would Block..", LogLevelType.LOG_ERROR, "AllLog");
				}
			}
		}
		return resultCode;		
	}
	
	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");
		}
	}
	
	public GMTResultVO connect(String ip, int port) {				
		GMTResultVO vo = new GMTResultVO();
		
		if (ipport == null) {
			vo.setSuccess(false);
			vo.setDescription("Client socket declare");
			return vo;
		}
		
		try {
			ipport.disconnect();		
			ipport.setTimeout(10);		
			ipport.setLinger(true);
//			ipport.config("InBufferSize=10485760");
//			ipport.config("OutBufferSize=2621440");


			if (lineMode) {
				ipport.setEOL("\r\n".getBytes());
			} else {
				ipport.setEOL("");
			}
			
			ipport.connect(ip, port);
		} catch (IPWorksException e) {
			vo.setSuccess(false);
			vo.setDescription("22 : " + e.getMessage());
			return vo;
		}
						
		vo.setSuccess(true);
		vo.setStatusCode("0");
		vo.setDescription("OK");
		return vo;
	}
	
	public boolean disconnect() {		
		try {
			ipport.disconnect();
		} catch (IPWorksException e) {			
			e.printStackTrace();
			return false;
		}
		
		return true;		
	}
	
	public void addSendMsg(String msg) {
		
	}
	
	public void addSendMsg(MsgObjVO vo) {
		if (vo != null && sendQ != null && ipport.isConnected()) {
			sendQ.offer(vo);
			/*System.out.println("sendQ.size : "+sendQ.size());*/
		}
	}
	
	public GMTResultVO changeConnection() {
		String ip   = "";
		
		GMTResultVO vo = new GMTResultVO();
		
		if (ipport == null) {
			vo.setSuccess(false);
			vo.setDescription("Client 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 {
			ipport.disconnect();
			if (!ipport.getRemoteHost().equalsIgnoreCase(ip)) {
				ipport.setRemoteHost(ip);
			}
			
			ipport.connect(ipport.getRemoteHost(), ipport.getRemotePort());
			vo.setSuccess(true);
			vo.setDescription("connection change success");
			
		} 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 ipport.getRemoteHost();
	}

	public boolean isConnected() {
		return ipport.isConnected();
	}
	
	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 int getLocalPort() {
		return this.ipport.getLocalPort();
	}

	public int getRecordLength(){
		return this.ipport.getRecordLength();
	}

	public String getInBuffSize() throws IPWorksException {
		return this.ipport.config("InBufferSize");
	}

	public String getOutBuffSize() throws IPWorksException {
		return this.ipport.config("OutBufferSize");
	}

	
	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 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(GmtClientEventListener 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;
	}
}