package com.eactive.eai.adapter.websocket;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import org.java_websocket.WebSocket;

import com.eactive.eai.common.session.ChannelSessionFacade;
import com.eactive.eai.common.session.SessionManager;
import com.eactive.eai.common.session.SessionStatusManager;
import com.eactive.eai.common.session.SessionVO;
import com.eactive.eai.common.session.ChannelSessionFacade.LogoutType;
import com.eactive.eai.common.sessioninfo.SessionInfoVO;
import com.eactive.eai.common.util.Logger;

public class WebSocketSessionManager {
	
	static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
	private static WebSocketSessionManager instance;

	private ConcurrentHashMap<String, WebSocketPool> adapterGroup;

	private WebSocketSessionManager() {
		this.adapterGroup = new ConcurrentHashMap<String, WebSocketPool>();
	}

	public static WebSocketSessionManager getInstance() {
		if (instance == null) {
			instance = new WebSocketSessionManager();
		}
		return instance;
	}

	public HashMap<String, ConcurrentHashMap<String, WebSocket>> getAdapterGroupConnections() {
		HashMap<String, ConcurrentHashMap<String, WebSocket>> adapterMap = new HashMap<String, ConcurrentHashMap<String, WebSocket>>();
		Iterator<String> i = adapterGroup.keySet().iterator();
		while (i.hasNext()) {
			String key = i.next();
			WebSocketPool pool = adapterGroup.get(key);
			ConcurrentHashMap<String, WebSocket> conMap = pool.getList();
			adapterMap.put(key, conMap);
		}
		return adapterMap;
	}

	public ConcurrentHashMap<String, WebSocket> getSessionList(String adapterGroupName) {
		WebSocketPool p = adapterGroup.get(adapterGroupName);
		if (p == null)
			return new ConcurrentHashMap<String, WebSocket>();
		else
			return p.getList();
	}

	public synchronized void sessionOpen(String adapterGroupName, WebSocket conn) {
		if (!adapterGroup.containsKey(adapterGroupName)) {
			adapterGroup.put(adapterGroupName, new WebSocketPool());
		}
		WebSocketPool p = adapterGroup.get(adapterGroupName);
		p.sessionOpen(conn);
	}

	public synchronized void sessionLogin(String adapterGroupName, WebSocket conn, String id) {
		if (!adapterGroup.containsKey(adapterGroupName)) {
			adapterGroup.put(adapterGroupName, new WebSocketPool());
		}
		WebSocketPool p = adapterGroup.get(adapterGroupName);
		p.sessionLogin(conn, id);
	}

	public String getWebSocketConnectionId(String adapterGroupName, WebSocket conn) {
		if (!adapterGroup.containsKey(adapterGroupName)) {
			adapterGroup.put(adapterGroupName, new WebSocketPool());
		}
		WebSocketPool p = adapterGroup.get(adapterGroupName);
		return p.getId(conn);
	}
	
	public void onCloseEvent(String adapterGroupName, WebSocket conn) {
		try {
			
			logger.warn("WebSocketSessionManager] onCloseEvent websocket =" + conn.getRemoteSocketAddress());
			
			if (!adapterGroup.containsKey(adapterGroupName)) {
				adapterGroup.put(adapterGroupName, new WebSocketPool());
			}
			WebSocketPool p = adapterGroup.get(adapterGroupName);
			
			String key = p.getId(conn);

			if (key == null) {
				
				logger.warn("WebSocketSessionManager] id not found websocket  - " + conn.getRemoteSocketAddress());
				
			} else {
				
				logger.warn("WebSocketSessionManager] removeLogin key=" + key);
				
				SessionVO sessionVo = SessionManager.getInstance().getLogin(key);
				if (sessionVo == null) {
					logger.warn("WebSocketSessionManager] LOGOUT Login Session Not found userId =" + key);
				} else {
					String uuid = sessionVo.getUuid();
					try {
						ChannelSessionFacade.getInstance().logout(key, uuid, true, LogoutType.AutoLogout);
					} catch (Exception e) {
						logger.error("WebSocketSessionManager] Error Occured in removeLogin - key : " + key, e);
					}
				}
			}
		} catch (Exception e) {
			logger.error("WebSocketSessionManager] Error Occured in Closing session - " + (conn == null ? "WebSocket is NULL" : conn.getRemoteSocketAddress()), e);
		} finally {
			try {
				if (conn != null && !conn.isClosed())
					conn.close();
			} catch (Exception ex) {
				logger.warn("WebSocketSessionManager] close connection error - " + conn);
			}
		}
	}


	public synchronized WebSocket getSession(String adapterGroupName, String id) {
		if (!adapterGroup.containsKey(adapterGroupName)) {
			adapterGroup.put(adapterGroupName, new WebSocketPool());
		}
		WebSocketPool p = adapterGroup.get(adapterGroupName);
		return p.getSession(id);
	}

	public synchronized Map<String, WebSocket> getSessionMap(String adapterGroupName) {
		if (!adapterGroup.containsKey(adapterGroupName)) {
			adapterGroup.put(adapterGroupName, new WebSocketPool());
		}
		WebSocketPool p = adapterGroup.get(adapterGroupName);
		return p.getSessionMap();
	}

	public synchronized boolean closeSession(String adapterGroupName, String id) {
		if (adapterGroupName == null) {
			logger.warn("WebSocketSessionManager] closeSession adapterGroupName is NULL - " + adapterGroupName);
			return false;
		}
		if (!adapterGroup.containsKey(adapterGroupName)) {
			logger.warn("WebSocketSessionManager] closeSession adapterGroupName not found - " + adapterGroupName);
			return false;
		}
		WebSocketPool p = adapterGroup.get(adapterGroupName);
		if (p == null) {
			logger.warn("WebSocketSessionManager] WebSocket is empty, WebSocketPool is NULL - " + adapterGroupName);
			return false;
		}
		WebSocket conn = p.getSession(id);
		if (conn == null) {
			logger.warn("WebSocketSessionManager] WebSocket Session not found - adapterGroupName : " + adapterGroupName
					+ ", loginId : " + id);
			return false;
		} else {
			logger.warn(
					"WebSocketSessionManager] WebSocket close session - "
					+ "adapterGroupName : " + adapterGroupName + ", "
					+ "loginId : " + id + ", "
					+ "remoteSocketAddress : " + conn.getRemoteSocketAddress());
			p.sessionClose(conn);
			return true;
		}
	}

	public synchronized void sessionError(String adapterGroupName, WebSocket conn) {
		// 에러발생시 웹소켓 연결이 끊기지 않음
		try {
			if (!adapterGroup.containsKey(adapterGroupName)) {
				adapterGroup.put(adapterGroupName, new WebSocketPool());
			}
			WebSocketPool p = adapterGroup.get(adapterGroupName);
			p.sessionError(conn);
		} catch (Exception e) {
			logger.error("WebSocketSessionManager] Error Occured in handling session Error", e);
		}
	}

	public boolean isContainKeys(String adapterGroupName, WebSocket conn) {
		if (!adapterGroup.containsKey(adapterGroupName)) {
			adapterGroup.put(adapterGroupName, new WebSocketPool());
		}
		WebSocketPool p = adapterGroup.get(adapterGroupName);
		return p.isContainKeys(conn);
	}

	public boolean isContainKeys(String adapterGroupName, String id) {
		if (!adapterGroup.containsKey(adapterGroupName)) {
			adapterGroup.put(adapterGroupName, new WebSocketPool());
		}
		WebSocketPool p = adapterGroup.get(adapterGroupName);
		return p.isContainKeys(id);
	}

	public List<String> getAdapterGroupNames() {
		List<String> groups = new ArrayList<String>();
		Enumeration keys = this.adapterGroup.keys();
		while (keys.hasMoreElements()) {
			String key = (String) keys.nextElement();
			groups.add(key);
		}
		return groups;
	}

	/**
	 * 일자전환관련 - 단말 WebSocket 어댑터를 끊기 위해서 생성
	 */
	public synchronized void sessionCloseAdapter(String adapterGroupName) {
		if(adapterGroupName == null) {
			logger.warn("WebSocketSessionManager] closeSession adapterGroupName is NULL - " + adapterGroupName);
			return;
		}
		if (!adapterGroup.containsKey(adapterGroupName)) {
			logger.warn("WebSocketSessionManager] closeSession adapterGroupName not found - " + adapterGroupName);
			return;
		}
		WebSocketPool p = adapterGroup.get(adapterGroupName);
		if (p == null) {
			logger.warn("WebSocketSessionManager] WebSocket is empty, WebSocketPool is NULL - " + adapterGroupName);
			return;
		}
		p.closeAll();
		logger.warn("WebSocketSessionManager] WebSocket Session close all - adapterGroupName : " + adapterGroupName);
	}
	
	public void closeSessionAll() {
		logger.debug("closeSessionAll] Start");
		for (String adapterGroupName : getAdapterGroupNames()) {
			try {
				/** Upsert DB WebSocket Session Info **/
				getSessionList(adapterGroupName).forEach((k, conn) -> {
					String key = getWebSocketConnectionId(adapterGroupName, conn);
					if (StringUtils.isNotBlank(key)) {
						SessionStatusManager.getInstance().manageWebSocketStatus(key, SessionInfoVO.Status.inactive);
					}
				});
				
				sessionCloseAdapter(adapterGroupName);
				logger.debug("closeSessionAll] sessionCloseAdapter -> adapterGroupName["+adapterGroupName+"] closed.");
			} catch (Exception e) {
				// Logging And Skip
				logger.error("closeSessionAll Error : " + e.getMessage(), e);
				continue;
			}
		}
	}
}

class WebSocketPool {
	private Set<WebSocket> conns;
	private Map<WebSocket, String> websockets;
	private Map<String, WebSocket> ids;

	public WebSocketPool() {
		this.conns = Collections.synchronizedSet(new HashSet<WebSocket>());
		this.websockets = new ConcurrentHashMap<WebSocket, String>();
		this.ids = new ConcurrentHashMap<String, WebSocket>();
	}

	public void sessionOpen(WebSocket conn) {
		conns.add(conn);
	}

	public void sessionLogin(WebSocket conn, String id) {
		this.websockets.put(conn, id);
		this.ids.put(id, conn);
	}

	public void sessionClose(WebSocket conn) {
		Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
		String id = this.websockets.get(conn);
		this.conns.remove(conn);
		this.websockets.remove(conn);
		if (id != null) {
			this.ids.remove(id);
		}
		try {
			if (conn != null)
				conn.close();
		} catch (Exception e) {
			logger.error("WebSocketSessionManager] Error Occured in sessionClose", e);
		}
	}

	public WebSocket getSession(String id) {
		if (this.ids.containsKey(id)) {
			return this.ids.get(id);
		} else {
			return null;
		}
	}

	public Map<String, WebSocket> getSessionMap() {
		return ids;
	}

	public String getId(WebSocket ws) {
		if (this.websockets.containsKey(ws)) {
			return this.websockets.get(ws);
		} else {
			return null;
		}
	}

	public void sessionError(WebSocket conn) {
		String id = this.websockets.get(conn);
		this.conns.remove(conn);
		this.websockets.remove(conn);
		this.ids.remove(id);
	}

	public boolean isContainKeys(WebSocket conn) {
		return this.websockets.containsKey(conn);
	}

	public boolean isContainKeys(String id) {
		return this.ids.containsKey(id);
	}

	public ConcurrentHashMap<String, WebSocket> getList() {
		if (this.ids == null)
			return new ConcurrentHashMap<String, WebSocket>();
		return (ConcurrentHashMap<String, WebSocket>) this.ids;
	}

	public void closeAll() {
		Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
		logger.info("WebSocketPool] closeAll" );
		for(WebSocket conn : this.websockets.keySet()){
			String id = this.websockets.get(conn);
			this.conns.remove(conn);
			this.websockets.remove(conn);
			if (id != null) {
				this.ids.remove(id);
			}
			try {
				if(conn != null) conn.close();
			}
			catch(Exception e) {
				logger.error("WebSocketPool] closeAll Error Occured in sessionClose" + conn, e);
			}
		}
	}
}
