package org.bench4q.master.domain.entity;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

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.Table;
import javax.persistence.Transient;

import org.apache.log4j.Logger;
import org.bench4q.master.domain.IAggregate;
import org.bench4q.master.domain.repository.TestPlanRepository;
import org.bench4q.master.domain.service.TestResultSave;
import org.bench4q.master.domain.valueobject.transaction.impl.TestPlanLoadApplication;
import org.bench4q.share.enums.master.TestPlanStatus;

@Entity
@Table(name = "testplan")
public class TestPlan implements IAggregate {
	private int id;
	private String name;
	private int sampleCycleInSeconds;
	private Date createDateTime;
	private Date lastRunningTime;
	private User user;
	private String testPlanRunId;
	private String currentStatus;
	private int failTimes;
	private int requiredLoad;
	private Set<TestPlanScript> testPlanScripts;
	private Set<Monitor> monitors;
	private TestResultSave testResultSave;
	private TestPlanRepository repository;

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

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

	@Column(name = "name", nullable = false)
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

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

	public void setSampleCycleInSeconds(int sampleCycleInSeconds) {
		this.sampleCycleInSeconds = sampleCycleInSeconds;
	}

	@Column(name = "testPlanRunId", columnDefinition = "TEXT", nullable = false)
	public String getTestPlanRunId() {
		return testPlanRunId;
	}

	public void setTestPlanRunId(String testPlanRunId) {
		this.testPlanRunId = testPlanRunId;
	}

	@Column(name = "createDateTime", nullable = false)
	public Date getCreateDateTime() {
		return createDateTime;
	}

	public void setCreateDateTime(Date createDateTime) {
		this.createDateTime = createDateTime;
	}

	@Column(name = "lastRunningTime", nullable = false)
	public Date getLastRunningTime() {
		return lastRunningTime;
	}

	public void setLastRunningTime(Date lastRunningTime) {
		this.lastRunningTime = lastRunningTime;
	}

	@ManyToOne
	@JoinColumn(name = "userId", nullable = false)
	public User getUser() {
		return user;
	}

	public void setUser(User user) {
		this.user = user;
	}

	@Column(name = "currentStatus", nullable = false)
	public String getCurrentStatus() {
		return currentStatus;
	}

	public void setCurrentStatus(String currentStatus) {
		this.currentStatus = currentStatus;
	}

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

	public void setFailTimes(int failTimes) {
		this.failTimes = failTimes;
	}

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

	public void setRequiredLoad(int requiredLoad) {
		this.requiredLoad = requiredLoad;
	}

	@OneToMany(mappedBy = "testPlan", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
	public Set<TestPlanScript> getTestPlanScripts() {
		return testPlanScripts;
	}

	public void setTestPlanScripts(Set<TestPlanScript> testPlanScripts) {
		this.testPlanScripts = testPlanScripts;
	}

	@OneToMany(mappedBy = "testPlan", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
	public Set<Monitor> getMonitors() {
		return monitors;
	}

	public void setMonitors(Set<Monitor> monitors) {
		this.monitors = monitors;
	}

	@Transient
	private TestResultSave getTestResultSave() {
		return testResultSave;
	}

	public void setTestResultSave(TestResultSave testResultSave) {
		this.testResultSave = testResultSave;
	}

	@Transient
	private TestPlanRepository getRepository() {
		return repository;
	}

	public void setRepository(TestPlanRepository repository) {
		this.repository = repository;
	}

	public TestPlanScript extracSpecifiedScript(int scriptId) {
		for (TestPlanScript testPlanScript : testPlanScripts) {
			if (testPlanScript.getScript().getId() == scriptId) {
				return testPlanScript;
			}
		}
		return null;
	}

	public Monitor extractSpecifiedMonitor(String hostName) {
		for (Monitor monitor : this.getMonitors()) {
			if (monitor.getHostName().equals(hostName))
				return monitor;
		}
		return null;
	}

	public void run() {
		Logger.getLogger(TestPlan.class).info(
				this.getTestPlanRunId() + " start to run ");
		this.setLastRunningTime(new Date());
		if (!applyForLoad()) {
			Logger.getLogger(TestPlan.class).info("applying the load fails");
			this.failTimes++;
			return;
		}
		if (!distributeScriptAndParams()) {
			Logger.getLogger(TestPlan.class).info(
					"distributeScriptAndParams fails");
			this.failTimes++;
			return;
		}
		doRun();
		doForInRunning();
		this.update();
	}

	public boolean stop() {
		boolean result = true;
		for (TestPlanScript runningScript : this.getTestPlanScripts()) {
			if (!runningScript.stop()) {
				result = false;
			}
		}
		this.setCurrentStatus(TestPlanStatus.Complete.name());
		return result;
	}

	private boolean applyForLoad() {
		boolean result = true;
		TestPlanStatus currentStatus = TestPlanStatus.valueOf(this
				.getCurrentStatus());
		TestPlanLoadApplication transaction = new TestPlanLoadApplication(this);
		try {
			currentStatus = transaction.execute();
		} catch (Exception e) {
			transaction.rollBack();
			result = false;
		}
		this.setCurrentStatus(currentStatus.name());
		Logger.getLogger(TestPlan.class).info(
				this.getTestPlanRunId() + " status is " + this.currentStatus);
		this.update();
		return result;
	}

	private boolean distributeScriptAndParams() {
		// TODO: refactor this to Transaction
		for (TestPlanScript testPlanScript : this.getTestPlanScripts()) {
			if (!testPlanScript.distributeScriptAndParams()) {
				return false;
			}
		}
		return true;
	}

	private void doRun() {
		for (TestPlanScript testPlanScript : this.getTestPlanScripts()) {
			testPlanScript.run();
		}
	}

	/**
	 * This function should be called when the testplan begin to run
	 */
	public void doForInRunning() {
		try {
			final ScheduledExecutorService scheScheduledExecutorService = Executors
					.newScheduledThreadPool(1);
			scheScheduledExecutorService.scheduleAtFixedRate(new Runnable() {
				public void run() {
					if (isFinish()) {
						scheScheduledExecutorService.shutdown();
						return;
					}
					getTestResultSave().update(TestPlan.this,
							collectResult(new Date()));
				}
			}, 0, this.getSampleCycleInSeconds(), TimeUnit.SECONDS);
		} catch (Exception e) {
			Logger.getLogger(TestPlan.class).info(e, e.fillInStackTrace());

		}

	}

	private List<Object> collectResult(Date sampleTime) {
		List<Object> resultList = new LinkedList<Object>();
		for (TestPlanScript testPlanScript : getTestPlanScripts()) {
			List<TestPlanScriptResult> testPlanScriptResults = testPlanScript
					.doAfterRun(sampleTime);
			if (testPlanScriptResults != null)
				resultList.addAll(testPlanScriptResults);
		}
		if (getMonitors() != null) {
			for (Monitor monitor : getMonitors()) {
				List<MonitorResult> monitorResults = monitor
						.doAfterRun(sampleTime);
				if (monitorResults != null) {
					resultList.addAll(monitorResults);
				}

			}
		}
		return resultList;
	}

	public void update() {
		this.getRepository().updateEntity(this);
	}

	@Transient
	public boolean isFinish() {
		for (TestPlanScript testPlanScript : this.getTestPlanScripts()) {
			if (!testPlanScript.isFinish()) {
				return false;
			}
		}
		return true;
	}

	@Transient
	public Date getCurrentStartTime() {
		return getLastRunningTime();
	}

}
