package org.bench4q.recorder.httpcapture.generator;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.bench4q.recorder.httpcapture.Action;
import org.bench4q.recorder.httpcapture.Config;
import org.bench4q.recorder.httpcapture.HeaderValue;
import org.bench4q.recorder.httpcapture.IScriptAdapter;
import org.bench4q.recorder.httpcapture.Param;
import org.bench4q.recorder.httpcapture.Utils;
import org.bench4q.share.models.agent.ParameterModel;
import org.bench4q.share.models.agent.scriptrecord.BehaviorModel;
import org.bench4q.share.models.agent.scriptrecord.UsePluginModel;

public class Bench4qCodeGenerator extends AbstractCodeGenerator {
	static final String TIMER_PROP = "generator.isac.timer";
	static final String NULL_TIMER = "null";
	static final String CONSTANT_TIMER = "ConstantTimer";
	static final String RANDOM_TIMER = "Random";
	static final String DEFAULT_TIMER = "ConstantTimer";
	static final String RANDOM_TIMER_DIST_PROP = "generator.isac.timer.random.dist";
	static final String RANDOM_TIMER_DIST_UNIFORM = "uniform";
	static final String RANDOM_TIMER_DIST_GAUSSIAN = "gaussian";
	static final String RANDOM_TIMER_DIST_POISSON = "poisson";
	static final String RANDOM_TIMER_DIST_NEGEXPO = "negexpo";
	static final String RANDOM_TIMER_DIST_DEFAULT = "uniform";
	static final String RANDOM_TIMER_MIN_PROP = "generator.isac.timer.random.min";
	static final String RANDOM_TIMER_DELTA_PROP = "generator.isac.timer.random.delta";
	static final String RANDOM_TIMER_UNIT_PROP = "generator.isac.timer.random.unit";
	static final String RANDOM_TIMER_DEVIATION_PROP = "generator.isac.timer.random.deviation";
	static final String[][] escapes = { { "\\", "\\\\" }, { ";", "\\;" },
			{ "&", "&amp;" }, { "\"", "&quot;" }, { "<", "&lt;" },
			{ ">", "&gt;" } };
	private String queryStringParameters = "";
	private String bodyParameters = "";
	private String body = "";
	private String headers = "";
	private String timer;
	private String randomDist;
	private int delta = 0;
	private int deviation = 0;
	private int unit = 1;
	private static Logger logger = Logger.getLogger(Bench4qCodeGenerator.class);

	private static String escapeXmlString(String str) {
		for (int i = 0; i < escapes.length; ++i)
			str = str.replace(escapes[i][0], escapes[i][1]);

		return str;
	}

	private static String encodedParameterList(Param[] params) {
		String result = "";
		for (int i = 0; i < params.length; ++i) {
			if (i > 0)
				result = result + ";";

			result = result + escapeXmlString(params[i].name) + "="
					+ escapeXmlString(params[i].value);
		}
		return result;
	}

	public Bench4qCodeGenerator(IScriptAdapter adapter) {
		super(adapter, new String[0]);
	}

	public static String getGeneratorDescription() {
		return "Bench4Q Scenario";
	}

	public void doNew() {
		List<UsePluginModel> usePlugins = new ArrayList<UsePluginModel>();
		UsePluginModel usePlugin1 = new UsePluginModel();
		UsePluginModel usePlugin2 = new UsePluginModel();

		usePlugin1.setParameters(new ArrayList<ParameterModel>());
		usePlugin1.setId("http");
		usePlugin1.setName("Http");
		usePlugin1.setNickName("Http_http");
		
		usePlugin2.setId("timer");
		usePlugin2.setParameters(new ArrayList<ParameterModel>());

		Config conf = Config.getConfig();
		this.timer = conf.getProperty("generator.isac.timer", "ConstantTimer");

		if (this.timer.equals("ConstantTimer")) {
			usePlugin2.setName("ConstantTimer");
		} else if (this.timer.equals("Random")) {
			usePlugin2.setName("Random");
			this.randomDist = Config.getConfig().getProperty(
					"generator.isac.timer.random.dist", "uniform");
			try {
				if (this.randomDist.equals("uniform")) {
					this.delta = conf.getPropertyInt(
							"generator.isac.timer.random.delta").intValue();
				} else if (this.randomDist.equals("gaussian")) {
					this.delta = conf.getPropertyInt(
							"generator.isac.timer.random.delta").intValue();
					this.deviation = conf.getPropertyInt(
							"generator.isac.timer.random.deviation").intValue();
				} else if (this.randomDist.equals("poisson")) {
					this.unit = conf.getPropertyInt(
							"generator.isac.timer.random.unit").intValue();
				} else if (!(this.randomDist.equals("negexpo"))) {

				} else {
					this.delta = conf.getPropertyInt(
							"generator.isac.timer.random.delta").intValue();
				}

			} catch (Utils.UserException ex) {
				ex.printStackTrace(System.err);
				System.err
						.println("Warning: incorrect property settings for Isac random timer distribution "
								+ this.randomDist);
			}
		}
		usePlugin2.setNickName(usePlugin2.getName()+"_"+usePlugin2.getId());
		usePlugins.add(usePlugin1);
		usePlugins.add(usePlugin2);

		appendUserPluginsToScenario(usePlugins);
	}

	public String[] getValidFileExtensions() {
		return new String[] { ".xis" };
	}

	public void doParameterList(Param[] params) throws Utils.UserException {
	}

	public void doQueryStringParameterList(Param[] params)
			throws Utils.UserException {
		this.queryStringParameters = encodedParameterList(params);
	}

	public void doBodyParameterList(Param[] params) throws Utils.UserException {
		this.bodyParameters = encodedParameterList(params);
	}

	public void doTestUrlMessage(String url) {
	}

	public void doSetData(String data) {
		if (data == null) {
			return;
		}
		body = data;
	}

	public void doCallUrl(Action action, String method, String data,
			String contentLength, int respCode, String respContentType) throws Utils.UserException {
		if (!isFirstRequest())
			doInsertDelay(getTimeElapsedSinceLastestRequest());
		BehaviorModel userBehavior = createHttpBehavior(this.getRequestCount(),
				action.getUrl(), method, respCode, respContentType);
		insertBehavior(userBehavior);

		this.queryStringParameters = "";
		this.bodyParameters = "";
	}

	public BehaviorModel createHttpBehavior(int behaviorId, String url,
			String method, int respCode, String respContentType) {
		List<ParameterModel> params = new ArrayList<ParameterModel>();
		params.add(ParameterModel.createParameter("url", url));
		params.add(ParameterModel.createParameter("queryParams",
				this.queryStringParameters));
		params.add(ParameterModel.createParameter("headers", this.headers));
		params.add(ParameterModel.createParameter("expResCode", Integer.toString(respCode)));
		params.add(ParameterModel.createParameter("expResCnttype", respContentType));
		if (method.equalsIgnoreCase("post")) {
			if (body.length() > 0) {
				params.add(ParameterModel.createParameter("bodyContent",
						this.body));
			} else {
				params.add(ParameterModel.createParameter("bodyparameters",
						this.bodyParameters));
			}
		}
		this.queryStringParameters = "";
		this.bodyParameters = "";
		this.headers = "";
		this.body = "";
		return BehaviorModel.UserBehaviorBuilder(behaviorId, method, "http",
				params);
	}

	public void doAssertResponse(int respCode) {
		// TODO:Here I can tell if there are some incorrect request when
		// record the script
	}

	public void doTidyCode(String url) {
	}

	@Override
	public String doParseHtmlContent(String responseBody, String rootUrl) {
		int htmlStart = responseBody.indexOf("<html");
		int bodyStart = responseBody.indexOf("<body");
		if (htmlStart == -1 || bodyStart == -1) {
			return responseBody;
		}
		int htmlEnd = responseBody.indexOf("</html>");
		int bodyEnd = responseBody.indexOf("</body>");
		if(bodyEnd == -1){
			responseBody += "</body>";
		
		}
		if(htmlEnd == -1){
			responseBody += "</html>";
			htmlEnd = responseBody.indexOf("</html>");
		}
		return parseDocument(rootUrl, responseBody, htmlStart, htmlEnd);
	}

	private String parseDocument(String rootUrl, String responseContent,
			int htmlStart, int htmlEnd) {
		// TODO:reset the childrenUrls in scirptAdapter
		String htmlContent = responseContent.substring(htmlStart, htmlEnd + 6);
		saveToFile(htmlContent);
		HtmlDocumentParser documentParser = new HtmlDocumentParser(htmlContent,
				rootUrl);
		List<ChildrenUrl> childrenUrls = documentParser
				.buildParentChildrenRelationship(this.getScriptAdapter()
						.getParentBatchIdWithParentUrl(rootUrl));
		this.getScriptAdapter().getChildrenUrls().addAll(childrenUrls);
		//add record flag to document
		documentParser.appendRecordFlag();
		return documentParser.toString();
	}

	private void saveToFile(String htmlContent) {
		// TODO: For Test
		try {
			File storeFile = new File("RecordScriptTestCase/baidu.html");
			FileUtils.writeStringToFile(storeFile, htmlContent);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void doResponseForStdOut(String url) {
	}

	public void doResponseForFile() {
	}

	public void doEndTransaction() throws Utils.UserException {
	}

	public void doStartRecording() {
	}

	public void doStopRecording() {
	}

	public void doClose() {
	}

	public void setStruts(boolean struts) {
	}

	public void doHeaders(HeaderValue[] headers) {
		StringBuilder headersSB = new StringBuilder();
		for (int i = 0; i < headers.length; ++i) {
			headersSB.append("header=");
			headersSB.append(headers[i].getHeader());
			headersSB.append("|value=");
			headersSB.append(headers[i].getValue());
			headersSB.append("|;");
		}
		this.headers = headersSB.toString();
	}

	private void appendUserPluginsToScenario(List<UsePluginModel> usePlugins) {
		this.getScriptAdapter().appendUsePluginsToScenario(usePlugins);
	}

	private void insertBehavior(BehaviorModel behaviorModel)
			throws Utils.UserException {
		insert(behaviorModel);
	}

	private void doInsertDelay(long delay) throws Utils.UserException {
		BehaviorModel timerBehavior = createTimerBehavior(delay);
		try {
			insertBehavior(timerBehavior);
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("exception occured when doInsertDelay" + e.toString());
		}

	}

	private BehaviorModel createTimerBehavior(long delay) {
		List<ParameterModel> parameterModels = new ArrayList<ParameterModel>();
		ParameterModel parameterModel = new ParameterModel();
		parameterModel.setKey("time");
		parameterModel.setValue(String.valueOf(delay));
		parameterModels.add(parameterModel);
		return BehaviorModel.TimerBehaviorBuilder(0, "Sleep", "timer",
				parameterModels);
	}

	@SuppressWarnings("unused")
	private StringBuilder createTimer(long delay) {
		StringBuilder timerStr = new StringBuilder();

		if (this.timer.equals("Random")) {
			int min = (int) (delay - this.delta);
			if (min < 0)
				min = 0;

			if (this.randomDist.equals("uniform")) {
				timerStr.append("\t\t\t<control use=\"replayTimer\" name=\"setUniform\">"
						+ EOL);
				timerStr.append("\t\t\t\t<params>" + EOL);
				timerStr.append("\t\t\t\t\t<param name=\"min\" value=\"" + min
						+ "\"/>" + EOL);
				timerStr.append("\t\t\t\t\t<param name=\"max\" value=\""
						+ (delay + this.delta) + "\"/>" + EOL);
				timerStr.append("\t\t\t\t</params>" + EOL);
				timerStr.append("\t\t\t</control>" + EOL);
			} else if (this.randomDist.equals("gaussian")) {
				timerStr.append("\t\t\t<control use=\"replayTimer\" name=\"setGaussian\">"
						+ EOL);
				timerStr.append("\t\t\t\t<params>" + EOL);
				timerStr.append("\t\t\t\t\t<param name=\"min\" value=\"" + min
						+ "\"/>" + EOL);
				timerStr.append("\t\t\t\t\t<param name=\"max\" value=\""
						+ (delay + this.delta) + "\"/>" + EOL);
				timerStr.append("\t\t\t\t\t<param name=\"mean\" value=\""
						+ delay + "\"/>" + EOL);
				timerStr.append("\t\t\t\t\t<param name=\"deviation\" value=\""
						+ this.deviation + "\"/>" + EOL);
				timerStr.append("\t\t\t\t</params>" + EOL);
				timerStr.append("\t\t\t</control>" + EOL);
			} else if (this.randomDist.equals("poisson")) {
				timerStr.append("\t\t\t<control use=\"replayTimer\" name=\"setPoisson\">"
						+ EOL);
				timerStr.append("\t\t\t\t<params>" + EOL);
				timerStr.append("\t\t\t\t\t<param name=\"unit\" value=\""
						+ this.unit + "\"/>" + EOL);
				timerStr.append("\t\t\t\t\t<param name=\"parameter\" value=\""
						+ delay + "\"/>" + EOL);
				timerStr.append("\t\t\t\t</params>" + EOL);
				timerStr.append("\t\t\t</control>" + EOL);
			} else if (this.randomDist.equals("negexpo")) {
				timerStr.append("\t\t\t<control use=\"replayTimer\" name=\"setNegativeExpo\">"
						+ EOL);
				timerStr.append("\t\t\t\t<params>" + EOL);
				timerStr.append("\t\t\t\t\t<param name=\"min\" value=\"" + min
						+ "\"/>" + EOL);
				timerStr.append("\t\t\t\t\t<param name=\"mean\" value=\""
						+ delay + "\"/>" + EOL);
				timerStr.append("\t\t\t\t</params>" + EOL);
				timerStr.append("\t\t\t</control>" + EOL);
			}
			timerStr.append("\t\t\t<timer use=\"replayTimer\" name=\"sleep\"/>"
					+ EOL);
		} else if (this.timer.equals("ConstantTimer")) {
			timerStr.append("\t\t\t<timer use=\"replayTimer\" name=\"sleep\">"
					+ EOL);
			timerStr.append("\t\t\t\t<params>" + EOL);
			timerStr.append("\t\t\t\t\t<param name=\"duration_arg\" value=\""
					+ delay + "\"></param>" + EOL);
			timerStr.append("\t\t\t\t</params>" + EOL);
			timerStr.append("\t\t\t</timer>" + EOL);
		}
		return timerStr;
	}

	public void doSetCharset(String cs) throws Utils.UserException {
	}
}
