package org.bench4q.agent.scenario.engine;

import java.util.Date;
import java.util.Observable;
import java.util.Observer;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ThreadPoolExecutor.DiscardPolicy;

import org.apache.log4j.Logger;
import org.bench4q.agent.datacollector.DataCollector;
import org.bench4q.agent.datacollector.impl.ScenarioResultCollector;
import org.bench4q.agent.plugin.PluginManager;
import org.bench4q.agent.scenario.Scenario;

public class ScenarioContext implements Observer {
	private static final long keepAliveTime = 10;
	private final UUID testId;
	private final Date startDate;
	private Date endDate;
	private final ThreadPoolExecutor executor;
	private Scenario scenario;
	private boolean finished;
	private final DataCollector dataCollector;
	private final PluginManager pluginManager;
	private Schedule schedule;

	public ScenarioContext(UUID testId, Date startDate,
			ThreadPoolExecutor executor, DataCollector dataCollector,
			PluginManager pluginManager) {
		this.testId = testId;
		this.startDate = startDate;
		this.executor = executor;
		this.dataCollector = dataCollector;
		this.pluginManager = pluginManager;
	}

	public UUID getTestId() {
		return testId;
	}

	public Date getStartDate() {
		return startDate;
	}

	public Date getEndDate() {
		return endDate;
	}

	public void setEndDate(Date endDate) {
		this.endDate = endDate;
	}

	public ThreadPoolExecutor getExecutor() {
		return executor;
	}

	public Scenario getScenario() {
		return scenario;
	}

	public void setScenario(Scenario scenario) {
		this.scenario = scenario;
	}

	public boolean isFinished() {
		return finished;
	}

	public void setFinished(boolean finished) {
		this.finished = finished;
	}

	public DataCollector getDataStatistics() {
		return dataCollector;
	}

	private PluginManager getPluginManager() {
		return pluginManager;
	}

	public Schedule getSchedule() {
		return schedule;
	}

	private void setSchedule(Schedule schedule) {
		this.schedule = schedule;
	}

	public static ScenarioContext buildScenarioContextWithoutScenario(
			UUID testId, int poolSize, PluginManager pluginManager) {
		final ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(
				poolSize);
		ThreadPoolExecutor executor = new ThreadPoolExecutor(poolSize,
				poolSize, keepAliveTime, TimeUnit.MINUTES, workQueue,
				new DiscardPolicy());
		ScenarioContext scenarioContext = new ScenarioContext(testId, new Date(
				System.currentTimeMillis()), executor,
				new ScenarioResultCollector(testId), pluginManager);
		return scenarioContext;
	}

	public ScenarioContext addScenrio(final Scenario scenario,
			Schedule schedule, final long realStartTime) {
		ScenarioContext result = new ScenarioContext(this.testId, new Date(
				realStartTime), this.executor, this.dataCollector,
				this.pluginManager);
		result.setSchedule(schedule);
		result.setEndDate(new Date(result.getSchedule().getScheduleRange()
				+ result.getStartDate().getTime()));
		result.setFinished(this.isFinished());
		result.setScenario(scenario);
		result.getSchedule().addObserver(result);
		return result;
	}

	/**
	 * Now, I tolerate that if the requiredLoad <
	 * this.getExecutor.getCorePoolSize(), then the excess threads will be
	 * killed when its current task complete
	 * 
	 * @param requiredLoad
	 */
	public void updatePopulation(int requiredLoad) {
		if (requiredLoad == 0) {
			requiredLoad = 1;
		}
		System.out.println(requiredLoad);
		this.getExecutor().setCorePoolSize(requiredLoad);
		this.getExecutor().setMaximumPoolSize(requiredLoad);
	}

	public void addATask() {
		if (this.isFinished()) {
			return;
		}
		this.getExecutor().execute(new VUser(this, 1, getPluginManager()));
		Logger.getLogger(this.getClass()).info(
				this.getExecutor().getActiveCount());
	}

	public void initTasks(int currentLoad) {
		this.updatePopulation(currentLoad);
		for (int i = 0; i < this.getSchedule().getMaxLoad() + 2; i++) {
			addATask();
		}
		addATask();
	}

	@Override
	public void update(Observable o, Object arg) {
		try {
			Schedule schedule = (Schedule) o;
			if (schedule.hasReachEnd()) {
				stop();
			} else {
				fillTaskIfRequired();
				this.updatePopulation((Integer) arg);
				Logger.getLogger(this.getClass()).info(
						"Active thread : "
								+ this.getExecutor().getActiveCount());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void fillTaskIfRequired() {
		int taskInPool = this.getExecutor().getPoolSize()
				- (this.getExecutor().getActiveCount() + this.getExecutor()
						.getQueue().size());
		if (taskInPool > 0) {
			for (int i = 0; i < taskInPool; i++) {
				addATask();
			}
		}
	}

	public void stop() {
		this.getSchedule().stop();
		this.setFinished(true);
		this.getExecutor().shutdownNow();
	}
}
