/*******************************************************************************
 * Copyright (c) 2004, 2011 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * IBM - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.make.xlc.core.scannerconfig;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.IMarkerGenerator;
import org.eclipse.cdt.make.core.scannerconfig.IScannerInfoCollector;
import org.eclipse.cdt.make.core.scannerconfig.IScannerInfoCollector2;
import org.eclipse.cdt.make.core.scannerconfig.IScannerInfoConsoleParser;
import org.eclipse.cdt.make.internal.core.scannerconfig.util.TraceUtil;
import org.eclipse.cdt.make.internal.core.scannerconfig2.SCProfileInstance;
import org.eclipse.cdt.make.internal.core.scannerconfig2.ScannerConfigProfile.BuildOutputProvider;
import org.eclipse.cdt.make.internal.core.scannerconfig2.ScannerConfigProfileManager;
import org.eclipse.cdt.make.xlc.core.activator.Activator;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.content.IContentTypeManager;

/**
 * @author crecoskie
 *
 */
public abstract class AbstractXLCBuildOutputParser implements IScannerInfoConsoleParser {

	protected static final String[] COMPILER_INVOCATION = { "xlc", "xlC"//$NON-NLS-1$ //$NON-NLS-2$
	};
	protected static final String DASHIDASH = "-I-"; //$NON-NLS-1$
	protected static final String DASHI = "-I"; //$NON-NLS-1$
	protected static final String DASHD = "-D"; //$NON-NLS-1$

	protected IProject fProject;
	protected IScannerInfoCollector fCollector;
	protected IPath fWorkingDir;
	protected IMarkerGenerator fMarkerGenerator;
	protected XLCBuildOutputParserUtility fUtility;

	protected boolean fBMultiline = false;
	protected String fSMultiline = ""; //$NON-NLS-1$

	protected String[] fCompilerCommands = { "xlc", "xlC" }; //$NON-NLS-1$ //$NON-NLS-2$

	/**
	 * @return Returns the fProject.
	 */
	protected IProject getProject() {
		return fProject;
	}

	/**
	 * @return Returns the fCollector.
	 */
	protected IScannerInfoCollector getCollector() {
		return fCollector;
	}

	public void startup(IProject project, IScannerInfoCollector collector) {
		fProject = project;
		fCollector = collector;
		fCompilerCommands = computeCompilerCommands();
	}

	/**
	 * Returns array of additional compiler commands to look for
	 *
	 * @return String[]
	 */
	protected String[] computeCompilerCommands() {
		if (fProject != null) {
			SCProfileInstance profileInstance = ScannerConfigProfileManager.getInstance().getSCProfileInstance(
					fProject, ScannerConfigProfileManager.NULL_PROFILE_ID);
			BuildOutputProvider boProvider = profileInstance.getProfile().getBuildOutputProviderElement();
			if (boProvider != null) {
				String compilerCommandsString = boProvider.getScannerInfoConsoleParser().getCompilerCommands();
				if (compilerCommandsString != null && compilerCommandsString.length() > 0) {
					String[] compilerCommands = compilerCommandsString.split(",\\s*"); //$NON-NLS-1$
					if (compilerCommands.length > 0) {
						String[] compilerInvocation = new String[COMPILER_INVOCATION.length + compilerCommands.length];
						System.arraycopy(COMPILER_INVOCATION, 0, compilerInvocation, 0, COMPILER_INVOCATION.length);
						System.arraycopy(compilerCommands, 0, compilerInvocation, COMPILER_INVOCATION.length,
								compilerCommands.length);
						return compilerInvocation;
					}
				}
			}
		}
		return COMPILER_INVOCATION;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.eclipse.cdt.make.core.scannerconfig.IScannerInfoConsoleParser#processLine
	 * (java.lang.String)
	 */
	@Override
	public boolean processLine(String line) {
		if (line.trim().length() == 0) {
			return false;
		}

		boolean rc = false;
		int lineBreakPos = line.length() - 1;
		char[] lineChars = line.toCharArray();
		while (lineBreakPos >= 0 && Character.isWhitespace(lineChars[lineBreakPos])) {
			lineBreakPos--;
		}
		if (lineBreakPos >= 0) {
			if (lineChars[lineBreakPos] != '\\' || (lineBreakPos > 0 && lineChars[lineBreakPos - 1] == '\\')) {
				lineBreakPos = -1;
			}
		}
		// check for multiline commands (ends with '\')
		if (lineBreakPos >= 0) {
			fSMultiline += line.substring(0, lineBreakPos);
			fBMultiline = true;
			return rc;
		}
		if (fBMultiline) {
			line = fSMultiline + line;
			fBMultiline = false;
			fSMultiline = ""; //$NON-NLS-1$
		}
		line = line.trim();
		try{
			TraceUtil.outputTrace("XLCBuildOutputParser parsing line: [", line, "]"); //$NON-NLS-1$ //$NON-NLS-2$
		}catch(NoClassDefFoundError e){
			//no problem, as this may be called from a standalone indexer
		}
		// make\[[0-9]*\]: error_desc
		int firstColon = line.indexOf(':');
		String make = line.substring(0, firstColon + 1);
		if (firstColon != -1 && make.indexOf("make") != -1) { //$NON-NLS-1$
			boolean enter = false;
			String msg = line.substring(firstColon + 1).trim();
			if ((enter = msg.startsWith("Entering directory")) || //$NON-NLS-1$
					(msg.startsWith("Leaving directory"))) { //$NON-NLS-1$
				int s = msg.indexOf('`');
				int e = msg.indexOf('\'');
				if (s != -1 && e != -1) {
					String dir = msg.substring(s + 1, e);
					if (getUtility() != null) {
						getUtility().changeMakeDirectory(dir, getDirectoryLevel(line), enter);
					}
					return rc;
				}
			}
		}
		// call sublclass to process a single line
		return processSingleLine(line.trim());
	}

	protected synchronized XLCBuildOutputParserUtility getUtility() {
		if (fUtility == null)
			fUtility = new XLCBuildOutputParserUtility(fProject, fWorkingDir, fMarkerGenerator);

		return fUtility;
	}

	protected int getDirectoryLevel(String line) {
		int s = line.indexOf('[');
		int num = 0;
		if (s != -1) {
			int e = line.indexOf(']');
			String number = line.substring(s + 1, e).trim();
			try {
				num = Integer.parseInt(number);
			} catch (NumberFormatException exc) {
			}
		}
		return num;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.eclipse.cdt.make.core.scannerconfig.IScannerInfoConsoleParser#shutdown
	 * ()
	 */
	@Override
	public void shutdown() {
		if (getUtility() != null) {
			getUtility().reportProblems();
		}

		if(fCollector != null && fCollector instanceof IScannerInfoCollector2) {
			IScannerInfoCollector2 collector = (IScannerInfoCollector2) fCollector;
			try {
				collector.updateScannerConfiguration(null);
			} catch (CoreException e) {
				// TODO Auto-generated catch block
				Activator.log(e);
			}
		}
	}

	/**
	 * Tokenizes a line into an array of commands. Commands are separated by
	 * ';', '&&' or '||'. Tokens are separated by whitespace unless found inside
	 * of quotes, back-quotes, or double quotes. Outside of single-, double- or
	 * back-quotes a backslash escapes white-spaces, all quotes, the backslash,
	 * '&' and '|'. A backslash used for escaping is removed. Quotes other than
	 * the back-quote plus '&&', '||', ';' are removed, also.
	 *
	 * @param line
	 *            to tokenize
	 * @return array of commands
	 */
	protected String[][] tokenize(String line, boolean escapeInsideDoubleQuotes) {
		ArrayList<String[]> commands = new ArrayList<String[]>();
		ArrayList<String> tokens = new ArrayList<String>();
		StringBuffer token = new StringBuffer();

		final char[] input = line.toCharArray();
		boolean nextEscaped = false;
		char currentQuote = 0;
		for (int i = 0; i < input.length; i++) {
			final char c = input[i];
			final boolean escaped = nextEscaped;
			nextEscaped = false;

			if (currentQuote != 0) {
				if (c == currentQuote) {
					if (escaped) {
						token.append(c);
					} else {
						if (c == '`') {
							token.append(c); // preserve back-quotes
						}
						currentQuote = 0;
					}
				} else {
					if (escapeInsideDoubleQuotes && currentQuote == '"' && c == '\\') {
						nextEscaped = !escaped;
						if (escaped) {
							token.append(c);
						}
					} else {
						if (escaped) {
							token.append('\\');
						}
						token.append(c);
					}
				}
			} else {
				switch (c) {
				case '\\':
					if (escaped) {
						token.append(c);
					} else {
						nextEscaped = true;
					}
					break;
				case '\'':
				case '"':
				case '`':
					if (escaped) {
						token.append(c);
					} else {
						if (c == '`') {
							token.append(c);
						}
						currentQuote = c;
					}
					break;
				case ';':
					if (escaped) {
						token.append(c);
					} else {
						endCommand(token, tokens, commands);
					}
					break;
				case '&':
				case '|':
					if (escaped || i + 1 >= input.length || input[i + 1] != c) {
						token.append(c);
					} else {
						i++;
						endCommand(token, tokens, commands);
					}
					break;

				default:
					if (Character.isWhitespace(c)) {
						if (escaped) {
							token.append(c);
						} else {
							endToken(token, tokens);
						}
					} else {
						if (escaped) {
							token.append('\\'); // for windows put backslash
												// back onto the token.
						}
						token.append(c);
					}
				}
			}
		}
		endCommand(token, tokens, commands);
		return commands.toArray(new String[commands.size()][]);
	}

	protected void endCommand(StringBuffer token, ArrayList<String> tokens, ArrayList<String[]> commands) {
		endToken(token, tokens);
		if (!tokens.isEmpty()) {
			commands.add(tokens.toArray(new String[tokens.size()]));
			tokens.clear();
		}
	}

	protected void endToken(StringBuffer token, ArrayList<String> tokens) {
		if (token.length() > 0) {
			tokens.add(token.toString());
			token.setLength(0);
		}
	}

	protected boolean processSingleLine(String line) {
		boolean rc = false;
		String[][] tokens = tokenize(line, true);
		for (int i = 0; i < tokens.length; i++) {
			String[] command = tokens[i];
			if (processCommand(command)) {
				rc = true;
			} else { // go inside quotes, if the compiler is called per wrapper
						// or shell script
				for (int j = 0; j < command.length; j++) {
					String[][] subtokens = tokenize(command[j], true);
					for (int k = 0; k < subtokens.length; k++) {
						String[] subcommand = subtokens[k];
						if (subcommand.length > 1) { // only proceed if there is
														// any additional info
							if (processCommand(subcommand)) {
								rc = true;
							}
						}
					}
				}
			}
		}
		return rc;
	}

	protected int findCompilerInvocation(String[] tokens) {
		for (int i = 0; i < tokens.length; i++) {
			final String token = tokens[i].toLowerCase();
			final int searchFromOffset = Math.max(token.lastIndexOf('/'), token.lastIndexOf('\\')) + 1;
			for (int j = 0; j < fCompilerCommands.length; j++) {
				if (token.indexOf(fCompilerCommands[j], searchFromOffset) != -1) {
					return i;
				}
			}
		}
		return -1;
	}

	@Override
	public void startup(IProject project, IPath workingDirectory, IScannerInfoCollector collector,
			IMarkerGenerator markerGenerator) {
		fProject = project;
		fWorkingDir = workingDirectory;
		fCollector = collector;
		fMarkerGenerator = markerGenerator;

	}

	abstract protected boolean processCommand(String[] tokens);

	protected  List<String> getFileExtensionsList() {
		IContentTypeManager manager = Platform.getContentTypeManager();
		List<String> extensions = new LinkedList<String>();
		IContentType cSource = manager.getContentType(CCorePlugin.CONTENT_TYPE_CSOURCE);
		IContentType cppSource = manager.getContentType(CCorePlugin.CONTENT_TYPE_CXXSOURCE);

		String[] cExtensions = cSource.getFileSpecs(IContentType.FILE_EXTENSION_SPEC);
		String[] cppExtensions = cppSource.getFileSpecs(IContentType.FILE_EXTENSION_SPEC);

		for (int k = 0; k < cExtensions.length; k++) {
			extensions.add("." + cExtensions[k]); //$NON-NLS-1$
		}

		for (int k = 0; k < cppExtensions.length; k++) {
			extensions.add("." + cppExtensions[k]); //$NON-NLS-1$
		}

		return extensions;
	}

	protected String[] getFileExtensions() {
		IContentTypeManager manager = Platform.getContentTypeManager();
		List<String> extensions = new LinkedList<String>();
		IContentType cSource = manager.getContentType(CCorePlugin.CONTENT_TYPE_CSOURCE);
		IContentType cppSource = manager.getContentType(CCorePlugin.CONTENT_TYPE_CXXSOURCE);

		String[] cExtensions = cSource.getFileSpecs(IContentType.FILE_EXTENSION_SPEC);
		String[] cppExtensions = cppSource.getFileSpecs(IContentType.FILE_EXTENSION_SPEC);

		for (int k = 0; k < cExtensions.length; k++) {
			extensions.add("." + cExtensions[k]); //$NON-NLS-1$
		}

		for (int k = 0; k < cppExtensions.length; k++) {
			extensions.add("." + cppExtensions[k]); //$NON-NLS-1$
		}

		return extensions.toArray(new String[0]);
	}

}
