package org.bench4q.master.domain.factory;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import javax.xml.bind.JAXBException;

import org.apache.log4j.Logger;
import org.bench4q.master.Main;
import org.bench4q.master.domain.entity.Monitor;
import org.bench4q.master.domain.entity.MonitorResult;
import org.bench4q.master.domain.entity.PlanedConfig;
import org.bench4q.master.domain.entity.RunningAgentDB;
import org.bench4q.master.domain.entity.Script;
import org.bench4q.master.domain.entity.TestPlan;
import org.bench4q.master.domain.entity.TestPlanScript;
import org.bench4q.master.domain.entity.TestPlanScriptResult;
import org.bench4q.master.domain.entity.User;
import org.bench4q.master.domain.repository.TestPlanRepository;
import org.bench4q.master.domain.service.ScriptService;
import org.bench4q.master.domain.service.TestResultSave;
import org.bench4q.master.domain.valueobject.transaction.TransactionFactory;
import org.bench4q.master.exception.ExceptionLog;
import org.bench4q.master.exception.ExceptionUtils.IllegalParameterException;
import org.bench4q.share.enums.master.TestPlanStatus;
import org.bench4q.share.helper.MarshalHelper;
import org.bench4q.share.models.agent.ParameterModel;
import org.bench4q.share.models.agent.RunScenarioModel;
import org.bench4q.share.models.agent.ScriptFilterOptionsModel;
import org.bench4q.share.models.agent.scriptrecord.BatchModel;
import org.bench4q.share.models.agent.scriptrecord.BehaviorModel;
import org.bench4q.share.models.agent.scriptrecord.PageModel;
import org.bench4q.share.models.agent.scriptrecord.UsePluginModel;
import org.bench4q.share.models.master.MonitorModel;
import org.bench4q.share.models.master.RunningScriptModel;
import org.bench4q.share.models.master.TestPlanModel;
import org.bench4q.share.models.master.TestScriptConfig;
import org.bench4q.share.models.master.statistics.ScriptResultModel;
import org.bench4q.share.models.monitor.MonitorMain;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class TestPlanFactory {
	private Logger logger = Logger.getLogger(TestPlanFactory.class);
	private ScriptService scriptService;
	private TestResultSave testResultSave;
	private TestPlanRepository testPlanRepository;
	private TransactionFactory transactionFactory;
	private RunningAgentFactory runningAgentFactory;

	private TestResultSave getTestResultSave() {
		return testResultSave;
	}

	@Autowired
	private void setTestResultSave(TestResultSave testResultSave) {
		this.testResultSave = testResultSave;
	}

	private ScriptService getScriptService() {
		return scriptService;
	}

	@Autowired
	private void setScriptService(ScriptService scriptService) {
		this.scriptService = scriptService;
	}

	private TestPlanRepository getTestPlanRepository() {
		return testPlanRepository;
	}

	@Autowired
	private void setTestPlanRepository(TestPlanRepository testPlanRepository) {
		this.testPlanRepository = testPlanRepository;
	}

	private TransactionFactory getTransactionFactory() {
		return transactionFactory;
	}

	@Autowired
	private void setTransactionFactory(TransactionFactory transactionFactory) {
		this.transactionFactory = transactionFactory;
	}

	private RunningAgentFactory getRunningAgentFactory() {
		return runningAgentFactory;
	}

	@Autowired
	private void setRunningAgentFactory(RunningAgentFactory runningAgentFactory) {
		this.runningAgentFactory = runningAgentFactory;
	}

	public TestPlan createATestPlanWithoutId(TestPlanModel testPlanModel,
			User user, UUID runId) throws IllegalParameterException {
		Logger.getLogger(TestPlanFactory.class).info(
				"testPlanName:" + testPlanModel.getName());
		TestPlan result = new TestPlan();
		result.setCreateDateTime(new Date());
		result.setCurrentStatus(TestPlanStatus.NotStart.name());
		result.setFailTimes(0);
		result.setName(testPlanModel.getName());
		result.setSampleCycleInSeconds(getLargerOneBetweenUserOptionAndMinInSystem(testPlanModel));
		result.setLastRunningTime(new Date(0));
		result.setTestPlanRunId(runId.toString());
		result.setUser(user);
		Set<TestPlanScript> testPlanScripts = new HashSet<TestPlanScript>();
		int requiredLoad = 0;
		for (RunningScriptModel runningScriptModel : testPlanModel
				.getRunningScriptModels()) {
			if (runningScriptModel.getRequireLoad() <= 0) {
				throw new IllegalParameterException(
						"runningScriptModel's requireLoad is L.T. zero where scriptId is "
								+ runningScriptModel.getScriptId());
			}
			requiredLoad += runningScriptModel.getRequireLoad();
			testPlanScripts.add(createATestPlanScriptWithoutId(
					runningScriptModel.getRequireLoad(),
					runningScriptModel.getScriptId(),
					runningScriptModel.getConfig(), result,
					runningScriptModel.getScriptFilterOptionsModel()));
		}
		result.setRequiredLoad(requiredLoad);
		result.setTestPlanScripts(testPlanScripts);

		Set<Monitor> monitors = new HashSet<Monitor>();
		for (MonitorModel monitorModel : testPlanModel.getMonitorModels()) {
			Monitor monitorInBusiness = BusinessModelMapFactory
					.toBusiness(monitorModel);
			monitorInBusiness.setTestPlan(result);
			monitors.add(monitorInBusiness);
		}
		result.setMonitors(monitors);
		return result;
	}

	private int getLargerOneBetweenUserOptionAndMinInSystem(
			TestPlanModel testPlanModel) {
		return testPlanModel.getSampleCycleInSeconds() <= Main.Min_Sample_Cycle_InSecond ? Main.Min_Sample_Cycle_InSecond
				: testPlanModel.getSampleCycleInSeconds();
	}

	public TestPlanScript createATestPlanScriptWithoutId(int requireLoad,
			int scriptId, TestScriptConfig config, TestPlan testPlanDB,
			ScriptFilterOptionsModel scriptFilterOptionsModel) {
		TestPlanScript testPlanScript = new TestPlanScript();
		testPlanScript.setRequireLoad(requireLoad);
		// filter the script
		testPlanScript.setScript(createAScriptWithFilter(
				scriptFilterOptionsModel,
				this.getScriptService().getScript(scriptId)));
		testPlanScript.setPlanedConfig(createAPlanedConfigWithoutId(config));
		testPlanScript.setTestPlan(testPlanDB);
		return testPlanScript;
	}

	private Script createAScriptWithFilter(
			ScriptFilterOptionsModel scriptFilterOptionsModel, Script script) {
		RunScenarioModel runScenarioModel = (RunScenarioModel) MarshalHelper
				.tryUnmarshal(RunScenarioModel.class,
						script.getFilteredScriptCnt());
		List<String> filterPluginId = new ArrayList<String>();
		if (scriptFilterOptionsModel.isFilterTimer()) {
			for (UsePluginModel pluginModel : runScenarioModel.getUsePlugins()) {
				if (pluginModel.getName().equals("ConstantTimer")) {
					filterPluginId.add(pluginModel.getId());
				}
			}
		}
		if (scriptFilterOptionsModel.getFilterTypeMatches() != null
				&& !scriptFilterOptionsModel.getFilterTypeMatches().equals("")) {
			for (PageModel pageModel : runScenarioModel.getPages()) {
				for (BatchModel batchModel : pageModel.getBatches()) {
					for (BehaviorModel behaviorModel : batchModel
							.getBehaviors()) {
						if (filterPluginId.contains(behaviorModel.getUse())
								|| isFilterType(
										scriptFilterOptionsModel
												.getFilterTypeMatches(),
										behaviorModel)) {
							batchModel.getBehaviors().remove(behaviorModel);
						}
					}
				}
			}
		}
		String contentString = MarshalHelper.tryMarshal(runScenarioModel);
		script.setFilteredScriptCnt(contentString);
		return script;
	}

	private boolean isFilterType(String filterTypeMatch,
			BehaviorModel behaviorModel) {
		for (ParameterModel parameterModel : behaviorModel.getParameters()) {
			if (parameterModel.getKey().equals("expResCnttype")) {

				if (parameterModel.getValue().matches(filterTypeMatch)) {
					return true;
				} else {
					return false;
				}
			}
		}
		return false;

	}

	private PlanedConfig createAPlanedConfigWithoutId(TestScriptConfig config) {
		PlanedConfig planedConfig = new PlanedConfig();
		planedConfig.setWarmUp(config.getWarmUp());
		planedConfig.setExecuteRange(config.getExecuteRange());
		planedConfig.setCoolDown(config.getCoolDown());
		return planedConfig;
	}

	public TestPlanScriptResult createScriptResultWithoutId(Object model,
			TestPlanScript testPlanScript, Date createDatetime) {
		TestPlanScriptResult result = new TestPlanScriptResult();
		result.setTestPlanScript(testPlanScript);
		try {
			result.setResultContent(MarshalHelper.marshal(model.getClass(),
					model));
			result.setResultType(model.getClass().getName());
		} catch (JAXBException e) {
			logger.error(ExceptionLog.getStackTrace(e));
			result.setResultContent("");
		}
		result.setCreateDatetime(createDatetime);
		return result;
	}

	// new interface
	public List<TestPlanScriptResult> createScriptResultsWithoutId(
			ScriptResultModel scriptResultModel, TestPlanScript testPlanScript,
			Date createDatetime) {

		List<TestPlanScriptResult> testPlanScriptResults = new LinkedList<TestPlanScriptResult>();
		Field[] fields = scriptResultModel.getClass().getDeclaredFields();
		for (int i = 0; i < fields.length; i++) {

			TestPlanScriptResult testPlanScriptResult = new TestPlanScriptResult();
			testPlanScriptResult.setCreateDatetime(createDatetime);
			testPlanScriptResult.setTestPlanScript(testPlanScript);
			testPlanScriptResult.setResultType(fields[i].getType().getName());
			try {

				fields[i].setAccessible(true);
				String content = MarshalHelper.marshal(fields[i].getType(),
						fields[i].get(scriptResultModel));
				testPlanScriptResult.setResultContent(content);
			} catch (Exception e) {
				Logger.getLogger(this.getClass()).info(
						ExceptionLog.getStackTrace(e));
				return null;
			}
			testPlanScriptResults.add(testPlanScriptResult);
		}
		return testPlanScriptResults;
	}

	public List<MonitorResult> createMonitorResultListWithOutId(
			MonitorMain monitorMain, TestPlan testPlan, Monitor monitor,
			Date createDatetime) {
		List<MonitorResult> monitorResults = new LinkedList<MonitorResult>();

		Field[] fields = monitorMain.getClass().getDeclaredFields();
		for (int i = 0; i < fields.length; i++) {
			MonitorResult monitorResult = new MonitorResult();
			monitorResult.setTestPlanDB(testPlan);
			monitorResult.setMonitor(monitor);
			monitorResult.setCreateDatetime(createDatetime);
			monitorResult.setHostNameUnderMonitor(monitor.getHostName());
			fields[i].setAccessible(true);
			monitorResult.setType(fields[i].getType().getName());
			try {
				monitorResult.setContent(MarshalHelper.marshal(
						fields[i].getType(), fields[i].get(monitorMain)));
			} catch (Exception e) {
				Logger.getLogger(this.getClass()).info(
						ExceptionLog.getStackTrace(e));
				return null;
			}
			monitorResults.add(monitorResult);
		}
		return monitorResults;

	}

	public TestPlan convertToDomain(TestPlan testPlanInRepo) {
		if (testPlanInRepo == null) {
			return null;
		}
		testPlanInRepo.setTestResultSave(this.getTestResultSave());
		testPlanInRepo.setRepository(this.getTestPlanRepository());
		for (TestPlanScript testPlanScript : testPlanInRepo
				.getTestPlanScripts()) {
			converToDomain(testPlanScript);
		}
		if (testPlanInRepo.getMonitors() != null) {
			for (Monitor monitor : testPlanInRepo.getMonitors()) {
				convertToDomain(monitor);
			}
		}
		logger.info("convertToDomain testPlan run id:"
				+ testPlanInRepo.getTestPlanRunId());
		return testPlanInRepo;
	}

	public void converToDomain(TestPlanScript testPlanScript) {
		testPlanScript.setTestPlanFactory(this);
		testPlanScript.setTransactionFactory(this.getTransactionFactory());
		for (RunningAgentDB runningAgent : testPlanScript.getRunningAgentsDB()) {
			this.getRunningAgentFactory().convertToDomain(runningAgent);
		}
	}

	private void convertToDomain(Monitor monitor) {
		monitor.setTestPlanFactory(this);
	}
}
