package com.eactive.eai.common.logger.async;

import java.util.Properties;
import java.util.concurrent.TimeUnit;

import com.eactive.eai.common.message.EAIMessage;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.env.ConfigKeys;
import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.BusySpinWaitStrategy;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.SleepingWaitStrategy;
import com.lmax.disruptor.TimeoutBlockingWaitStrategy;
import com.lmax.disruptor.WaitStrategy;
import com.lmax.disruptor.WorkHandler;
import com.lmax.disruptor.YieldingWaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;

public class LoggingPoolObject {
	static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
	Disruptor<LoggingEvent> disruptor = null;
	RingBuffer<LoggingEvent> ringBuffer = null;
	int id = 0;
	int queueMax =  (int)Math.pow(2, 10);
	int workerSize = 0;

	public LoggingPoolObject() {
		
	}
	
	private WaitStrategy getWaitStrategy(String waitStrategy) {
		WaitStrategy ws = null;
		if(ConfigKeys.LOGGER_ASYNC_WAITSTRATEGY_BLOCK.equals(waitStrategy)) {
			// throughput and low-latency are not as important as CPU resource
			return new BlockingWaitStrategy();
		}
		if(ConfigKeys.LOGGER_ASYNC_WAITSTRATEGY_SLEEP.equals(waitStrategy)) {
			// default : 200, 100ns
//			return new SleepingWaitStrategy();
			// default : 200, 10 ms
			return new SleepingWaitStrategy(200, 10 * 1000000);
		}
		if(ConfigKeys.LOGGER_ASYNC_WAITSTRATEGY_TIME.equals(waitStrategy)) {
			// 100ms
			return new TimeoutBlockingWaitStrategy(100 * 1000, TimeUnit.MICROSECONDS);
		}
		if(ConfigKeys.LOGGER_ASYNC_WAITSTRATEGY_BUSYSPIN.equals(waitStrategy)) {
			// when threads can be bound to specific CPU cores.
			return new BusySpinWaitStrategy();
		}
		if(ConfigKeys.LOGGER_ASYNC_WAITSTRATEGY_YIELD.equals(waitStrategy)) {
			return new YieldingWaitStrategy();
		}
		return ws;
	}
	
	public LoggingPoolObject(int id, int queueSize, int workerSize, String waitStrategy) {
		this.id = id;
		if(logger.isWarn()) {
		    logger.warn(String.format(">> Disruptor-%d queueSize = %d", id, queueSize));
		    logger.warn(String.format(">> Disruptor-%d workerSize = %d", id, workerSize));
		    logger.warn(String.format(">> Disruptor-%d waitStrategy = %s", id, waitStrategy));
		}
	    CustomThreadFactory tFactory = new CustomThreadFactory();

	    disruptor = new Disruptor<LoggingEvent>(LoggingEvent.EVENT_FACTORY, queueSize, tFactory,
        		ProducerType.SINGLE, 
        		getWaitStrategy(waitStrategy));
//        BlockingWaitStrategy | SleepingWaitStrategy | YieldingWaitStrategy | BusySpinWaitStrategy
        if(workerSize > 1) {
		    WorkHandler<LoggingEvent>[] handlers = new WorkHandler[workerSize];
	        for(int i=0; i< handlers.length; i++) {
	        	// TODO : 현재는 delay 없이 처리하도록 하고, 추후 DB부하를 줄이려면 sleep을 정의.
	        	CustomWorkHandler handler = new CustomWorkHandler(String.format("CustomWorkHandler%d-%d",id, i), 0, 1); 
		        handlers[i] = handler;
	        }
	        disruptor.handleEventsWithWorkerPool(handlers);	        
        }
        else {
        	CustomEventHandler handler = new CustomEventHandler(String.format("CustomEventHandler%d-%d",id, 0), 0, 1);
        	disruptor.handleEventsWith(handler);
        }
        
        try {
	        
	        logger.warn(String.format(">> disruptor-%d.start",id));
	        disruptor.start();
	        ringBuffer = disruptor.getRingBuffer();
        }
        catch(Exception ex) {
        	ex.printStackTrace();
        }
        finally {
        	;
        }
	}
	
	public void putMessage(EAIMessage message, Properties prop) {
		long seq = ringBuffer.next();
        try {
        	if(logger.isDebug()) {
        		logger.debug( String.format("disruptor-%d publish : ringBuffer seq = %d uuid=%s logSeq=%s",id, seq, message.getSvcOgNo() ,message.getLogPssSno()) );
        	}
        	LoggingEvent loggingEvent = ringBuffer.get(seq);            
        	loggingEvent.setMessage(message);
        	loggingEvent.setProperty(prop);
        }
        finally {
        	ringBuffer.publish(seq);
        }
	}

	public void shutdown() {
		if(disruptor == null) return;
		while(true) {
    		if(queueMax == disruptor.getRingBuffer().remainingCapacity()) break;
    		try {
    			logger.warn(String.format("disruptor-%d Sleep 100 ms.", id));
				Thread.sleep(100);
			} catch (InterruptedException e) {
				;
			}
    	}
		if(logger.isWarn()) {
			logger.warn(String.format("<< disruptor-%d shutdown remainingCapacity : %d",id, disruptor.getRingBuffer().remainingCapacity()) );
		}
    	if(disruptor !=null) disruptor.shutdown();
	}
}
