package com.eactive.eai.adapter.socket2.protocol;

import com.eactive.eai.adapter.socket2.common.SMSLogger;
import com.eactive.eai.adapter.socket2.common.SimpleFuture;
import com.eactive.eai.adapter.socket2.config.SocketAdapterContext;
import com.eactive.eai.adapter.socket2.filter.acl.AclFilter;
import com.eactive.eai.adapter.socket2.filter.acl.BlockIpFilter;
import com.eactive.eai.adapter.socket2.filter.acl.L4SkipIpFilter;
import com.eactive.eai.adapter.socket2.filter.datacontrol.DataControlFilter;
import com.eactive.eai.adapter.socket2.filter.executor.BoundedExecutorFilter;
import com.eactive.eai.adapter.socket2.filter.initmsgshake.InitMsgFilter;
import com.eactive.eai.adapter.socket2.filter.limit.ConnectionLimitFilter;
import com.eactive.eai.adapter.socket2.filter.polling.PollingFilter;
import com.eactive.eai.adapter.socket2.filter.ssl.SSLContextFactory;
import com.eactive.eai.adapter.socket2.filter.validation.ValidationFilter;
import edu.emory.mathcs.backport.java.util.concurrent.Executor;
import edu.emory.mathcs.backport.java.util.concurrent.ExecutorService;
import edu.emory.mathcs.backport.java.util.concurrent.Executors;
import edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor;

import org.apache.mina.common.ThreadModel;
import org.apache.mina.common.support.BaseIoAcceptor;
import org.apache.mina.common.support.BaseIoAcceptorConfig;
import org.apache.mina.filter.SSLFilter;

import javax.net.ssl.SSLContext;
import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.List;


public abstract class SocketAdapterServer extends SocketAdapter {
	protected ExecutorService ioProcessExecutor;
	protected BaseIoAcceptor acceptor;
	protected BaseIoAcceptorConfig config;

	protected SocketAdapterServer(SocketAdapterContext context) throws Exception {
		super(context);
		
		config = createAcceptorConfig();
	
		config.setThreadModel(ThreadModel.MANUAL);
		
		/**
		 * filters
		 */
		config.getFilterChain().addFirst("threadPool", new BoundedExecutorFilter(this, executor));
		//config.getFilterChain().addLast("logger", new LoggingFilter());

		if (!"".equals(context.getL4SkipIpList().trim())) {
			config.getFilterChain().addLast("l4SkipIp", new L4SkipIpFilter(this));
		}
		if (!"".equals(context.getBlockIpList().trim())) {
			config.getFilterChain().addLast("blockIp", new BlockIpFilter(this));
		}		
		// polling response 후 LIMIT Filter에 걸리는 경우가 있어 ACL, LIMIT Filter를 앞에 둠
		if (!"".equals(context.getAclList().trim())) {
			config.getFilterChain().addLast("acl", new AclFilter(this));
		}
		
		if (context.getMaxConnection() > 0 || context.getConnLimitPerIp() > 0) {
			config.getFilterChain().addLast("limit", new ConnectionLimitFilter(this));
		}

		if ("Y".equals(context.getSslUse())) {
			SSLContext sslContext = SSLContextFactory.createSSLContext(context);
			SSLFilter sslFilter = new SSLFilter(sslContext);
			config.getFilterChain().addLast("ssl", sslFilter);
		}
	
		if (context.isInitMsgUse()) {
			config.getFilterChain().addLast("initMsg", new InitMsgFilter(this));
		}
		
		if (context.isPollingUse()) {
			config.getFilterChain().addLast("polling", new PollingFilter(this));
		}
		
		if (!"".equals(context.getValidationRule().trim())) {
			config.getFilterChain().addLast("validation", new ValidationFilter(this));
		}
		if (!"".equals(context.getDataControlRule().trim())) {
			config.getFilterChain().addLast("datacontrol", new DataControlFilter(this));
		}
	}

	public SimpleFuture start() throws Exception {
		if ( context.getTraceLevel() >= SocketAdapterContext.TRACE_LEVEL_INFO) {
			logger.info(context.getAdapterName() + " | start server socket adapter.");
		}		

		switch(getState()) {
		case STARTED:
			throw new Exception(context.getAdapterName() + " | socket adapter already started - " + getState().name());
		case STARTING:
			throw new Exception(context.getAdapterName() + " | socket adapter booting process already started - " + getState().name());
		case STOPPING:
			throw new Exception(context.getAdapterName() + " | socket adapter shutdown progressing - " + getState().name());
		default:
			break;
		}

		/**
		 * start protocol handler
		 */
		handler.start();
		
		/**
		 * ioProcessorExecutor
		 */
		ioProcessExecutor = Executors.newCachedThreadPool();
		
		/**
		 * determine ioProcessor count
		 */
		int ioProcessorCount1 = 1 + Runtime.getRuntime().availableProcessors();
		int ioProcessorCount2 = 1 + (context.getMaxThread() / 10);
		int ioProcessorCount = ioProcessorCount1 < ioProcessorCount2 ? ioProcessorCount1 : ioProcessorCount2;
		
		/**
		 * create acceptor
		 */	
		acceptor = createAcceptor(ioProcessorCount, ioProcessExecutor);
		
		setState(State.STARTING);		
		SimpleFuture future = new SimpleFuture();
		
		/**
		 * bind
		 */
		try {
			acceptor.bind(new InetSocketAddress(context.getPortNumber()), handler, config);
			if (!context.isPermanent()) {
				setAdapterStatus(true);
			}
			setState(State.STARTED);
			context.setStarted(true);

			if (context.getTraceLevel() >= SocketAdapterContext.TRACE_LEVEL_INFO) {
				logger.info(context.getAdapterName() + " | socket adapter listening on port " + context.getPortNumber());
			}
			
			future.set(Boolean.TRUE);
			
			return future;
		} catch(Exception e) {
        	logger.error(context.getAdapterName() + " | " + e.getMessage(), e);
			addRecentError(context.getAdapterName() + " | " + e.getMessage(), e);
//			SMSLogger.loggingSMSWithCode(context.getAdapterName(), "RECEAINNA013");
			SMSLogger.loggingSMS4Soc(context.getAdapterName(), context.getDescription(), "RECEAINNA013");
			setAdapterStatus(false);
			context.setStarted(false);
			setState(State.STOPPED);
			future.set(Boolean.FALSE);
			return future;
		}
	}

	public SimpleFuture stop() throws Exception {
		if (context.getTraceLevel() >= SocketAdapterContext.TRACE_LEVEL_INFO) {
			logger.info(context.getAdapterName() + " | stop server socket adapter.");
		}
		
		switch(getState()) {
		case STOPPING:
		case STOPPED:
			throw new Exception(context.getAdapterName() + " | socket adapter already " + getState().name());
		default:
			break;
		}

		return doStop();
	}
	
	public SimpleFuture stopForcely() throws Exception {
		if (context.getTraceLevel() >= SocketAdapterContext.TRACE_LEVEL_INFO) {
			logger.info(context.getAdapterName() + " | stop server socket adapter forcely.");
		}
		
		return doStop();
	}
	
	private SimpleFuture doStop() throws Exception {
		setState(State.STOPPING);
		context.setStarted(false);
		
		final SimpleFuture future = new SimpleFuture();
		
		Runnable runnable = new Runnable() {
			public void run() {
				try {
			        acceptor.unbindAll();
			        handler.stop();
			        getSessionPool().shutdown();
					setAdapterStatus(false);
					setState(State.STOPPED);
					List list = ioProcessExecutor.shutdownNow();
					for (Iterator iter = list.iterator(); iter.hasNext(); ) {
						((Thread) iter.next()).interrupt();
					}
					future.set(Boolean.TRUE);

					if (context.getTraceLevel() >= SocketAdapterContext.TRACE_LEVEL_INFO) {
						logger.info(context.getAdapterName() + " | socket adapter stopped.");
					}
				} catch(Exception e) {
					logger.error(context.getAdapterName() + " | socket adapter stop process failed.", e);
					addRecentError(context.getAdapterName() + " | socket adapter stop process failed.", e);
					future.set(e);
				}
			}
		};
		
		//executor.execute(runnable);
		Thread t = new Thread(runnable);
		t.setName(context.getAdapterName() + "-Terminator");
		t.start();
		
		return future;	
	}
	
	protected abstract BaseIoAcceptorConfig createAcceptorConfig();
	protected abstract BaseIoAcceptor createAcceptor(int ioProcessorCount, Executor ioProcessExecutor);
}
