/*******************************************************************************
 * Copyright (c) 2007, 2016 Intel Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 * Intel Corporation - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.managedbuilder.internal.buildmodel;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;

import org.eclipse.cdt.core.settings.model.util.CDataUtil;
import org.eclipse.cdt.managedbuilder.buildmodel.BuildDescriptionManager;
import org.eclipse.cdt.managedbuilder.buildmodel.IBuildCommand;
import org.eclipse.cdt.managedbuilder.buildmodel.IBuildDescription;
import org.eclipse.cdt.managedbuilder.buildmodel.IBuildResource;
import org.eclipse.cdt.managedbuilder.buildmodel.IBuildStep;
import org.eclipse.cdt.managedbuilder.buildmodel.IStepVisitor;
import org.eclipse.cdt.managedbuilder.core.ManagedBuilderCorePlugin;
import org.eclipse.cdt.managedbuilder.internal.core.ManagedMakeMessages;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

public class BuildDescriptionGnuMakefileGenerator {
	private static final String OUT_STEP_RULE = "post_build"; //$NON-NLS-1$
	private static final String IN_STEP_RULE = "pre_build"; //$NON-NLS-1$
	private static final String ALL = "all"; //$NON-NLS-1$
	private static final String TARGET_SEPARATOR = ":"; //$NON-NLS-1$
	private static final String LINE_SEPARATOR = "\n"; //$NON-NLS-1$
	private static final String TAB = "\t"; //$NON-NLS-1$
	private static final String SPACE = " "; //$NON-NLS-1$
	private static final String ENCODING = "utf-8"; //$NON-NLS-1$
	private static final String VAR_SOURCES = "SOURCES"; //$NON-NLS-1$
	private static final String VAR_TARGETS = "TARGETS"; //$NON-NLS-1$
	private static final String EQUALS = "="; //$NON-NLS-1$
	private static final String VARREF_PREFIX = "${"; //$NON-NLS-1$
	private static final String VARREF_SUFFIX = "}"; //$NON-NLS-1$

	private static final String DOT_DOT_SLASH = "../"; //$NON-NLS-1$
	private static final String DOT_DOT_BACKSLASH = "..\\"; //$NON-NLS-1$

	private IBuildDescription fDes;

	private class DescriptionVisitor implements IStepVisitor {
		Writer fWriter;

		DescriptionVisitor(Writer writer) {
			fWriter = writer;
		}

		@Override
		public int visit(IBuildStep step) throws CoreException {
			if (step == fDes.getInputStep() || step == fDes.getOutputStep())
				return VISIT_CONTINUE;

			try {
				write(fWriter, step);
			} catch (IOException e) {
				throw new CoreException(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.getUniqueIdentifier(),
						ManagedMakeMessages.getString("BuildDescriptionGnuMakefileGenerator.0"), e)); //$NON-NLS-1$
			}
			return VISIT_CONTINUE;
		}
	}

	public BuildDescriptionGnuMakefileGenerator(IBuildDescription des) {
		fDes = des;
	}

	public void store(OutputStream stream) throws CoreException {
		Writer writer = createWriter(stream);

		try {
			writer.write(VAR_SOURCES);
			writer.write(EQUALS);
			IBuildStep step = fDes.getInputStep();
			String tmp = toString(step.getOutputResources());
			writer.write(tmp);
			writer.write(LINE_SEPARATOR);
			writer.write(LINE_SEPARATOR);

			writer.write(VAR_TARGETS);
			writer.write(EQUALS);
			step = fDes.getOutputStep();
			tmp = toString(step.getInputResources());
			writer.write(tmp);
			writer.write(LINE_SEPARATOR);
			writer.write(LINE_SEPARATOR);

			writer.write(LINE_SEPARATOR);
			writeRuleHeader(writer, ALL, IN_STEP_RULE + SPACE + OUT_STEP_RULE);
			writer.write(LINE_SEPARATOR);
			writer.write(LINE_SEPARATOR);

			write(writer, fDes.getOutputStep());

			write(writer, fDes.getInputStep());

			BuildDescriptionManager.accept(new DescriptionVisitor(writer), fDes, true);

			writer.flush();
		} catch (IOException e) {
			throw new CoreException(new Status(IStatus.ERROR, ManagedBuilderCorePlugin.getUniqueIdentifier(),
					ManagedMakeMessages.getString("BuildDescriptionGnuMakefileGenerator.1"), e)); //$NON-NLS-1$
		}

	}

	protected Writer createWriter(OutputStream stream) {
		try {
			return new OutputStreamWriter(stream, ENCODING);
		} catch (UnsupportedEncodingException e1) {
			ManagedBuilderCorePlugin.log(e1);
		}
		return new OutputStreamWriter(stream);

	}

	protected String createVarRef(String var) {
		return new StringBuilder().append(VARREF_PREFIX).append(var).append(VARREF_SUFFIX).toString();
	}

	protected void write(Writer writer, IBuildStep step) throws CoreException, IOException {
		writer.write(LINE_SEPARATOR);

		String target, deps;
		if (step == fDes.getOutputStep()) {
			target = OUT_STEP_RULE;
			deps = createVarRef(VAR_TARGETS);
		} else if (step == fDes.getInputStep()) {
			target = IN_STEP_RULE;
			deps = ""; //$NON-NLS-1$
		} else {
			IBuildResource[] inputs = step.getInputResources();
			IBuildResource[] outputs = step.getOutputResources();
			target = toString(outputs);
			deps = toString(inputs);
		}

		writeRuleHeader(writer, target, deps);

		IBuildCommand[] cmds = step.getCommands(null, null, null, true);
		for (int i = 0; i < cmds.length; i++) {
			String cmdStr = toString(cmds[i]);
			writeCommand(writer, cmdStr);
		}

		writer.write(LINE_SEPARATOR);
		writer.write(LINE_SEPARATOR);

	}

	protected void writeCommand(Writer writer, String cmd) throws IOException {
		writer.write(TAB);
		writer.write(cmd);
		writer.write(LINE_SEPARATOR);
	}

	protected String toString(IBuildCommand cmd) {
		StringBuilder buf = new StringBuilder();
		buf.append(cmd.getCommand());
		String argsString = CDataUtil.arrayToString(cmd.getArgs(), SPACE);
		if (argsString != null && argsString.length() != 0) {
			buf.append(SPACE);
			buf.append(argsString);
		}
		return removeDotDotSlashesAndBackSlashesHack(buf.toString());
	}

	protected void writeRuleHeader(Writer writer, String target, String deps) throws IOException {
		writer.write(target);
		writer.write(TARGET_SEPARATOR);
		writer.write(SPACE);
		writer.write(deps);
		writer.write(LINE_SEPARATOR);
	}

	protected String toString(IBuildResource[] rcs) {
		StringBuilder buf = new StringBuilder();
		for (int i = 0; i < rcs.length; i++) {
			if (i != 0)
				buf.append(SPACE);
			buf.append(toString(rcs[i]));

		}
		return buf.toString();
	}

	protected String toString(IBuildResource rc) {
		return removeDotDotSlashesAndBackSlashesHack(
				BuildDescriptionManager.getRelPath(fDes.getDefaultBuildDirLocation(), rc.getLocation()).toString());
	}

	/*
	 * this is a very bad hack that removes the "../" and "..\" from the string
	 * this is needed to overcome an assumption that the source root is ../
	 * the BuildDescription calculation mechanism should be fixed to remove this assumption
	 */
	private String removeDotDotSlashesAndBackSlashesHack(String str) {
		str = removeDotDotSlashes(str);
		return removeDotDotBackslashes(str);
	}

	private String removeDotDotSlashes(String str) {
		int index = str.indexOf(DOT_DOT_SLASH, 0);
		if (index != -1) {
			StringBuilder buf = new StringBuilder();
			int start = 0;
			for (; index != -1; index = str.indexOf(DOT_DOT_SLASH, start)) {
				buf.append(str.substring(start, index));
				start = index + DOT_DOT_SLASH.length();
			}
			buf.append(str.substring(start));
			return buf.toString();
		}
		return str;
	}

	private String removeDotDotBackslashes(String str) {
		int index = str.indexOf(DOT_DOT_BACKSLASH, 0);
		if (index != -1) {
			StringBuilder buf = new StringBuilder();
			int start = 0;
			for (; index != -1; index = str.indexOf(DOT_DOT_BACKSLASH, start)) {
				buf.append(str.substring(start, index));
				start = index + DOT_DOT_BACKSLASH.length();
			}
			buf.append(str.substring(start));
			return buf.toString();
		}
		return str;
	}

}
