/*******************************************************************************
 * Copyright (c) 2007, 2018 Alphonse Van Assche and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Alphonse Van Assche - initial API and implementation
 *    Alexander Kurtakov - cleanups and simplification
 *******************************************************************************/
package org.eclipse.linuxtools.internal.rpm.rpmlint.builder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.linuxtools.internal.rpm.rpmlint.Activator;
import org.eclipse.linuxtools.internal.rpm.rpmlint.RpmlintLog;
import org.eclipse.linuxtools.internal.rpm.rpmlint.parser.RpmlintItem;
import org.eclipse.linuxtools.internal.rpm.rpmlint.parser.RpmlintParser;
import org.eclipse.linuxtools.rpm.ui.editor.markers.SpecfileErrorHandler;
import org.eclipse.linuxtools.rpm.ui.editor.markers.SpecfileTaskHandler;
import org.eclipse.linuxtools.rpm.ui.editor.parser.SpecfileParser;

/**
 * Visitor that generates markers for rpmlint found warnings and errors.
 *
 */
public class RpmlintMarkerVisitor implements IResourceVisitor {

	private List<RpmlintItem> rpmlintItems;

	private boolean firstWarningInResource;
	private SpecfileParser parser;
	private SpecfileErrorHandler errorHandler;
	private SpecfileTaskHandler taskHandler;

	/**
	 * Creates a visitor for handling .rpm and .spec files and adding markers for
	 * rpmlint warnings/errors.
	 *
	 * @param rpmlintItems The rpmlint identified warnings and errors.
	 */
	public RpmlintMarkerVisitor(List<RpmlintItem> rpmlintItems) {
		this.rpmlintItems = rpmlintItems;
		parser = new SpecfileParser();
	}

	@Override
	public boolean visit(IResource resource) throws CoreException {
		if (resource.getType() == IResource.FILE) {
			if (Activator.SPECFILE_EXTENSION.equals(resource.getFileExtension())) {
				firstWarningInResource = true;
				for (RpmlintItem item : rpmlintItems) {
					if (item.getFileName().equals(resource.getLocation().toOSString())) {
						IFile currentFile = ((IFile) resource);
						if (firstWarningInResource) {
							RpmlintParser.deleteMarkers(resource);
							// remove internal marks on the current resource
							currentFile.deleteMarkers(SpecfileErrorHandler.SPECFILE_ERROR_MARKER_ID, false,
									IResource.DEPTH_ZERO);
							firstWarningInResource = false;
						}

						String specContent = fileToString(currentFile);
						int lineNumber;
						// FIXME: workaround the wrong line number with
						// configure-without-libdir-spec
						if (item.getId().equals("configure-without-libdir-spec")) { //$NON-NLS-1$
							item.setLineNbr(-1);
							lineNumber = RpmlintParser.getRealLineNbr(specContent, "./configure"); //$NON-NLS-1$
							if (lineNumber == -1) {
								lineNumber = RpmlintParser.getRealLineNbr(specContent, "%configure"); //$NON-NLS-1$
							}
							item.setLineNbr(lineNumber);
						}

						lineNumber = item.getLineNbr();
						if (lineNumber == -1) {
							lineNumber = RpmlintParser.getRealLineNbr(specContent, item.getRefferedContent());
							if (lineNumber == -1) {
								lineNumber = 1;
							}
						}
						lineNumber -= 1;
						// end workaround

						// BTW we mark specfile with the internal marker.
						parser.setErrorHandler(getSpecfileErrorHandler(currentFile, specContent));
						parser.setTaskHandler(getSpecfileTaskHandler(currentFile, specContent));
						parser.parse(specContent);

						IDocument document = new Document(specContent);
						int charStart = getLineOffset(document, lineNumber);
						int charEnd = charStart + getLineLength(document, lineNumber);
						RpmlintParser.addMarker(currentFile, item.getId() + ": " //$NON-NLS-1$
								+ item.getMessage(), lineNumber, charStart, charEnd, item.getSeverity(), item.getId(),
								item.getRefferedContent());
					}
				}
			} else if (Activator.RPMFILE_EXTENSION.equals(resource.getFileExtension())) {
				firstWarningInResource = true;
				for (RpmlintItem item : rpmlintItems) {
					IFile currentFile = ((IFile) resource);
					if (firstWarningInResource) {
						RpmlintParser.deleteMarkers(resource);
						// remove internal marks on the current resource
						currentFile.deleteMarkers(SpecfileErrorHandler.SPECFILE_ERROR_MARKER_ID, false,
								IResource.DEPTH_ZERO);
						firstWarningInResource = false;
					}

					RpmlintParser.addMarker(currentFile, item.getId() + ": " //$NON-NLS-1$
							+ item.getMessage(), item.getSeverity(), item.getId(), item.getRefferedContent());
				}
			}
		}
		return true;
	}

	private SpecfileErrorHandler getSpecfileErrorHandler(IFile file, String specContent) {
		if (errorHandler == null) {
			errorHandler = new SpecfileErrorHandler(file, new Document(specContent));
		} else {
			errorHandler.setFile(file);
			errorHandler.setDocument(new Document(specContent));
		}
		return errorHandler;
	}

	private SpecfileTaskHandler getSpecfileTaskHandler(IFile file, String specContent) {
		if (taskHandler == null) {
			taskHandler = new SpecfileTaskHandler(file, new Document(specContent));
		} else {
			taskHandler.setFile(file);
			taskHandler.setDocument(new Document(specContent));
		}
		return taskHandler;
	}

	private static int getLineOffset(IDocument document, int lineNumber) {
		try {
			return document.getLineOffset(lineNumber);
		} catch (BadLocationException e) {
			RpmlintLog.logError(e);
			return 1;
		}
	}

	private static int getLineLength(IDocument document, int lineNumber) {
		try {
			return document.getLineLength(lineNumber);
		} catch (BadLocationException e) {
			RpmlintLog.logError(e);
			return 1;
		}
	}

	private static String fileToString(IFile file) {
		String ret = ""; //$NON-NLS-1$
		try (InputStream in = file.getContents()) {
			int nbrOfByte = in.available();
			byte[] bytes = new byte[nbrOfByte];
			in.read(bytes);
			ret = new String(bytes);
		} catch (CoreException | IOException e) {
			RpmlintLog.logError(e);
		}
		return ret;
	}

}
