/*******************************************************************************
 * Copyright (c) 2006, 2012 QNX Software Systems 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:
 *     QNX - initial API and implementation,  @author Doug Schaefer
 *******************************************************************************/

package org.eclipse.cdt.core.errorparsers;

import java.io.File;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.cdt.core.ErrorParserManager;
import org.eclipse.cdt.core.IMarkerGenerator;
import org.eclipse.cdt.internal.core.Cygwin;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;

/**
 * Error Pattern - used by Error Parser to convert build output to problem markers
 * @since 5.1
 *
 * Clients may extend this class.
 */
public class ErrorPattern {
	private final Pattern pattern;
	private final int groupFileName;
	private final int groupLineNum;
	private final int groupDesc;
	private final int groupVarName;
	private final int severity;

	private static boolean isCygwin = true;

	/**
	 * Full Pattern Constructor. Note that a group equal -1 means that
	 * the parameter is missing in the error message.
	 *
	 * @param pattern - regular expression describing the message
	 * @param groupFileName - matcher group of file name
	 * @param groupLineNum - matcher group of line number
	 * @param groupDesc - matcher group of description
	 * @param groupVarName - matcher group of variable name
	 * @param severity - severity, one of
	 *        <br>{@link IMarkerGenerator#SEVERITY_INFO},
	 *        <br>{@link IMarkerGenerator#SEVERITY_WARNING},
	 *        <br>{@link IMarkerGenerator#SEVERITY_ERROR_RESOURCE},
	 *        <br>{@link IMarkerGenerator#SEVERITY_ERROR_BUILD}
	 */
	public ErrorPattern(String pattern, int groupFileName, int groupLineNum, int groupDesc, int groupVarName,
			int severity) {
		this.pattern = Pattern.compile(pattern);
		this.groupFileName = groupFileName;
		this.groupLineNum = groupLineNum;
		this.groupDesc = groupDesc;
		this.groupVarName = groupVarName;
		this.severity = severity;
	}

	/**
	 * Pattern for errors not associated file a file
	 * (e.g. make and linker errors).
	 *
	 * @param pattern - regular expression describing the message
	 * @param groupDesc - matcher group of description
	 * @param severity - severity, one of
	 *        <br>{@link IMarkerGenerator#SEVERITY_INFO},
	 *        <br>{@link IMarkerGenerator#SEVERITY_WARNING},
	 *        <br>{@link IMarkerGenerator#SEVERITY_ERROR_RESOURCE},
	 *        <br>{@link IMarkerGenerator#SEVERITY_ERROR_BUILD}
	 */
	public ErrorPattern(String pattern, int groupDesc, int severity) {
		this(pattern, 0, 0, groupDesc, 0, severity);
	}

	/**
	 * Pattern for errors that should be skipped.
	 *
	 * @param pattern - error pattern.
	 */
	public ErrorPattern(String pattern) {
		this(pattern, 0, 0, 0, 0, -1);
	}

	/**
	 * @param input - input line.
	 * @return matcher to interpret the input line.
	 */
	public Matcher getMatcher(CharSequence input) {
		return pattern.matcher(input);
	}

	/**
	 * @param matcher - matcher to parse the input line.
	 * @return parsed file name or {@code null}.
	 */
	public String getFileName(Matcher matcher) {
		return groupFileName != 0 ? matcher.group(groupFileName) : null;
	}

	/**
	 * @param matcher - matcher to parse the input line.
	 * @return parsed line number or {@code 0}.
	 */
	public int getLineNum(Matcher matcher) {
		try {
			return groupLineNum != 0 ? Integer.valueOf(matcher.group(groupLineNum)).intValue() : 0;
		} catch (NumberFormatException e) {
			return 0;
		}
	}

	/**
	 * @param matcher - matcher to parse the input line.
	 * @return parsed description or {@code null}.
	 */
	public String getDesc(Matcher matcher) {
		return groupDesc != 0 ? matcher.group(groupDesc) : null;
	}

	/**
	 * @param matcher - matcher to parse the input line.
	 * @return parsed variable name or {@code null}.
	 */
	public String getVarName(Matcher matcher) {
		return groupVarName != 0 ? matcher.group(groupVarName) : null;
	}

	/**
	 * @param matcher - matcher to parse the input line.
	 * @return severity of the problem.
	 */
	public int getSeverity(Matcher matcher) {
		return severity;
	}

	/**
	 * Parse a line of build output and register error/warning for
	 * Problems view.
	 *
	 * @param line - one line of output.
	 * @param eoParser - {@link ErrorParserManager}.
	 * @return {@code true} if error/warning/info problem was found.
	 */
	public boolean processLine(String line, ErrorParserManager eoParser) {
		Matcher matcher = getMatcher(line);
		if (!matcher.find())
			return false;

		return recordError(matcher, eoParser);
	}

	/**
	 * Register the error in {@link ErrorParserManager}.
	 *
	 * @param matcher - matcher to parse the input line.
	 * @param eoParser - {@link ErrorParserManager}.
	 * @return {@code true} indicating that error was found.
	 */
	protected boolean recordError(Matcher matcher, ErrorParserManager eoParser) {
		int severity = getSeverity(matcher);
		if (severity == -1)
			// Skip
			return true;

		String fileName = getFileName(matcher);
		int lineNum = getLineNum(matcher);
		String desc = getDesc(matcher);
		String varName = getVarName(matcher);
		IPath externalPath = null;

		IResource file = null;
		if (fileName != null) {
			file = eoParser.findFileName(fileName);

			if (file == null) {
				// If the file is not found in the workspace we attach the problem to the project
				// and add the external path to the file.
				file = eoParser.getProject();
				externalPath = getLocation(fileName);
			}
		}

		eoParser.generateExternalMarker(file, lineNum, desc, severity, varName, externalPath);
		return true;
	}

	/**
	 * If the file designated by filename exists, return the IPath representation of the filename
	 * If it does not exist, try cygpath translation
	 *
	 * @param filename - file name
	 * @return location (outside of the workspace).
	 */
	protected IPath getLocation(String filename) {
		IPath path = new Path(filename);
		File file = path.toFile();
		if (!file.exists() && isCygwin && path.isAbsolute()) {
			try {
				String cygfilename = Cygwin.cygwinToWindowsPath(filename);
				IPath convertedPath = new Path(cygfilename);
				file = convertedPath.toFile();
				if (file.exists()) {
					path = convertedPath;
				}
			} catch (UnsupportedOperationException e) {
				isCygwin = false;
			} catch (IOException e) {
			}
		}
		return path;
	}

}
