/*
 * Encog(tm) Core v3.2 - Java Version
 * http://www.heatonresearch.com/encog/
 * https://github.com/encog/encog-java-core
 
 * Copyright 2008-2013 Heaton Research, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *   
 * For more information on Heaton Research copyrights, licenses 
 * and trademarks visit:
 * http://www.heatonresearch.com/copyright
 */
package org.encog.app.generate.generators.mql4;

import java.io.File;

import org.encog.app.analyst.EncogAnalyst;
import org.encog.app.analyst.script.DataField;
import org.encog.app.analyst.script.normalize.AnalystField;
import org.encog.app.analyst.script.prop.ScriptProperties;
import org.encog.app.generate.AnalystCodeGenerationError;
import org.encog.app.generate.generators.AbstractTemplateGenerator;
import org.encog.ml.MLMethod;
import org.encog.neural.flat.FlatNetwork;
import org.encog.neural.networks.BasicNetwork;
import org.encog.persist.EncogDirectoryPersistence;
import org.encog.util.EngineArray;

public class GenerateMQL4 extends AbstractTemplateGenerator {

	@Override
	public String getNullArray() {
		return "{-1}";
	}

	@Override
	public String getTemplatePath() {
		return "org/encog/data/mt4.mql4";
	}

	private void processCalc() {
		AnalystField firstOutputField = null;
		final int barsNeeded = Math.abs(getAnalyst().determineMinTimeSlice());

		final int inputCount = getAnalyst().determineInputCount();
		final int outputCount = getAnalyst().determineOutputCount();

		setIndentLevel(2);
		addLine("if( _inputCount>0 && Bars>=" + barsNeeded + " )");
		addLine("{");
		indentIn();
		addLine("double input[" + inputCount + "];");
		addLine("double output[" + outputCount + "];");

		int idx = 0;
		for (final AnalystField field : getAnalyst().getScript().getNormalize()
				.getNormalizedFields()) {
			if (field.isInput()) {

				final DataField df = getAnalyst().getScript().findDataField(
						field.getName());
				String str;

				switch (field.getAction()) {
				case PassThrough:
					str = EngineArray.replace(df.getSource(), "##", "pos+"
							+ (-field.getTimeSlice()));
					addLine("input[" + idx + "]=" + str + ";");
					idx++;
					break;
				case Normalize:
					str = EngineArray.replace(df.getSource(), "##", "pos+"
							+ (-field.getTimeSlice()));
					addLine("input[" + idx + "]=Norm(" + str + ","
							+ field.getNormalizedHigh() + ","
							+ field.getNormalizedLow() + ","
							+ field.getActualHigh() + ","
							+ field.getActualLow() + ");");
					idx++;
					break;
				case Ignore:
					break;
				default:
					throw new AnalystCodeGenerationError(
							"Can't generate Ninjascript code, unsupported normalizatoin action: "
									+ field.getAction().toString());
				}
			}
			if (field.isOutput()) {
				if (firstOutputField == null) {
					firstOutputField = field;
				}
			}
		}

		if (firstOutputField == null) {
			throw new AnalystCodeGenerationError(
					"Could not find an output field.");
		}

		addLine("Compute(input,output);");

		addLine("ExtMapBuffer1[pos] = DeNorm(output[0]" + ","
				+ firstOutputField.getNormalizedHigh() + ","
				+ firstOutputField.getNormalizedLow() + ","
				+ firstOutputField.getActualHigh() + ","
				+ firstOutputField.getActualLow() + ");");
		indentOut();
		addLine("}");
		setIndentLevel(2);

	}

	private void processHeaders() {
		final DataField[] fields = getAnalyst().getScript().getFields();

		final StringBuilder line = new StringBuilder();
		line.append("FileWrite(iHandle");

		for (final DataField df : fields) {
			line.append(",");
			line.append("\"");
			line.append(df.getName());
			line.append("\"");
		}

		line.append(");");
		addLine(line.toString());
	}

	private void processMainBlock() {
		final EncogAnalyst analyst = getAnalyst();

		final String processID = analyst.getScript().getProperties()
				.getPropertyString(ScriptProperties.PROCESS_CONFIG_SOURCE_FILE);

		final String methodID = analyst
				.getScript()
				.getProperties()
				.getPropertyString(
						ScriptProperties.ML_CONFIG_MACHINE_LEARNING_FILE);

		final File methodFile = analyst.getScript().resolveFilename(methodID);

		final File processFile = analyst.getScript().resolveFilename(processID);

		MLMethod method = null;
		int[] contextTargetOffset = null;
		int[] contextTargetSize = null;
		boolean hasContext = false;
		int inputCount = 0;
		int[] layerContextCount = null;
		int[] layerCounts = null;
		int[] layerFeedCounts = null;
		int[] layerIndex = null;
		double[] layerOutput = null;
		double[] layerSums = null;
		int outputCount = 0;
		int[] weightIndex = null;
		double[] weights = null;
		int neuronCount = 0;
		int layerCount = 0;
		int[] activation = null;
		double[] p = null;

		if (methodFile.exists()) {
			method = (MLMethod) EncogDirectoryPersistence
					.loadObject(methodFile);
			final FlatNetwork flat = ((BasicNetwork) method).getFlat();

			contextTargetOffset = flat.getContextTargetOffset();
			contextTargetSize = flat.getContextTargetSize();
			hasContext = flat.getHasContext();
			inputCount = flat.getInputCount();
			layerContextCount = flat.getLayerContextCount();
			layerCounts = flat.getLayerCounts();
			layerFeedCounts = flat.getLayerFeedCounts();
			layerIndex = flat.getLayerIndex();
			layerOutput = flat.getLayerOutput();
			layerSums = flat.getLayerSums();
			outputCount = flat.getOutputCount();
			weightIndex = flat.getWeightIndex();
			weights = flat.getWeights();
			activation = createActivations(flat);
			p = createParams(flat);
			neuronCount = flat.getLayerOutput().length;
			layerCount = flat.getLayerCounts().length;
		}

		setIndentLevel(2);
		indentIn();
		addNameValue("string EXPORT_FILENAME", "\"" + processFile.getName()
				+ "\"");

		addNameValue("int _neuronCount", neuronCount);
		addNameValue("int _layerCount", layerCount);
		addNameValue("int _contextTargetOffset[]", contextTargetOffset);
		addNameValue("int _contextTargetSize[]", contextTargetSize);
		addNameValue("bool _hasContext", hasContext ? "true" : "false");
		addNameValue("int _inputCount", inputCount);
		addNameValue("int _layerContextCount[]", layerContextCount);
		addNameValue("int _layerCounts[]", layerCounts);
		addNameValue("int _layerFeedCounts[]", layerFeedCounts);
		addNameValue("int _layerIndex[]", layerIndex);
		addNameValue("double _layerOutput[]", layerOutput);
		addNameValue("double _layerSums[]", layerSums);
		addNameValue("int _outputCount", outputCount);
		addNameValue("int _weightIndex[]", weightIndex);
		addNameValue("double _weights[]", weights);
		addNameValue("int _activation[]", activation);
		addNameValue("double _p[]", p);

		indentOut();
		setIndentLevel(0);
	}

	private void processObtain() {
		setIndentLevel(3);

		addLine("FileWrite(iHandle, when,");

		final DataField[] fields = getAnalyst().getScript().getFields();
		String lastLine = null;
		for (final DataField field : fields) {
			final DataField df = field;
			if (!df.getName().equalsIgnoreCase("time")
					&& !df.getName().equalsIgnoreCase("prediction")) {
				final String str = EngineArray.replace(df.getSource(), "##",
						"pos");
				if (lastLine != null) {
					addLine(lastLine + ",");
				}
				lastLine = str;
			}
		}

		if (lastLine != null) {
			addLine(lastLine);
		}
		addLine(");");
		setIndentLevel(0);
	}

	@Override
	public void processToken(final String command) {
		if (command.equalsIgnoreCase("MAIN-BLOCK")) {
			processMainBlock();
		} else if (command.equals("CALC")) {
			processCalc();
		} else if (command.equals("OBTAIN")) {
			processObtain();
		} else if (command.equals("HEADERS")) {
			processHeaders();
		}
		setIndentLevel(0);
	}

}
