package org.gcube.accounting.aggregator;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

import org.gcube.accounting.datamodel.validations.validators.MatcherReplace;
import org.gcube.accounting.persistence.AccountingPersistenceConfiguration;
import org.gcube.com.fasterxml.jackson.databind.JavaType;
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
import org.gcube.documentstore.persistence.ExecutorUtils;
import org.gcube.documentstore.records.DSMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RegexRulesAggregator implements Runnable {
	
	private static final Logger logger = LoggerFactory.getLogger(RegexRulesAggregator.class);
	
	public static final ScheduledExecutorService REGEX_REDISCOVERY_POOL;
	
	protected static final String DELAY = "delay";
	protected static final String TIME_UNIT = "timeUnit";
	
	protected static final String MATCHER_REPLACE_RULES = "MatcherReplaceRules";
	
	static {
		
		REGEX_REDISCOVERY_POOL = Executors.newScheduledThreadPool(50, new ThreadFactory() {
			
			private int counter = 0;
			private static final String prefix = "AggregationRegexRediscoveryThread";	 
	
			public Thread newThread(Runnable r) {
			   return new Thread(r, prefix + "-" + counter++);
			}
		});
		
	}
	
	protected TimeUnit timeUnit = TimeUnit.MINUTES;
	protected long delay =  TimeUnit.MINUTES.toMinutes(15);
	
	protected ScheduledFuture<?> rulesReloader;
	
	protected List<MatcherReplace> matcherReplaceList;
	
	protected AccountingPersistenceConfiguration accountingPersistenceConfiguration;
	
	protected static RegexRulesAggregator instance;
	
	protected boolean changeRate; 
	
	protected ScheduledFuture<?> reloadAggregatorRules;
	
	public synchronized static RegexRulesAggregator getInstance() {
		if(instance==null) {
			instance = new RegexRulesAggregator();
		}
		return instance;
	}
	
	protected RegexRulesAggregator() {
		matcherReplaceList = new ArrayList<>();
		changeRate = false;
		readConfiguration();
	}
		
	public List<MatcherReplace> getMatcherReplaceList() {
		synchronized(matcherReplaceList) {
			return matcherReplaceList;
		}
	}

	public MatcherReplace addMatcherReplace(MatcherReplace matcherReplace) {
		synchronized(matcherReplaceList) {
			matcherReplaceList.add(matcherReplace);
		}
		return matcherReplace;
	}
	
	public void vacumRules() {
		synchronized(matcherReplaceList) {
			matcherReplaceList = new ArrayList<>();
		}
	}
	
	
	public void readConfiguration() {
		try {
			accountingPersistenceConfiguration = new AccountingPersistenceConfiguration(this.getClass());
			
			try {
				long oldDelay = delay;
				
				String delayString = accountingPersistenceConfiguration.getProperty(DELAY);
				delay = Long.parseLong(delayString);
				if(oldDelay!=delay) {
					changeRate = true;
				}
				
				TimeUnit oldTimeUnit = timeUnit;
				
				String timeUnitString = accountingPersistenceConfiguration.getProperty(TIME_UNIT);
				timeUnit = TimeUnit.valueOf(timeUnitString.toUpperCase());
				
				if(oldTimeUnit.ordinal() != timeUnit.ordinal()) {
					changeRate = true;
				}
				
			}catch (Exception e) {
				logger.warn("Unable to retrieve regex reload delay. Goign to use last known delay {} {}", delay, timeUnit.name().toLowerCase());
			}
			
			String rulesString = accountingPersistenceConfiguration.getProperty(MATCHER_REPLACE_RULES);
			ObjectMapper mapper = DSMapper.getObjectMapper();
			JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, MatcherReplace.class);
			
			List<MatcherReplace> rules = mapper.readValue(rulesString, type);
			synchronized(matcherReplaceList) {
				matcherReplaceList = rules;
			}
			
		} catch(Exception e) {
			logger.error("Unable to properly load RegexRules", e);
		}
	}
	
	@Override
	public void run() {
		readConfiguration();
		if(changeRate) {
			stop();
		}
		if(reloadAggregatorRules == null) {
			reloadAggregatorRules = REGEX_REDISCOVERY_POOL.scheduleAtFixedRate(this, delay, delay, timeUnit);
		}
	}
	
	/**
	 * Stop rule reloader. Use only if you really know what you do.
	 */
	public void stop() {
		if(reloadAggregatorRules != null) {
			try {
				reloadAggregatorRules.cancel(true);
				reloadAggregatorRules = null;
			}catch (Throwable t) {
				logger.error("Unable to properly stop {} reloader", this.getClass().getSimpleName(), t);
			}
		}
	}
	
	@Deprecated
	public void start() {
		
	}
	
	public static void shutdown() {
		RegexRulesAggregator.getInstance().stop();
		ExecutorUtils.shutDownPool(RegexRulesAggregator.REGEX_REDISCOVERY_POOL);
	}
	
}
