/*
 * Created on 2005. 2. 26.
 */
package com.eactive.eai.adapter.socket2.config;

import com.eactive.eai.adapter.AdapterVO;
import com.eactive.eai.adapter.socket2.common.ExceptionStackTrace;
import com.eactive.eai.adapter.socket2.common.SimpleFuture;
import com.eactive.eai.adapter.socket2.common.TimeUtil;
import com.eactive.eai.adapter.socket2.protocol.ISocketAdapter;
import com.eactive.eai.common.lifecycle.Lifecycle;
import com.eactive.eai.common.lifecycle.LifecycleException;
import com.eactive.eai.common.lifecycle.LifecycleListener;
import com.eactive.eai.common.util.Logger;
import org.apache.mina.common.IoSession;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 
 */
public class SocketAdapterManager implements Lifecycle {
	private Logger logger = SocketLoggerManager.getInstance().getLogger();
	private Logger errLogger = SocketLoggerManager.getInstance().getErrLogger();

	public static final long CONNECTION_CHECK_CYCLE = 60 * 1000L; // every 60 sec.
	public static final long RECOVERY_CHECK_CYCLE = 10 * 1000L; // every 20 sec.
	// 10분간 미상용시 Connection Close; 0으로 설정시 강제적으로 미사용 소켓을 CLOSE하지 않음....
	public static final long UNUSED_CONNECTION_TIMEOUT = 0L; // 10 * 60 * 1000L;

	private static final SocketAdapterManager instance = new SocketAdapterManager();
	private SocketAdapterFactory socketAdapterFactory;
	
	private ConcurrentHashMap<String, ConcurrentHashMap<String, ISocketAdapter>> adapterGroups;
	Random random = new Random();

	private boolean started;
	
	public static SocketAdapterManager getInstance() {
		return instance;
	}

	private SocketAdapterManager() {
		adapterGroups = new ConcurrentHashMap<>();

		socketAdapterFactory = new SocketAdapterFactory();
		started = false;
		logger.debug(socketAdapterFactory.toString() +" started = " + started);
	}

	
	public boolean isStarted() {
		// 자동 생성된 메소드 스텁
		return false;
	}	

	public void start() throws LifecycleException {
		// 자동 생성된 메소드 스텁
		
	}
	
	public void stop() {
    	for (Enumeration<ConcurrentHashMap<String, ISocketAdapter>> e = adapterGroups.elements(); e.hasMoreElements(); ) {
	    	ConcurrentHashMap<String, ISocketAdapter> group = e.nextElement();
	    	for (Enumeration<ISocketAdapter> ee = group.elements(); ee.hasMoreElements(); ) {
	    		ISocketAdapter adapter = ee.nextElement();
	    		try {
		    		SimpleFuture future = adapter.stop();
		    		future.join();
	    		} catch(Exception ex) {
	    			logger.error(adapter.getContext().getAdapterName() + " | " + ex.getMessage(), ex);
	    			errLogger.error(adapter.getContext().getAdapterName() + " | " + ex.getMessage(), ex);
	    		}
	    	}
    	}
	}

    public ConcurrentMap<String, ConcurrentHashMap<String, ISocketAdapter>> getSocketAdapterGroups() {
    	return adapterGroups;
    }
    
    public ISocketAdapter[] getSocketAdapters(String adapterGroupName) {
    	ConcurrentHashMap<String, ISocketAdapter> adapters0 = adapterGroups.get(adapterGroupName);
    	if (adapters0 == null)
    		return null;
    	ISocketAdapter[] adapters = new ISocketAdapter[adapters0.size()];
    	int i = 0;
    	for (Enumeration<ISocketAdapter> e = adapters0.elements(); e.hasMoreElements(); ) {
    		adapters[i++] = e.nextElement();
    	}
    	return adapters;
    }
    
    public ISocketAdapter getSocketAdapter(String adapterGroupName, String adapterName) {
    	ConcurrentHashMap<String, ISocketAdapter> adapters = adapterGroups.get(adapterGroupName);
    	if (adapters == null)
    		return null;
    	
        if (adapterName != null) {
        	return adapters.get(adapterName);
        } else {
        	return adapters.elements().nextElement();
        }
    }

    public ISocketAdapter getOutboundSocketAdapter(String adapterGroupName, String adapterName) {
    	ConcurrentHashMap<String, ISocketAdapter> adapters = adapterGroups.get(adapterGroupName);
    	if (adapters == null)
    		return null;
    	
        if (adapterName != null) {
        	return adapters.get(adapterName);
        } else {
        	// select random
        	Vector<ISocketAdapter> started = new Vector<>();
        	for (Iterator<ISocketAdapter> iter = adapters.values().iterator(); iter.hasNext();) {
        		ISocketAdapter adapter = iter.next();
        		if (adapter.getState() == ISocketAdapter.State.STARTED 
        			&& !"INBOUND".equals(adapter.getContext().getBoundUsage())
        			&& ("N".equals(adapter.getContext().getPermanent()) || adapter.getSessionPool().size() > 0) 
        		) // concern Non-Permanent pool size 0
        			started.add(adapter);
        	}
        	Object[] array = started.toArray();
        	if (array == null || array.length <= 0) {
        		return null;
        	}
        	else if(array.length > 1) {        		
        		int index = random.nextInt(array.length);
        		return (ISocketAdapter) array[index];
        	}
        	else {
        		return (ISocketAdapter) array[0];
        	}
        }
    }
    
    public ISocketAdapter getSocketAdapter(String adapterFullName) {
    	String adapterGroupName = adapterFullName;
        String adapterName = null;
        if (adapterFullName.indexOf('.') != -1) {
        	adapterGroupName = adapterFullName.substring(0, adapterFullName.indexOf('.'));
            adapterName = adapterFullName.substring(adapterFullName.indexOf('.')+1);
        } 

    	ConcurrentHashMap<String, ISocketAdapter> adapters = adapterGroups.get(adapterGroupName);
    	if (adapters == null)
    		return null;
    	
        if (adapterName != null) {
        	return adapters.get(adapterName);
        } else {
        	// select random
        	Vector<ISocketAdapter> started = new Vector<>();
        	for (Iterator<ISocketAdapter> iter = adapters.values().iterator(); iter.hasNext();) {
        		ISocketAdapter adapter = iter.next();
        		if (adapter.getState() == ISocketAdapter.State.STARTED
        				&& adapter.getSessionPool().size() > 0) {
        			started.add(adapter);
        		}
        	}
        	Object[] array = started.toArray();
        	if (array == null || array.length <= 0) {
        		return null;
        	}
        	else if(array.length > 1) {        		
        		int index = random.nextInt(array.length);
        		return (ISocketAdapter) array[index];
        	}
        	else {
        		return (ISocketAdapter) array[0];
        	}
        }
    }

	/**
	 * get adapter context
	 */
	public SocketAdapterContext getContext(String adapterName) {
		ISocketAdapter adapter = getSocketAdapter(adapterName);
		if (adapter != null)
			return adapter.getContext();
		return null;
	}
	
	/**
	 * Adapter Name별 Socket Adapter starting...
	 */
	public String start(String adapterGroupName, String adapterName) {
		ISocketAdapter adapter = getSocketAdapter(adapterGroupName, adapterName);
		if (adapter == null)
			return adapterGroupName + "." + adapterName + " | SocketAdapter Manager - cannot start - socket adapter does not exists.";
		
		try {
			adapter.start().join(60000L);
		} catch(Exception e) {
			return ExceptionStackTrace.dump(e);
		}
		
		return adapterGroupName + "." + adapterName + " | SocketAdapterManager - adapter started";
	}

	
	public String stop(String adapterGroupName, String adapterName) {
//		StringBuffer sb = new StringBuffer();
		ISocketAdapter adapter = getSocketAdapter(adapterGroupName, adapterName);
		if (adapter == null)
			return adapterGroupName + "." + adapterName + " | SocketAdapterManager - socket adapter does not exists.";
		
		try {
			adapter.stop().join();
		} catch(Exception e) {
			return ExceptionStackTrace.dump(e);
		}
		
		return adapterGroupName + "." + adapterName + " | SocketAdapterManager - adapter stopped";		
	}
	

	public void stop(AdapterVO vo) throws Exception {
		stop(vo.getAdapterGroupName(), vo.getName());
	}

	public void addAdapter(ISocketAdapter adapter) throws Exception {
		ConcurrentHashMap adapters = adapterGroups.get(adapter.getContext().getAdapterGroupName());
		if (adapters == null) {
			adapterGroups.put(adapter.getContext().getAdapterGroupName(), new ConcurrentHashMap<String, ISocketAdapter>());
		} else {
			if (adapters.get(adapter.getContext().getAdapterName()) != null) {
				logger.warn(adapter.getContext().getAdapterName() + " | SocketAdapterManager - socket adapter already exist -> replace [" + adapter.getContext().getAdapterGroupName() + ", " + adapter.getContext().getAdapterName() + "]");
				try {
					((ISocketAdapter) adapters.get(adapter.getContext().getAdapterName())).stop().join();
				} catch(Exception e) {
					logger.warn(adapter.getContext().getAdapterName() + " | old adapter stop error.", e);
					//adapter.addRecentError(adapter.getContext().getAdapterName() + " | old adapter stop error.", e);
				} finally {
					// sleep 1 second to avoid BindException
					try {
						Thread.sleep(1000L);
					} catch(Exception e) {
						logger.warn(adapter.getContext().getAdapterName() + " | sleep error.", e);
					}
				}
				//throw new Exception(adapter.getContext().getAdapterName() + " | SocketAdapterManager | start error : Socket adapter aready exist [" + adapter.getContext().getAdapterGroupName() + ", " + adapter.getContext().getAdapterName() + "]");
			}
		}
		adapterGroups.get(adapter.getContext().getAdapterGroupName()).put(adapter.getContext().getAdapterName(), adapter);		
	}


	public synchronized String disconnect(String remoteIP, String remotePort) {
		return null;
	}

    public String toString() {
    	return toString(false);
    }
	
	public String toString(boolean verbose) {
		StringBuilder sb = new StringBuilder();
		//sb.append("SocketAdater Info (AdpaterGroup > Adapter > Session)\n");
        sb.append("---------------------------------------------------------------------------------------------------------------------\n");
		
	    for (Enumeration<String> e = adapterGroups.keys(); e.hasMoreElements();) {
	    	String adapterGroupName = e.nextElement();
	    	ConcurrentHashMap<String, ISocketAdapter> adapters = adapterGroups.get(adapterGroupName);
	    	sb.append("ⓖ AdapterGroup : " + adapterGroupName);
	    	//sb.append(", " + adapterGroups);
	    	sb.append("\n");
		    for (Enumeration<ISocketAdapter> ee = adapters.elements() ; ee.hasMoreElements() ;) {
		    	ISocketAdapter adapter = ee.nextElement();
		    	sb.append("  ⓐ Adapter : ");
		    	sb.append(adapter.getContext().getAdapterName());
		    	sb.append(", " + adapter.getState().name());
		    	sb.append(", " + adapter.getContext().getProtocol());
		    	sb.append(", " + adapter.getContext().getBoundUsage());
		    	sb.append("-" + adapter.getContext().getSocketType());
		    	if (adapter.getContext().isClient()) {
			    	sb.append(" (" + adapter.getContext().getLocalHostName() + ":" + adapter.getContext().getLocalPortNumber());
			    	sb.append("->");
			    	sb.append("/" + adapter.getContext().getHostName() + ":" + adapter.getContext().getPortNumber() + ")");
		    	} else {
		    		sb.append(" (" + adapter.getContext().getHostName() + ":" + adapter.getContext().getPortNumber());
			    	sb.append("<-)");
		    	}
		    	if(adapter.getContext().isSync()) {
		    		sb.append(", SYNC");
		    	} else {
		    		sb.append(", ASYN");
		    	}
		    	if ("Y".equals(adapter.getContext().getAckSupport())) {
		    		sb.append(", ACK");
		    	} else {
		    		sb.append(", NO_ACK");
		    	}
		    	if(adapter.getContext().isPermanent()) {
		    		sb.append(", PERMANENT");
		    	} else {
		    		sb.append(", NON_PERM");
		    	}
		    	sb.append(", " + adapter.getContext().getMaxConnection());
		    	sb.append(", " + adapter.getContext().getMaxThread());
		    	
		    	if ("FIXED_LENGTH_HEADER".equals(adapter.getContext().getProtocol()) 
		    			|| "ADMIN".equals(adapter.getContext().getProtocol())) {
			    	sb.append(", " + adapter.getContext().getLlFieldType());
			    	sb.append(":" + adapter.getContext().getLlFieldOffset());
			    	sb.append(":" + adapter.getContext().getLlFieldLength());
			    	sb.append(":" + adapter.getContext().getLlFieldInclude());
			    	sb.append(":" + adapter.getContext().getLlInclude());
		    	}

		    	if ("VARIABLE_LENGTH_HEADER".equals(adapter.getContext().getProtocol()) 
		    			|| "ADMIN".equals(adapter.getContext().getProtocol())) {
			    	sb.append(", " + adapter.getContext().getLlFieldType());
			    	sb.append(":" + adapter.getContext().getLlFieldOffset());
			    	sb.append(":" + adapter.getContext().getLlFieldLength());
			    	sb.append(":" + adapter.getContext().getLlFieldInclude());
			    	sb.append(":" + adapter.getContext().getLlInclude());
			    	sb.append(":" + adapter.getContext().getRecvHeaderLength());
			    	sb.append(":" + adapter.getContext().getSendHeaderLength());
		    	}

		    	
		    	if (adapter.getContext().isClient() &&adapter.getContext().isPollingUse()) {
		    		sb.append(" (");
			    	sb.append("POLLING, ");
			    	sb.append(", " + adapter.getContext().getPollingTimeout());
			    	sb.append(", " + adapter.getContext().getPollingRequest());
		    		sb.append(")");
		    	}
		    	
		    	//KSH
		    	if (adapter.getContext().isClient() &&adapter.getContext().isInitMsgUse()) {
		    		sb.append(" (");
			    	sb.append("INIT_MSG, ");
			    	sb.append(", " + adapter.getContext().getInitMsgRequest());
		    		sb.append(")");
		    	}
		    	
		    	sb.append(", pool:" + adapter.getSessionPool().remaining() + "/" + adapter.getSessionPool().peekSize());
		    	sb.append(", recv:" + adapter.getMessageRecv());
		    	sb.append(", send:" + adapter.getMessageSend());
		    	sb.append(", error:" + adapter.getRecentErrorCount()); 	

	    		if ("XECURE".equals(adapter.getContext().getProtocol())) {
		    		sb.append(", (");
		    		sb.append(adapter.getContext().getXecureType());
		    		//sb.append(", " + adapter.getContext().getXecureTmode());
		    		//sb.append(", " + adapter.getContext().getXecureEncodeMode());
		    		//sb.append(", " + adapter.getContext().getXecurePassword());
		    		sb.append(", " + adapter.getContext().getXecureConfigPath());
		    		sb.append(", " + adapter.getContext().getXecureRemoteCert());
		    		sb.append(")");
	    		}
		    	
//		    	if (verbose && false) {
//		    		sb.append(", " + adapter.getContext().getDescription());
//		    	}
		    	
		    	sb.append("\n");
//		    	if (verbose && false) {
//		    		sb.append(adapter.getContext() + "\n");
//		    	}
			    for (Iterator<IoSession> iii = adapter.getSessionPool().iterator() ; iii.hasNext(); ) {
			    	IoSession session = iii.next();
			    	sb.append("    ");
			    	if (!session.isConnected())
			    		sb.append("●");
			    	else if (session.isClosing())
			    		sb.append("◐");
			    	else if (System.currentTimeMillis() - session.getLastIoTime() < 1000L)
			    		sb.append("⊙");
			    	else
			    		sb.append("○");
			    	sb.append(" Session :");
			    	//sb.append(" [" + session.getTrafficMask() + "]");
			    	if (session.isClosing())
			    		sb.append(" [CLOSING]");
			    	if (!session.isConnected())
			    		sb.append(" [NOT CONNECTED]");
			    	sb.append(" " + session.getLocalAddress() + (adapter.getContext().isClient() ? "->" : "<-") + session.getRemoteAddress());
			    	sb.append(", " + TimeUtil.formatDate(session.getCreationTime()));
			    	//sb.append(" start:" + DatetimeUtil.getCurrentDateDash(session.getCreationTime())
			    	sb.append(", recv:" + session.getReadMessages());
			    	//sb.append("(" + session.getReadBytes() + ")");
			    	sb.append(", send:" + session.getWrittenMessages());
			    	//sb.append("(" + session.getWrittenBytes() + ")");
			    	sb.append(", idle:" + TimeUtil.elapsedTime(session.getLastIoTime()) + "\n");
			    }
			    
			    if (adapter.getSessionPool().checkedOut() > 0) {
			    	sb.append("       -----------------------------------------------------------------------------------------------\n");
			    }
			    
		    	for (Iterator iii = adapter.getSessionPool().checkedOutIterator() ; iii.hasNext(); ) {
			    	IoSession session = (IoSession) iii.next();
			    	sb.append("    ");
			    	if (!session.isConnected())
			    		sb.append("●");
			    	else if (session.isClosing())
			    		sb.append("◐");
			    	else if (System.currentTimeMillis() - session.getLastIoTime() < 1000L)
			    		sb.append("⊙");
			    	else
			    		sb.append("○");
			    	sb.append(" Session :");
			    	//sb.append(" [" + session.getTrafficMask() + "]");
			    	if (session.isClosing())
			    		sb.append(" [CLOSING]");
			    	if (!session.isConnected())
			    		sb.append(" [NOT CONNECTED]");
			    	sb.append(" " + session.getLocalAddress() + (adapter.getContext().isClient() ? "->" : "<-") + session.getRemoteAddress());
			    	sb.append(", " + TimeUtil.formatDate(session.getCreationTime()));
			    	//sb.append(" start:" + DatetimeUtil.getCurrentDateDash(session.getCreationTime())
			    	sb.append(", recv:" + session.getReadMessages());
			    	//sb.append("(" + session.getReadBytes() + ")");
			    	sb.append(", send:" + session.getWrittenMessages());
			    	//sb.append("(" + session.getWrittenBytes() + ")");
			    	sb.append(", idle:" + TimeUtil.elapsedTime(session.getLastIoTime()) + "\n");
			    }
		    }
	    }		
        sb.append("---------------------------------------------------------------------------------------------------------------------\n");
        //sb.append(queue.toString());
        return sb.toString();
	}

	public void addLifecycleListener(LifecycleListener listener) {
		if(logger.isDebug()) logger.debug("addLifecycleListener : " + listener);
	}

	public LifecycleListener[] findLifecycleListeners() {		
		return new LifecycleListener[0];
	}

	public void removeLifecycleListener(LifecycleListener listener) {	
		if(logger.isDebug()) logger.debug("removeLifecycleListener : " + listener);
	}
	
	public List<String> searchAdapterGroupName(String searchType,String searchValue){
		List<String> adapterGroupNames = new ArrayList<>();
	    for (Enumeration<String> e = adapterGroups.keys(); e.hasMoreElements();) {
	    	String adapterGroupName = e.nextElement();
	    	ConcurrentHashMap<String, ISocketAdapter> adapters = adapterGroups.get(adapterGroupName);
		    
	    	for (Enumeration<ISocketAdapter> ee = adapters.elements() ; ee.hasMoreElements() ;) {
		    	ISocketAdapter adapter = ee.nextElement();
		    	
		    	String value = null;
		    	if ("PROTOCOL".equals(searchType)){
		    		value = adapter.getContext().getProtocol();
		    	}else if ("PORTNUMBER".equals(searchType)){
		    		value = String.valueOf(adapter.getContext().getPortNumber());
		    	}else if ("HOSTNAME".equals(searchType)){
		    		value = adapter.getContext().getHostName();
		    	}
		    	
		    	if (value != null && value.equals(searchValue)){
		    		adapterGroupNames.add(adapterGroupName);
		    		break;
		    	}
		    }
	    }
	    
	    return adapterGroupNames;
	}
	/**
	 * 일자전환 관련
	 * - ATM socket 어댑터를 끊기 위해서 생성
	 * 
	 */
	public void sessionCloseAll(String adapterGroupName){
    	ConcurrentHashMap<String, ISocketAdapter> adapters0 = adapterGroups.get(adapterGroupName);
    	if (adapters0 == null)
    		return ;
    	int i = 0;
    	for (Enumeration<ISocketAdapter> e = adapters0.elements(); e.hasMoreElements(); ) {
    		ISocketAdapter socket  = e.nextElement();
    		socket.getSessionPool().shutdown();
    	}
	}
}
