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

import java.time.Duration;
import java.util.Properties;

import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

import com.eactive.eai.common.lifecycle.Lifecycle;
import com.eactive.eai.common.lifecycle.LifecycleException;
import com.eactive.eai.common.lifecycle.LifecycleListener;
import com.eactive.eai.common.lifecycle.LifecycleSupport;
import com.eactive.eai.common.message.EAIMessage;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.env.ElinkConfig;

public class AsyncLoggingPoolManager implements Lifecycle {
	static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
	private static AsyncLoggingPoolManager instance;
	
	private static boolean stopped = true;
	 
	private int maxPoolSize = 10;
	private int initPoolSize = 8;
	
	private boolean initOnStartup = true;
	
	private ObjectPool<LoggingPoolObject> pool = null;
	
	/**
	 * 기동 여부
	 */
	private boolean started;

	/**
	 * LifeccyleSupport object
	 */
	private LifecycleSupport lifecycle = new LifecycleSupport(this);
	
	public static synchronized AsyncLoggingPoolManager getInstance() {
		if(instance == null) {
			instance = new AsyncLoggingPoolManager();
		}
		return instance;
	}
	
	public int getMaxPoolSize() {
		return maxPoolSize;
	}

	public void setMaxPoolSize(int maxPoolSize) {
		this.maxPoolSize = maxPoolSize;
	}

	public int getInitPoolSize() {
		return initPoolSize;
	}

	public void setInitPoolSize(int initPoolSize) {
		this.initPoolSize = initPoolSize;
	}

	public boolean isInitOnStartup() {
		return initOnStartup;
	}

	public void setInitOnStartup(boolean initOnStartup) {
		this.initOnStartup = initOnStartup;
	}

	public void publish(EAIMessage message, Properties prop) throws Exception {
		LoggingPoolObject queue = null;
		try {
			queue = borrowObject();
			queue.putMessage(message, prop);
		}
		catch(Exception ex) {
			logger.error("publish failed.", ex);
			throw ex;
		}
		finally {
			if(queue != null) returnObject(queue);
		}
	}
	
	public void init() {
		if(pool != null) shutdown();
		maxPoolSize = ElinkConfig.getAsyncPoolMaxSize();
		initPoolSize = ElinkConfig.getAsyncPoolInitSize();
		initOnStartup = ElinkConfig.isAsyncInitOnstartup();
		
		if(logger.isWarn()) {
			logger.warn("<< init start - maxPoolSize = {}, initPoolSize = {}, initOnStartup = {}", maxPoolSize, initPoolSize, initOnStartup);
		}
		@SuppressWarnings("rawtypes")
		GenericObjectPoolConfig config = new GenericObjectPoolConfig();
		LoggingPoolObjectFactory factory = new LoggingPoolObjectFactory();
		
		config.setMaxTotal(maxPoolSize);
		//config.setMinIdle(maxPoolSize); // default: 0
		//config.setMaxIdle(maxPoolSize); // default: 8
        config.setMaxWait(Duration.ofSeconds(5));        
        // add proper config options
//        config.setLifo(true);
        pool = new GenericObjectPool<LoggingPoolObject>(factory, config);
        
        if(initOnStartup) {
        	LoggingPoolObject[] pools = new LoggingPoolObject[initPoolSize];
	        for(int i=0; i<initPoolSize; i++) {
	        	try {
	        		logger.warn(">> borrowObject LoggingPoolObject-"+ i);
					pools[i] = pool.borrowObject();
				} catch (Exception e) {
					// nothing to do
				}
	        }
	        for(int i=0; i<initPoolSize; i++) {
	        	try {
	        		logger.warn("<< returnObject LoggingPoolObject-"+ i);
	        		pool.returnObject(pools[i]);
				} catch (Exception e) {
					// nothing to do
				}
	        }
        }
        stopped = false;
        logger.warn("<< init end");
	}
	
	public LoggingPoolObject borrowObject() throws Exception {
		if(stopped) throw new PoolShutdownException();
		return pool.borrowObject();
	}
	
	public void returnObject(LoggingPoolObject object) throws Exception {
		if(stopped) throw new PoolShutdownException();
		pool.returnObject(object);
	}
	
	public void shutdown() {
		if(logger.isWarn()) {
			logger.warn("<< shutdown start");
		}
		int retry = 0;
		 
		try {
			if(pool != null) {
				while(true) {
					pool.close();
					logger.warn("close pool.getNumActive() = "+pool.getNumActive());
					if(pool.getNumActive() == 0 || retry > 10) break;
					logger.warn("close - sleep 100ms");
					Thread.sleep(100);
					retry++;
				}
			}
		} catch (Exception e) {
			logger.error("shutdown failed.", e);
		}
		finally {
			stopped = true;
			pool = null;
		}
		logger.warn(">> shutdown end");
	}
	@Override
	public void addLifecycleListener(LifecycleListener listener) {
		lifecycle.addLifecycleListener(listener);
	}

	@Override
	public LifecycleListener[] findLifecycleListeners() {
		return lifecycle.findLifecycleListeners();
	}

	@Override
	public void removeLifecycleListener(LifecycleListener listener) {
		lifecycle.removeLifecycleListener(listener);
	}

	@Override
	public void start() throws LifecycleException {
		if (started)
			throw new LifecycleException("RECEAICRT201");

		lifecycle.fireLifecycleEvent(STARTING_EVENT, this);
		// init pool & disruptor component
		init();
		started = true;
		lifecycle.fireLifecycleEvent(STARTED_EVENT, this);		
	}
	
	@Override
	public void stop() throws LifecycleException {
		if (!started)
			throw new LifecycleException("RECEAICRT203");
		lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
		shutdown();
		started = false;
		lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
	}

	@Override
	public boolean isStarted() {
		return this.started;
	}

}
