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

import com.eactive.eai.adapter.socket2.filter.executor.BoundedExecutorFilter;
import com.eactive.eai.adapter.socket2.protocol.ByteMessage;
import org.apache.mina.common.DefaultIoFilterChainBuilder;
import org.apache.mina.common.IoFilterAdapter;
import org.apache.mina.common.IoFilterChain;
import org.apache.mina.common.IoFilterChain.Entry;
import org.apache.mina.common.IoSession;

import java.util.Iterator;
import java.util.List;


/**
 * IoFilterChain chain = config.getFilterChain();
 * ReadThrottleFilterBuilder filter = new ReadThrottleFilterBuilder(); 
 * filter.attach(chain);
 */

public class ReadThrottleFilterBuilder {
	public static final String COUNTER = ReadThrottleFilterBuilder.class.getName() + ".counter";

	public static final String SUSPENDED_READS = ReadThrottleFilterBuilder.class.getName() + ".suspendedReads";

	private volatile int maximumConnectionBufferSize = 100; // 1024 * 1024; // 1mb

	public void setMaximumConnectionBufferSize(int maximumConnectionBufferSize) {
		this.maximumConnectionBufferSize = maximumConnectionBufferSize;
	}

	public void attach(IoFilterChain chain) {
		String name = getThreadPoolFilterEntryName(chain.getAll());

		chain.addBefore(name, getClass().getName() + ".add", new Add());
		chain.addAfter(name, getClass().getName() + ".release", new Release());
	}

	public void attach(DefaultIoFilterChainBuilder builder) {
		String name = getThreadPoolFilterEntryName(builder.getAll());

		builder.addBefore(name, getClass().getName() + ".add", new Add());
		builder.addAfter(name, getClass().getName() + ".release", new Release());
	}

	private String getThreadPoolFilterEntryName(List<Entry> entries) {
		Iterator<Entry> i = entries.iterator();

		while (i.hasNext()) {
			Entry entry = i.next();

			if (entry.getFilter().getClass().isAssignableFrom(BoundedExecutorFilter.class)) {
				return entry.getName();
			}
		}

		throw new IllegalStateException("Chain does not contain a ExecutorFilter");
	}

	private void add(IoSession session, int size) {
		synchronized (session) {
			int counter = getCounter(session);
			counter = counter + size;	
			session.setAttribute(COUNTER, new Integer(counter));

			if (counter >= maximumConnectionBufferSize && session.getTrafficMask().isReadable()) {
//System.out.println("----- session -> suspendRead() : ");				
				session.suspendRead();
				session.setAttribute(SUSPENDED_READS);
			}
		}
	}

	private void release(IoSession session, int size) {
		synchronized (session) {
			int counter = Math.max(0, getCounter(session) - size);
			session.setAttribute(COUNTER, new Integer(counter));

			if (counter < maximumConnectionBufferSize && isSuspendedReads(session)) {
//System.out.println("+++++ session -> resumeRead() : ");				
				session.resumeRead();
				session.removeAttribute(SUSPENDED_READS);
			}

		}
	}

	private boolean isSuspendedReads(IoSession session) {
		Boolean flag = (Boolean) session.getAttribute(SUSPENDED_READS);
		return null != flag && flag.booleanValue();
	}

	private int getCounter(IoSession session) {
		Integer i = (Integer) session.getAttribute(COUNTER);
		return null == i ? 0 : i.intValue();
	}

	private class Add extends IoFilterAdapter {
		public void messageReceived(NextFilter nextFilter, IoSession session, Object message)
				throws Exception {
			if (message instanceof ByteMessage) {
				add(session, ((ByteMessage) message).size());
			}

			nextFilter.messageReceived(session, message);
		}
	}

	private class Release extends IoFilterAdapter {
		public void messageReceived(NextFilter nextFilter, IoSession session, Object message)
				throws Exception {
			if (message instanceof ByteMessage) {
				release(session, ((ByteMessage) message).size());
			}

			nextFilter.messageReceived(session, message);
		}
	}
}
