package org.bench4q.master.infrastructure.communication.impl;

import java.io.File;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.log4j.Logger;
import org.bench4q.master.domain.entity.Agent;
import org.bench4q.master.infrastructure.communication.AgentMessenger;
import org.bench4q.master.infrastructure.faultolerence.FaultTolerenceFactory;
import org.bench4q.share.communication.HttpRequester;
import org.bench4q.share.communication.HttpRequester.HttpResponse;
import org.bench4q.share.helper.MarshalHelper;
import org.bench4q.share.models.agent.RunScenarioModel;
import org.bench4q.share.models.agent.RunScenarioResultModel;
import org.bench4q.share.models.agent.ServerStatusModel;
import org.bench4q.share.models.agent.StopTestModel;
import org.bench4q.share.models.agent.TestBriefStatusModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class AgentMessengerImpl implements AgentMessenger {
	private HttpRequester httpRequester;
	private ExecutorService executorService = Executors.newCachedThreadPool();
	private Logger logger = Logger.getLogger(AgentMessengerImpl.class);

	private HttpRequester getHttpRequester() {
		return httpRequester;
	}

	@Autowired
	private void setHttpRequester(HttpRequester httpRequester) {
		this.httpRequester = httpRequester;
	}

	private void logIt(HttpResponse httpResponse, Throwable e) {
		logger.error(HttpRequester.isInvalidResponse(httpResponse) ? ""
				: httpResponse.getContent(), e);
	}

	public RunScenarioResultModel bookTest(Agent agent, int requireLoad) {
		HttpResponse httpResponse = null;
		try {
			httpResponse = this.getHttpRequester().sendGet(
					buildBaseUrl(agent) + "/test/bookTest/" + requireLoad,
					null, null);
			System.out.println(buildBaseUrl(agent) + "/test/bookTest/"
					+ requireLoad);
			return (RunScenarioResultModel) MarshalHelper.unmarshal(
					RunScenarioResultModel.class, httpResponse.getContent());
		} catch (Exception e) {
			logger.error(httpResponse.getContent(), e);
			return null;
		}
	}

	public Future<RunScenarioResultModel> bookTestAsync(final Agent agent,
			final int requiredLoad) {
		return this.executorService
				.submit(new Callable<RunScenarioResultModel>() {
					@Override
					public RunScenarioResultModel call() throws Exception {
						return bookTest(agent, requiredLoad);
					}
				});
	}

	public RunScenarioResultModel submitScenrioWithParams(Agent agent,
			UUID agentRunId, List<File> paramFiles,
			final RunScenarioModel runScenarioModel, Date realStartDate) {
		HttpResponse httpResponse = null;
		try {
			final String modelContent = MarshalHelper
					.tryMarshal(runScenarioModel);
			final Map<String, String> stringParts = new LinkedHashMap<String, String>();
			stringParts.put("scenarioModel", modelContent);
			stringParts.put("realStartTime",
					String.valueOf(realStartDate.getTime()));
			httpResponse = this.httpRequester.postFiles(null,
					buildBaseUrl(agent) + "/test/submitScenarioWithParams/"
							+ agentRunId + "/" + realStartDate.getTime(),
					"files[]", paramFiles, stringParts);
			return (RunScenarioResultModel) MarshalHelper.unmarshal(
					RunScenarioResultModel.class, httpResponse.getContent());
		} catch (Exception e) {
			logIt(httpResponse, e);
			return null;
		}
	}

	public Future<RunScenarioResultModel> submitScenarioWithParamsAsync(
			final Agent agent, final UUID agentRunId,
			final List<File> paramFiles,
			final RunScenarioModel runScenarioModel, final Date realStartDate) {
		return this.executorService
				.submit(new Callable<RunScenarioResultModel>() {
					@Override
					public RunScenarioResultModel call() throws Exception {
						return submitScenrioWithParams(agent, agentRunId,
								paramFiles, runScenarioModel, realStartDate);
					}
				});
	}

	public RunScenarioResultModel runWithParams(Agent agent, UUID agentRunId) {
		HttpResponse httpResponse = null;
		try {
			if (agent == null || agentRunId == null) {
				return null;
			}
			httpResponse = this.httpRequester.sendPost(buildBaseUrl(agent)
					+ "/test/runWithParams/" + agentRunId.toString(), null,
					null);
			if (HttpRequester.isInvalidResponse(httpResponse)) {
				logger.error("the response is not valid");
				return null;
			}
			return (RunScenarioResultModel) MarshalHelper.unmarshal(
					RunScenarioResultModel.class, httpResponse.getContent());
		} catch (Exception e) {
			logIt(httpResponse, e);
			return null;
		}
	}

	public Future<RunScenarioResultModel> runAsync(final Agent agent,
			final UUID agentRunId) {
		return this.executorService
				.submit(new Callable<RunScenarioResultModel>() {
					@Override
					public RunScenarioResultModel call() throws Exception {
						return runWithParams(agent, agentRunId);
					}
				});
	}

	// there is bug in here
	public TestBriefStatusModel scriptBriefAll(Agent agent, UUID runId) {
		HttpResponse httpResponse = null;
		try {
			if (agent == null || runId == null) {
				return null;
			}
			httpResponse = this.httpRequester.sendGet(buildBaseUrl(agent)
					+ "/test/briefAll/" + runId, null, null);
			if (HttpRequester.isInvalidResponse(httpResponse)) {
				return null;
			}
			logger.info(httpResponse.getContent());
			return (TestBriefStatusModel) MarshalHelper.unmarshal(
					TestBriefStatusModel.class, httpResponse.getContent());
		} catch (Exception e) {
			logIt(httpResponse, e);
			logger.error(e.toString()
					+ " When get script all brief the agent with hostName "
					+ agent.getHostName());
			FaultTolerenceFactory.getBriefFaultTolerance(agent).doTolerance();
			return null;
		}
	}

	public Future<TestBriefStatusModel> scriptBriefAsync(final Agent agent,
			final UUID runId) {
		return this.executorService
				.submit(new Callable<TestBriefStatusModel>() {
					@Override
					public TestBriefStatusModel call() throws Exception {
						return scriptBriefAll(agent, runId);
					}
				});
	}

	public StopTestModel stop(Agent agent, UUID runId) {
		HttpResponse httpResponse;
		try {
			if (agent == null || runId == null) {
				return null;
			}
			httpResponse = this.httpRequester.sendGet(buildBaseUrl(agent)
					+ "/test/stop/" + runId.toString(), null, null);
			if (HttpRequester.isInvalidResponse(httpResponse)) {
				return null;
			}
			return (StopTestModel) MarshalHelper.unmarshal(StopTestModel.class,
					httpResponse.getContent());
		} catch (Exception e) {
			logger.error(e.toString() + " when stop the agent with hostName "
					+ agent.getHostName());
			FaultTolerenceFactory.getStopAgentFault(agent, runId).doTolerance();
			return null;
		}
	}

	public Future<StopTestModel> stopAsync(final Agent agent, final UUID runId) {
		return this.executorService.submit(new Callable<StopTestModel>() {

			@Override
			public StopTestModel call() throws Exception {
				return stop(agent, runId);
			}
		});
	}

	private String buildBaseUrl(Agent agent) {
		return agent.getHostName() + ":" + agent.getPort();
	}

	public ServerStatusModel getStatus(Agent agent) {
		try {
			HttpResponse httpResponse = this.getHttpRequester().sendGet(
					agent.getHostName() + ":" + agent.getPort() + "/", null,
					null);
			if (HttpRequester.isInvalidResponse(httpResponse)) {
				return null;
			}
			return (ServerStatusModel) MarshalHelper.unmarshal(
					ServerStatusModel.class, httpResponse.getContent());
		} catch (Exception e) {
			logger.error("agent :" + agent.getHostName() + " break down!");
			return null;
		}
	}

	public Future<ServerStatusModel> getStatusAsync(final Agent agent) {
		return this.executorService.submit(new Callable<ServerStatusModel>() {
			@Override
			public ServerStatusModel call() throws Exception {
				return getStatus(agent);
			}
		});
	}

}
