package com.eactive.eai.adapter.socket2.filter.executor;

import com.eactive.eai.adapter.socket2.config.SocketAdapterContext;
import com.eactive.eai.adapter.socket2.protocol.ISocketAdapter;
import edu.emory.mathcs.backport.java.util.concurrent.Executor;
import org.apache.mina.common.IdleStatus;
import org.apache.mina.common.IoFilterAdapter;
import org.apache.mina.common.IoSession;
import org.apache.mina.util.ByteBufferUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;


public class ExecutorFilter extends IoFilterAdapter {
	private final Logger logger = LoggerFactory.getLogger(getClass());
	private final Executor executor;
	private ISocketAdapter adapter;
	private SocketAdapterContext context;

	/**
	 * Creates a new instace with the default thread pool implementation (
	 * 
	 * <tt>new ThreadPoolExecutor(16, 16, 60, TimeUnit.SECONDS, new LinkedBlockingQueue() )</tt>
	 * ).
	 */
	/*
	public ExecutorFilter() {
		this(new ThreadPoolExecutor(16, 16, 60, TimeUnit.SECONDS, new LinkedBlockingQueue()));
	}
	*/

	/**
	 * Creates a new instance with the specified <tt>executor</tt>.
	 */
	/*
	public ExecutorFilter(Executor executor) {
		if (executor == null) {
			throw new NullPointerException("executor");
		}

		this.executor = executor;
	}
	*/

	/**
	 * Creates a new instance with the specified <tt>executor</tt>.
	 */
	// ----------------------------------------------------------------------
	// added by bmchae
	// ----------------------------------------------------------------------
	public ExecutorFilter(ISocketAdapter adapter, Executor executor) {
		this.executor = executor;
		this.adapter = adapter;
		this.context = adapter.getContext();
	}

	/**
	 * Returns the underlying {@link Executor} instance this filter uses.
	 */
	public Executor getExecutor() {
		return executor;
	}

	private void fireEvent(NextFilter nextFilter, IoSession session, EventType type, Object data) {
		Event event = new Event(type, nextFilter, data);
		SessionBuffer buf = SessionBuffer.getSessionBuffer(session);

		synchronized (buf.eventQueue) {
			buf.eventQueue.add(event);
			if (buf.processingCompleted) {
				buf.processingCompleted = false;
				if (logger.isDebugEnabled()) {
					logger.debug("Launching thread for " + session.getRemoteAddress());
				}

				try {
					executor.execute(new ProcessEventsRunnable(context, buf));
				} catch(RejectedExecutionException e) {
					throw new RuntimeException("cannot allocate thread -> increase thread pool size (max.threads)", e);
				}
			} else { //if (type==EventType.CLOSED  || type==EventType.EXCEPTION) {
				//--------------------------------------------------------------
				// added by bmchae
				//--------------------------------------------------------------
				//--------------------------------------------------------------
				// client && sync && non-permanent 일 경우 CLOSED/EXCEPTION event 무시
				//--------------------------------------------------------------
				
//				StringBuffer sb = new StringBuffer();
//				sb.append("EventQueue=[");
//				for (Iterator iter = buf.eventQueue.iterator(); iter.hasNext();) {
//					Event ev = (Event) iter.next();
//					sb.append(ev.getType().toString() + ",");
//				}
//				sb.append("]");
//				adapter.addRecentError(context.getAdapterName() + " | " +sb.toString());
//				logger.info(context.getAdapterName() + " | " +sb.toString());
//				System.out.println("######################### " + type + " | " + data);

				if (context.isClient() 
					&& context.isSync()
					&& !context.isPermanent() 
					//&& context.isBlocking()
				    && (type.equals(EventType.CLOSED) 
				       || (type.equals(EventType.EXCEPTION) && data instanceof java.io.IOException))) {				
					return;
				}

				try {
					executor.execute(new ProcessEventsRunnable(context, buf));
				} catch(RejectedExecutionException e) {
					throw new RuntimeException("cannot allocate thread -> increase thread pool size (max.threads)", e);
				}
			}
		}
	}

	private static class SessionBuffer {
		private static final String KEY = SessionBuffer.class.getName() + ".KEY";

		private static SessionBuffer getSessionBuffer(IoSession session) {
			synchronized (session) {
				SessionBuffer buf = (SessionBuffer) session.getAttribute(KEY);
				if (buf == null) {
					buf = new SessionBuffer(session);
					session.setAttribute(KEY, buf);
				}
				return buf;
			}
		}

		private final IoSession session;

		private final List eventQueue = new ArrayList();

		private boolean processingCompleted = true;

		private SessionBuffer(IoSession session) {
			this.session = session;
		}
	}

	protected static class EventType {
		public static final EventType OPENED = new EventType("OPENED");
		public static final EventType CLOSED = new EventType("CLOSED");
		public static final EventType READ = new EventType("READ");
		public static final EventType WRITTEN = new EventType("WRITTEN");
		public static final EventType RECEIVED = new EventType("RECEIVED");
		public static final EventType SENT = new EventType("SENT");
		public static final EventType IDLE = new EventType("IDLE");
		public static final EventType EXCEPTION = new EventType("EXCEPTION");

		private final String value;

		private EventType(String value) {
			this.value = value;
		}

		public String toString() {
			return value;
		}
	}

	protected static class Event {
		private final EventType type;

		private final NextFilter nextFilter;

		private final Object data;

		Event(EventType type, NextFilter nextFilter, Object data) {
			this.type = type;
			this.nextFilter = nextFilter;
			this.data = data;
		}

		public Object getData() {
			return data;
		}

		public NextFilter getNextFilter() {
			return nextFilter;
		}

		public EventType getType() {
			return type;
		}
	}
	
	public void sessionCreated(NextFilter nextFilter, IoSession session) {
		nextFilter.sessionCreated(session);
	}

	public void sessionOpened(NextFilter nextFilter, IoSession session) {
		fireEvent(nextFilter, session, EventType.OPENED, null);
	}

	public void sessionClosed(NextFilter nextFilter, IoSession session) {
		fireEvent(nextFilter, session, EventType.CLOSED, null);
	}

	public void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) {
		fireEvent(nextFilter, session, EventType.IDLE, status);
	}

	public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) {
		fireEvent(nextFilter, session, EventType.EXCEPTION, cause);
	}

	public void messageReceived(NextFilter nextFilter, IoSession session, Object message) {
		ByteBufferUtil.acquireIfPossible(message);
		fireEvent(nextFilter, session, EventType.RECEIVED, message);
	}

	public void messageSent(NextFilter nextFilter, IoSession session, Object message) {
		ByteBufferUtil.acquireIfPossible(message);
		fireEvent(nextFilter, session, EventType.SENT, message);
	}

	protected void processEvent(NextFilter nextFilter, IoSession session, EventType type,
			Object data) {
		//----------------------------------------------------------------------
		// added by bmchae
		//----------------------------------------------------------------------
		adapter.getThreadMap().put(Thread.currentThread().getName(), Thread.currentThread());
		try {
			if (type.equals(EventType.RECEIVED)) {
				nextFilter.messageReceived(session, data);
				ByteBufferUtil.releaseIfPossible(data);
			} else if (type.equals(EventType.SENT)) {
				nextFilter.messageSent(session, data);
				ByteBufferUtil.releaseIfPossible(data);
			} else if (type.equals(EventType.EXCEPTION)) {
				nextFilter.exceptionCaught(session, (Throwable) data);
			} else if (type.equals(EventType.IDLE)) {
				nextFilter.sessionIdle(session, (IdleStatus) data);
			} else if (type.equals(EventType.OPENED)) {
				nextFilter.sessionOpened(session);
			} else if (type.equals(EventType.CLOSED)) {
				nextFilter.sessionClosed(session);
			}
		} finally {
			adapter.getThreadMap().remove(Thread.currentThread().getName());
		}
	}

	public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) {
		nextFilter.filterWrite(session, writeRequest);
	}

	public void filterClose(NextFilter nextFilter, IoSession session) throws Exception {
		nextFilter.filterClose(session);
	}

	private class ProcessEventsRunnable implements Runnable {
		private final SessionBuffer buffer;
		private SocketAdapterContext context;

		ProcessEventsRunnable(SocketAdapterContext context, SessionBuffer buffer) {
			this.context = context;
			this.buffer = buffer;
		}

		public void run() {
			//------------------------------------------------------------------
			// added by bmchae
			//------------------------------------------------------------------
			if (Thread.currentThread().getName().startsWith("pool-") && context != null) {
				Thread.currentThread().setName(
						context.getAdapterName() + "-" + Thread.currentThread().getName());
			}

			while (true) {
				Event event;

				synchronized (buffer.eventQueue) {
					if (buffer.eventQueue.isEmpty()) {
						buffer.processingCompleted = true;
						break;
					}

					event = (Event) buffer.eventQueue.remove(0);
				}

				processEvent(event.getNextFilter(), buffer.session, event.getType(), event
						.getData());
			}

			if (logger.isDebugEnabled()) {
				logger.debug("Exiting since queue is empty for "
						+ buffer.session.getRemoteAddress());
			}
		}
	}
}
