package org.bench4q.master.domain.entity;

import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.apache.log4j.Logger;
import org.bench4q.master.domain.RunningAgentInterface;
import org.bench4q.master.domain.RunningScriptInterface;
import org.bench4q.master.domain.factory.TestPlanFactory;
import org.bench4q.master.domain.valueobject.datastatistics.RunningScriptSampler;
import org.bench4q.master.domain.valueobject.schedulscript.ExecutionOverTask;
import org.bench4q.master.domain.valueobject.schedulscript.WarmUpOverTask;
import org.bench4q.master.domain.valueobject.transaction.Transaction;
import org.bench4q.master.domain.valueobject.transaction.TransactionFactory;
import org.bench4q.master.exception.ExceptionLog;
import org.bench4q.share.helper.MarshalHelper;
import org.bench4q.share.models.agent.RunScenarioModel;
import org.bench4q.share.models.master.statistics.ScriptResultModel;

@Entity
@Table(name = "TestPlanScript")
public class TestPlanScript implements RunningScriptInterface {
	private int id;
	private Script script;
	private int requireLoad;
	private PlanedConfig planedConfig;
	private TestPlan testPlan;
	private Set<RunningAgentDB> runningAgents;
	private Set<TestPlanScriptResult> testPlanScriptResults;
	private boolean finish;
	private TestPlanFactory testPlanFactory;
	private TransactionFactory transactionFactory;
	private RunningScriptSampler sampler;
	private Logger logger = Logger.getLogger(TestPlanScript.class);
	private static int SECOND_MILISECOND_UNIT_CONVERSION = 1000;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "id", nullable = false)
	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	@ManyToOne
	@JoinColumn(name = "scriptId", nullable = false)
	public Script getScript() {
		return script;
	}

	public void setScript(Script script) {
		this.script = script;
	}

	@Column(name = "requireLoad", nullable = false)
	public int getRequireLoad() {
		return requireLoad;
	}

	public void setRequireLoad(int requireLoad) {
		this.requireLoad = requireLoad;
	}

	@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
	@JoinColumn(name = "configId", nullable = false)
	public PlanedConfig getPlanedConfig() {
		return planedConfig;
	}

	public void setPlanedConfig(PlanedConfig planedConfig) {
		this.planedConfig = planedConfig;
	}

	@ManyToOne
	@JoinColumn(name = "testPlanId")
	public TestPlan getTestPlan() {
		return testPlan;
	}

	public void setTestPlan(TestPlan testPlan) {
		this.testPlan = testPlan;
	}

	@OneToMany(mappedBy = "testPlanScript", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
	public Set<TestPlanScriptResult> getTestPlanScriptResults() {
		return testPlanScriptResults;
	}

	public void setTestPlanScriptResults(
			Set<TestPlanScriptResult> testPlanScriptResults) {
		this.testPlanScriptResults = testPlanScriptResults;
	}

	@OneToMany(mappedBy = "testPlanScript", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
	public Set<RunningAgentDB> getRunningAgentsDB() {
		return runningAgents;
	}

	public void setRunningAgentsDB(Set<RunningAgentDB> runningAgents) {
		this.runningAgents = runningAgents;
	}

	@Column(name = "finish", nullable = false)
	public boolean isFinish() {
		return finish;
	}

	public void setFinish(boolean finish) {
		this.finish = finish;
	}

	@Transient
	public int getScriptId() {
		return this.getScript().getId();
	}

	@Transient
	public RunScenarioModel getScenario() {
		try {
			return (RunScenarioModel) MarshalHelper
					.unmarshal(RunScenarioModel.class, this.getScript()
							.getScriptContent());
		} catch (Exception e) {
			Logger.getLogger(TestPlanScript.class).error(
					ExceptionLog.getStackTrace(e));
			return null;
		}
	}

	@Transient
	public Set<RunningAgentDB> getRunningAgents() {
		return this.getRunningAgentsDB();
	}

	@Transient
	public UUID getTestPlanID() {
		return UUID.fromString(this.getTestPlan().getTestPlanRunId());
	}

	@Transient
	private TestPlanFactory getTestPlanFactory() {
		return testPlanFactory;
	}

	public void setTestPlanFactory(TestPlanFactory testPlanFactory) {
		this.testPlanFactory = testPlanFactory;
	}

	@Transient
	public RunningScriptSampler getSampler() {
		return sampler;
	}

	public void setSampler(RunningScriptSampler sampler) {
		this.sampler = sampler;
	}

	@Transient
	private TransactionFactory getTransactionFactory() {
		return transactionFactory;
	}

	public void setTransactionFactory(TransactionFactory transactionFactory) {
		this.transactionFactory = transactionFactory;
	}

	/**
	 * This should be called when really in running
	 * 
	 * @return
	 */
	private boolean scheduleAsConfig() {
		PlanedConfig testScriptConfig = this.getPlanedConfig();
		if (testScriptConfig == null) {
			return false;
		}
		Timer timer = new Timer();
		timer.schedule(new WarmUpOverTask(), testScriptConfig.getWarmUp()
				* SECOND_MILISECOND_UNIT_CONVERSION);

		ExecutionOverTask executionOverTask = new ExecutionOverTask(this);
		timer.schedule(
				executionOverTask,
				(testScriptConfig.getWarmUp() + testScriptConfig
						.getExecuteRange()) * SECOND_MILISECOND_UNIT_CONVERSION);

		timer.schedule(
				new TimerTask() {
					@Override
					public void run() {
						System.out.println("cool down over!");
					}
				},
				(testScriptConfig.getWarmUp()
						+ testScriptConfig.getExecuteRange() + testScriptConfig
							.getCoolDown()) * SECOND_MILISECOND_UNIT_CONVERSION);
		return true;
	}

	public void doAfterApplyLoad(
			List<? extends RunningAgentInterface> agentsAfterDistribute) {
		for (RunningAgentInterface runningAgent : agentsAfterDistribute) {
			this.getRunningAgentsDB().add((RunningAgentDB) runningAgent);
		}
		this.setSampler(new RunningScriptSampler(Collections
				.unmodifiableCollection(this.getRunningAgentsDB())));
	}

	public List<TestPlanScriptResult> doAfterRun(Date sampleTime) {
		try {
			ScriptResultModel scriptResultModel = this.getSampler()
					.getResultModelFromAgent();

			List<TestPlanScriptResult> testPlanScriptResultList = this
					.getTestPlanFactory().createScriptResultsWithoutId(
							scriptResultModel, this, sampleTime);
			return testPlanScriptResultList;
		} catch (Exception e) {
			logger.info(ExceptionLog.getStackTrace(e));
			return null;
		}
	}

	public List<TestPlanScriptResult> createFinishedResult() {
		ScriptResultModel scriptResultModel = ScriptResultModel
				.buildFinishedResult();
		List<TestPlanScriptResult> testPlanScriptResultList = this
				.getTestPlanFactory().createScriptResultsWithoutId(
						scriptResultModel, this, new Date());
		return testPlanScriptResultList;
	}

	public boolean applyForLoad() {
		return this.applyForLoad(this.getRequireLoad());
	}

	@SuppressWarnings("unchecked")
	public boolean applyForLoad(int requiredLoad) {
		Transaction scriptLoadCommand = this.getTransactionFactory()
				.buildScriptTransaction(this, this.getRequireLoad());
		List<? extends RunningAgentInterface> runningAgents = null;
		try {
			runningAgents = (List<? extends RunningAgentInterface>) scriptLoadCommand
					.execute();
		} catch (Exception e) {
			logger.error("distribute load error, the runningAgents is null", e);
			scriptLoadCommand.rollBack();
			return false;
		}
		this.doAfterApplyLoad(runningAgents);
		this.getTestPlan().update();
		return true;
	}

	public boolean stop() {
		boolean result = true;
		for (RunningAgentInterface runningAgent : this.getRunningAgentsDB()) {
			if (runningAgent == null) {
				continue;
			}
			if (!runningAgent.stop()) {
				logger.error("can't stop the agent with hostName"
						+ runningAgent.getAgent().getHostName()
						+ "and currentRunId is " + runningAgent.getAgentRunId());
				result = false;
				continue;
			}
		}
		this.setFinish(true);
		this.getTestPlan().update();
		return result;
	}

	public boolean run() {
		for (RunningAgentDB runningAgentDB : this.getRunningAgentsDB()) {
			if (!runningAgentDB.run()) {
				return false;
			}
		}
		this.scheduleAsConfig();
		return true;
	}

	public boolean distributeScriptAndParams() {
		// TODO: refactor this to Transaction
		for (RunningAgentDB runningAgent : this.getRunningAgentsDB()) {
			if (!runningAgent.distributeScriptAndParams()) {
				return false;
			}
		}
		return true;
	}

	@Override
	@Transient
	public Date getStartTime() {
		return this.getTestPlan().getCurrentStartTime();
	}
}
