package org.bench4q.agent.datacollector.impl;

import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.bench4q.agent.scenario.engine.BehaviorResult;
import org.bench4q.agent.scenario.engine.PageResult;
import org.bench4q.share.models.agent.statistics.AgentBriefStatusModel;

/**
 * This class collect the behavior result and statistic it.
 * 
 * @author coderfengyun
 * 
 */
public class ScenarioResultCollector extends AbstractDataCollector {
	private long timeOfPreviousCall;
	private long failCountOfThisCall;
	private long successCountOfThisCall;
	private long totalResponseTimeOfThisCall;
	private long maxResponseTimeOfThisCall;
	private long minResponseTimeOfThisCall;
	private long totalSqureResponseTimeOfThisCall;
	private long cumulativeSucessfulCount;
	private long cumulativeFailCount;
	private static long TIME_UNIT = 1000;
	private UUID testID;
	private PageResultCollector pageResultCollector;
	// The first integer is the behavior's id, and the second integer is
	// the StatusCode of this behaviorResult.
	private Map<Integer, Map<Integer, BehaviorStatusCodeResult>> detailMap;

	private void setTimeOfPreviousCall(long timeOfPreviousCall) {
		this.timeOfPreviousCall = timeOfPreviousCall;
	}

	private void setFailCountOfThisCall(long failCountOfThisCall) {
		this.failCountOfThisCall = failCountOfThisCall;
	}

	private long getSuccessCountOfThisCall() {
		return successCountOfThisCall;
	}

	private void setSuccessCountOfThisCall(long successCountOfThisCall) {
		this.successCountOfThisCall = successCountOfThisCall;
	}

	private void setTotalResponseTimeOfThisCall(long totalResponseTimeOfThisCall) {
		this.totalResponseTimeOfThisCall = totalResponseTimeOfThisCall;
	}

	private long getMaxResponseTimeOfThisCall() {
		return maxResponseTimeOfThisCall;
	}

	private void setMaxResponseTimeOfThisCall(long maxResponseTimeOfThisCall) {
		this.maxResponseTimeOfThisCall = maxResponseTimeOfThisCall;
	}

	private long getMinResponseTimeOfThisCall() {
		return minResponseTimeOfThisCall;
	}

	private void setMinResponseTimeOfThisCall(long minResponseTimeOfThisCall) {
		this.minResponseTimeOfThisCall = minResponseTimeOfThisCall;
	}

	private void setTotalSqureResponseTimeOfThisCall(
			long totalSqureResponseTimeOfThisCall) {
		this.totalSqureResponseTimeOfThisCall = totalSqureResponseTimeOfThisCall;
	}

	private void setCumulativeSucessfulCount(long cumulativeSucessfulCount) {
		this.cumulativeSucessfulCount = cumulativeSucessfulCount;
	}

	private void setCumulativeFailCount(long cumulativeFailCount) {
		this.cumulativeFailCount = cumulativeFailCount;
	}

	private String getTestID() {
		return testID == null ? "default" : testID.toString();
	}

	private void setTestID(UUID testID) {
		this.testID = testID;
	}

	private void setDetailMap(
			Map<Integer, Map<Integer, BehaviorStatusCodeResult>> detailMap) {
		this.detailMap = detailMap;
	}

	private PageResultCollector getPageResultCollector() {
		return pageResultCollector;
	}

	private void setPageResultCollector(PageResultCollector pageResultCollector) {
		this.pageResultCollector = pageResultCollector;
	}

	public ScenarioResultCollector(UUID testId) {
		this.setTestID(testId);
		this.setPageResultCollector(new PageResultCollector());
		init();
	}

	private void init() {
		reset();
		this.setCumulativeFailCount(0);
		this.setCumulativeSucessfulCount(0);
		this.setDetailMap(new HashMap<Integer, Map<Integer, BehaviorStatusCodeResult>>());
	}

	private void reset() {
		this.setTimeOfPreviousCall(System.currentTimeMillis());
		this.setFailCountOfThisCall(0);
		this.setMaxResponseTimeOfThisCall(Long.MIN_VALUE);
		this.setMinResponseTimeOfThisCall(Long.MAX_VALUE);
		this.setSuccessCountOfThisCall(0);
		this.setTotalResponseTimeOfThisCall(0);
		this.setTotalSqureResponseTimeOfThisCall(0);
	}

	// ///////////////////////////////
	// DataStatistics Interface start
	// ///////////////////////////////

	public AgentBriefStatusModel getScenarioBriefStatistics() {
		AgentBriefStatusModel result = new AgentBriefStatusModel();
		result.setTimeFrame(System.currentTimeMillis()
				- this.timeOfPreviousCall);
		if (this.getSuccessCountOfThisCall() == 0) {
			result.setMaxResponseTime(0);
			result.setMinResponseTime(0);
		} else {
			result.setMinResponseTime(this.minResponseTimeOfThisCall);
			result.setMaxResponseTime(this.maxResponseTimeOfThisCall);
		}
		this.cumulativeSucessfulCount += this.successCountOfThisCall;
		result.setSuccessCountFromBegin(this.cumulativeSucessfulCount);
		this.cumulativeFailCount += this.failCountOfThisCall;
		result.setFailCountFromBegin(this.cumulativeFailCount);
		if (result.getTimeFrame() == 0) {
			result.setSuccessThroughputThisTime(0);
			result.setFailThroughputThisTime(0);
		} else {
			result.setSuccessThroughputThisTime(this.successCountOfThisCall
					* TIME_UNIT / result.getTimeFrame());
			result.setFailThroughputThisTime(this.failCountOfThisCall
					* TIME_UNIT / result.getTimeFrame());
		}
		result.setTotalResponseTimeThisTime(this.totalResponseTimeOfThisCall);
		result.setSuccessCountThisTime(this.successCountOfThisCall);
		result.setFailCountThisTime(this.failCountOfThisCall);
		result.setTotalSqureResponseTimeThisTime(this.totalSqureResponseTimeOfThisCall);
		reset();
		return result;
	}

	// ///////////////////////////////
	// DataStatistics Interface end
	// ///////////////////////////////
	/**
	 * For the failed one, only the fail count statistics will be added, Others
	 * of this failed one will be ignored.
	 * 
	 * @param behaviorResult
	 */

	private void statisticScenarioBriefResult(BehaviorResult behaviorResult) {
		this.successCountOfThisCall += behaviorResult.getSuccessCount();
		this.failCountOfThisCall += behaviorResult.getFailCount();
		if (behaviorResult.getSuccessCount() == 0)
			return;
		this.totalResponseTimeOfThisCall += behaviorResult.getResponseTime();
		this.totalSqureResponseTimeOfThisCall += Math.pow(
				behaviorResult.getResponseTime(), 2);
		if (behaviorResult.getResponseTime() > this
				.getMaxResponseTimeOfThisCall()) {
			this.setMaxResponseTimeOfThisCall(behaviorResult.getResponseTime());
		}
		if (behaviorResult.getResponseTime() < this
				.getMinResponseTimeOfThisCall()) {
			this.setMinResponseTimeOfThisCall(behaviorResult.getResponseTime());
		}
	}

	private void statisticBehaviorBriefResult(BehaviorResult behaviorResult) {
		insertWhenNotExist(behaviorResult);
		Map<Integer, BehaviorStatusCodeResult> detailStatusMap = this.detailMap
				.get(behaviorResult.getBehaviorId());
		// TODO: there's a problem about concurrency
		guardStatusMapExists(behaviorResult, detailStatusMap);
		BehaviorStatusCodeResult statusCodeResult = detailStatusMap
				.get(behaviorResult.getStatusCode());
		statusCodeResult.count++;
		if (behaviorResult.getSuccessCount() == 0) {
			statusCodeResult.contentLength += 0;
			statusCodeResult.totalResponseTimeThisTime = 0;
			return;
		}
		statusCodeResult.contentLength += behaviorResult.getContentLength();
		statusCodeResult.totalResponseTimeThisTime += behaviorResult
				.getResponseTime();
		if (behaviorResult.getResponseTime() > statusCodeResult.maxResponseTime) {
			statusCodeResult.maxResponseTime = behaviorResult.getResponseTime();
		}
		if (behaviorResult.getResponseTime() < statusCodeResult.minResponseTime) {
			statusCodeResult.minResponseTime = behaviorResult.getResponseTime();
		}
	}

	private synchronized void guardStatusMapExists(
			BehaviorResult behaviorResult,
			Map<Integer, BehaviorStatusCodeResult> detailStatusMap) {
		if (!detailStatusMap.containsKey(behaviorResult.getStatusCode())) {
			detailStatusMap.put(
					new Integer(behaviorResult.getStatusCode()),
					new BehaviorStatusCodeResult(behaviorResult
							.getContentType()));
		}
	}

	private synchronized void insertWhenNotExist(BehaviorResult behaviorResult) {
		if (!this.detailMap.containsKey(behaviorResult.getBehaviorId())) {
			this.detailMap.put(new Integer(behaviorResult.getBehaviorId()),
					new HashMap<Integer, BehaviorStatusCodeResult>());
		}
	}

	@Override
	protected String calculateSavePath(BehaviorResult behaviorResult) {
		Date now = new Date();

		return "DetailResults" + System.getProperty("file.separator")
				+ new SimpleDateFormat("yyyyMMdd").format(now)
				+ System.getProperty("file.separator") + this.getTestID() + "_"
				+ behaviorResult.getBehaviorId() + "_"
				+ new SimpleDateFormat("hhmm") + ".txt";
	}

	public synchronized void add(PageResult pageResult) {
		this.getPageResultCollector().add(pageResult);
	}

	@Override
	public synchronized void add(BehaviorResult behaviorResult) {
		super.add(behaviorResult);
		statisticScenarioBriefResult(behaviorResult);
		statisticBehaviorBriefResult(behaviorResult);
	}

	@Override
	public Map<Integer, BehaviorStatusCodeResult> getBehaviorBriefStatistics(
			int behaviorId) {
		if (!this.detailMap.containsKey(behaviorId)) {
			return null;
		}
		return Collections.unmodifiableMap(this.detailMap.get(behaviorId));
	}

	public Object getPageBriefStatistics(int pageId) {
		return this.getPageResultCollector().getPageBriefStatistics(pageId);
	}

}
