package org.anair.disruptor;

import org.anair.disruptor.exception.DisruptorExceptionHandler;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lmax.disruptor.EventTranslator;
import com.lmax.disruptor.dsl.EventHandlerGroup;
import com.lmax.disruptor.dsl.ProducerType;

/**
 * Create and configure a default LMAX Disruptor spring bean.
 * 
 * <p>Required values:
 * <ul>
 * 	<li>Executor Thread name: This will help in debugging disruptor logs by looking for the thread name.</li>
 * 	<li>EventFactory: Factory wrapper around the model object. This is used to prepare the ring buffer with the model type objects. </li>
 * 	<li>EventHandlerChain: Design the dependency barrier and event processor/consumer dependencies in a chained manner.</li>
 * </ul>
 * 
 * <p>Default values, if not provided through Spring configuration:
 * <ul>
 * 	<li>Ring buffer size: 1024</li>
 *  <li>Producer Type: {@link ProducerType.SINGLE}</li>
 *  <li>Wait Strategy Type: {@link WaitStrategyType.BLOCKING}</li>
 * </ul>
 * 
 * <p>
 * Sample Spring configuration:
 * <pre>{@code
	<bean id="billingDisruptor" class="org.anair.disruptor.DefaultDisruptorConfig"
		init-method="init" destroy-method="controlledShutdown">

		<property name="threadName" value="billingThread" />
		<property name="eventFactory">
			<bean
				class="org.anair.disruptor.eventfactory.BillingEvent" />
		</property>
		<property name="eventHandlerChain">
			<array>
				<bean class="org.anair.disruptor.EventHandlerChain" scope="prototype">
					<constructor-arg name="currentEventHandlers">
						<array value-type="com.lmax.disruptor.EventHandler">
							<ref bean="journalBillingEventProcessor" />
							<ref bean="billingValidationEventProcessor" />
						</array>
					</constructor-arg>
					<constructor-arg name="nextEventHandlers">
						<array value-type="com.lmax.disruptor.EventHandler">
							<ref bean="billingBusinessEventProcessor" />
							<ref bean="corporateBillingBusinessEventProcessor" />
							<ref bean="customerSpecificBillingBusinessEventProcessor" />
						</array>
					</constructor-arg>
				</bean>
			</array>
		</property>
	</bean>

 * }</pre>
 * 
 * @see <a href="https://github.com/LMAX-Exchange/disruptor/wiki/Getting-Started">LMAX Disruptor</a> 
 * @author Anoop Nair
 *
 * @param <T>
 */
public class DefaultDisruptorConfig<T> extends BaseDisruptorConfig<T>{
	private static final Logger LOG = LoggerFactory.getLogger(DefaultDisruptorConfig.class);

	private EventHandlerChain<T>[] eventHandlerChain;

	@Override
	public void publish(EventTranslator<T> eventTranslator){
		getRingBuffer().publishEvent(eventTranslator);
		LOG.debug("Published " + eventTranslator.getClass().getSimpleName()  +" event to sequence: " + getCurrentLocation());
	}
	
	@Override
	public void disruptorEventHandler() {
		Validate.notEmpty(eventHandlerChain, "Define a Event Handler Chain.");
		disruptorEventHandlerChain();
	}

	@Override
	public void disruptorExceptionHandler() {
		getDisruptor().setDefaultExceptionHandler(new DisruptorExceptionHandler<T>(getThreadName()));
	}
	
	private void disruptorEventHandlerChain() {
		for(int i=0;i<eventHandlerChain.length;i++){
			EventHandlerChain<T> eventHandlersChain = eventHandlerChain[i];
			EventHandlerGroup<T> eventHandlerGroup = null;
			if(i == 0){
				eventHandlerGroup = getDisruptor().handleEventsWith(eventHandlersChain.getCurrentEventHandlers());
			}else{
				eventHandlerGroup = getDisruptor().after(eventHandlersChain.getCurrentEventHandlers());
			}
			
			if(! ArrayUtils.isEmpty(eventHandlersChain.getNextEventHandlers())){
				eventHandlerGroup.then(eventHandlersChain.getNextEventHandlers());
			}
		}
		
		getEventProcessorGraph();
	}
	
	public String getEventProcessorGraph(){
		StringBuilder str = new StringBuilder();
		for(int i=0;i<eventHandlerChain.length;i++){
			str.append("\n");
			str.append(eventHandlerChain[i].printDependencyGraph());
		}
		LOG.info(str.toString());
		
		return str.toString();
	}

	public void setEventHandlerChain(EventHandlerChain<T>[] eventHandlerChain) {
		this.eventHandlerChain = eventHandlerChain;
	}

}