package org.bench4q.agent.scenario.engine;

import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.log4j.Logger;
import org.bench4q.agent.datacollector.DataCollector;
import org.bench4q.agent.plugin.ParameterBarn;
import org.bench4q.agent.plugin.Plugin;
import org.bench4q.agent.plugin.PluginManager;
import org.bench4q.agent.plugin.basic.PluginReturn;
import org.bench4q.agent.plugin.basic.http.HttpReturn;
import org.bench4q.agent.scenario.Batch;
import org.bench4q.agent.scenario.Page;
import org.bench4q.agent.scenario.Parameter;
import org.bench4q.agent.scenario.Scenario;
import org.bench4q.agent.scenario.UsePlugin;
import org.bench4q.agent.scenario.behavior.Behavior;
import org.bench4q.agent.scenario.dfa.ParamPart;

public class VUser implements Runnable {
	private ScenarioContext scenarioContext;
	private int currentIterationId;

	private PluginManager pluginManager;

	private int getCurrentIterationId() {
		return currentIterationId;
	}

	private void setCurrentIterationId(int currentIterationId) {
		this.currentIterationId = currentIterationId;
	}

	private ScenarioContext getScenarioContext() {
		return scenarioContext;
	}

	private void setScenarioContext(ScenarioContext scenarioContext) {
		this.scenarioContext = scenarioContext;
	}

	private PluginManager getPluginManager() {
		return pluginManager;
	}

	private void setPluginManager(PluginManager pluginManager) {
		this.pluginManager = pluginManager;
	}

	public VUser(ScenarioContext scenarioContext, int currentIterationId,
			PluginManager pluginManager) {
		this.setScenarioContext(scenarioContext);
		this.setPluginManager(pluginManager);
		this.setCurrentIterationId(currentIterationId);
	}

	public void run() {
		doRunScenario(getScenarioContext());
		doCleanUp();
	}

	private void doCleanUp() {
		this.getScenarioContext().addATask();
		this.setScenarioContext(null);
	}

	private void doRunScenario(ScenarioContext context) {
		Map<String, Object> plugins = new LinkedHashMap<String, Object>();
		preparePlugins(context.getScenario(), plugins);
		for (int i = 0; i < context.getScenario().getPages().length; i++) {
			Page page = context.getScenario().getPages()[i];
			long startTime = System.currentTimeMillis();
			for (Batch batch : page.getBatches()) {
				doRunBatch(plugins, batch, context.getDataStatistics());
			}
			long endTime = System.currentTimeMillis();
			context.getDataStatistics().add(
					PageResult.buildPageResult(i, startTime, endTime));
		}
	}

	private void doRunBatch(Map<String, Object> plugins, Batch batch,
			DataCollector dataCollector) {
		for (Behavior behavior : batch.getBehaviors()) {
			Object plugin = plugins.get(behavior.getUse());
			Date startDate = new Date(System.currentTimeMillis());
			PluginReturn pluginReturn = (PluginReturn) this.getPluginManager()
					.doBehavior(plugin, behavior.getName(),
							reassamblyParameters(behavior, plugins));
			Date endDate = new Date(System.currentTimeMillis());
			if (!behavior.shouldBeCount()) {
				continue;
			}
			dataCollector.add(buildBehaviorResult(behavior, plugin, startDate,
					pluginReturn, endDate));
		}
	}

	private Map<String, String> reassamblyParameters(Behavior behavior,
			Map<String, Object> plugins) {
		Map<String, String> behaviorParameters = new HashMap<String, String>();
		for (Parameter parameter : behavior.getParameters()) {
			behaviorParameters.put(parameter.getKey(),
					reassamblyParameter(parameter.getParamParts(), plugins));
		}
		return behaviorParameters;
	}

	private String reassamblyParameter(ParamPart[] parts,
			Map<String, Object> plugins) {
		StringBuilder buf = new StringBuilder();
		for (int i = 0, len = parts.length; i < len; i++) {
			switch (parts[i].getType()) {
			case STRING:
				buf.append(parts[i].getContentForStringType());
				break;
			case SESSION_ID:
				buf.append(this.getCurrentIterationId());
				break;
			case CONTEXT_CALL:
				try {
					Object dpObj = plugins.get(parts[i]
							.getPluginIdForContextCallType());
					if (dpObj == null) {
						throw new RuntimeException("No such plug-in id: "
								+ parts[i].getPluginIdForContextCallType());
					} else if (dpObj instanceof ParameterBarn) {
						buf.append(((ParameterBarn) dpObj).getValue(parts[i]
								.getVariableForContextCallAndProperty()));
					} else {
						throw new RuntimeException("Plug-in "
								+ parts[i].getPluginIdForContextCallType()
								+ " is not a data provider.");
					}
				} catch (Exception e) {
					Logger.getLogger(VUser.class)
							.error(parts[i].getPluginIdForContextCallType()
									+ "variable "
									+ parts[i]
											.getVariableForContextCallAndProperty(),
									e);
				}
				break;
			case PROPERTY:
				buf.append(System.getProperty(
						parts[i].getVariableForContextCallAndProperty(), ""));
				break;
			}
		}
		return buf.toString();
	}

	private BehaviorResult buildBehaviorResult(Behavior behavior,
			Object plugin, Date startDate, PluginReturn pluginReturn,
			Date endDate) {
		BehaviorResult result = new BehaviorResult();
		result.setBehaviorId(behavior.getId());
		result.setStartDate(startDate);
		result.setEndDate(endDate);
		result.setSuccessCount(pluginReturn.getSuccessCount());
		result.setFailCount(pluginReturn.getFailCount());
		result.setResponseTime(endDate.getTime() - startDate.getTime());
		result.setBehaviorName(behavior.getName());
		result.setPluginId(behavior.getUse());
		result.setPluginName(plugin.getClass().getAnnotation(Plugin.class)
				.value());
		result.setShouldBeCountResponseTime(behavior.shouldBeCount());
		if (pluginReturn instanceof HttpReturn) {
			HttpReturn httpReturn = (HttpReturn) pluginReturn;
			// TODO: this param in result is not Appropriate
			result.setBehaviorUrl(behavior.getSpecificParamValue("url"));
			result.setContentLength(httpReturn.getContentLength());
			result.setContentType(httpReturn.getContentType());
			result.setStatusCode(httpReturn.getStatusCode());
		}
		return result;
	}

	private void preparePlugins(Scenario scenario, Map<String, Object> plugins) {
		for (UsePlugin usePlugin : scenario.getUsePlugins()) {
			String pluginId = usePlugin.getId();
			Class<?> pluginClass = this.getPluginManager().getPlugins()
					.get(usePlugin.getName());
			Map<String, String> initParameters = new HashMap<String, String>();
			for (Parameter parameter : usePlugin.getParameters()) {
				initParameters.put(parameter.getKey(), parameter.getValue());
			}
			Map<String, String> extraParameters = new HashMap<String, String>();
			extraParameters.put("testId", this.getScenarioContext().getTestId()
					.toString());
			Object plugin = this.getPluginManager().initializePlugin(
					pluginClass, initParameters, extraParameters);
			plugins.put(pluginId, plugin);
		}
	}

}
