/*******************************************************************************
 * Copyright (c) 2006 IONA Technologies PLC
 * 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:
 *     IONA Technologies PLC - initial API and implementation
 *******************************************************************************/
package org.eclipse.stp.sc.xmlvalidator.builder;

import java.util.Map;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.stp.common.logging.LoggingProxy;
import org.eclipse.stp.sc.xmlvalidator.XmlValidatorPlugin;
import org.eclipse.stp.sc.xmlvalidator.XmlValidatorResources;
import org.eclipse.stp.sc.xmlvalidator.preferences.PreferenceInitializer;
import org.eclipse.stp.sc.xmlvalidator.rule.engine.VRuleEngine;
import org.eclipse.stp.sc.xmlvalidator.rule.engine.VRuleManager;
import org.eclipse.stp.sc.xmlvalidator.rule.model.VRuleError;
import org.eclipse.stp.sc.xmlvalidator.utils.XMLUtils;
import org.w3c.dom.Document;

/**
 * The XML vlaidator is built based on IncrementalProjectBuilder It will called
 * during project compilation. And it use the vrule engine within this plugin to
 * validte the xml file against user defined rules. The problems will be
 * reported as problem markers. User can override the needToValidate to support
 * other extension than xml and override loadXMLFile to load xml model from file
 * such as java source code
 */
public class XmlValidator extends IncrementalProjectBuilder {
	
	private static final LoggingProxy LOG = LoggingProxy.getlogger(XmlValidator.class);
	
	public static final String BUILDER_ID = "org.eclipse.stp.sc.xmlvalidator.validatorbuilder";

	public static final String RULESET_EXT_ID = "org.eclipse.stp.sc.xmlvalidator.ruleset";

	public static final String RULESET_EXT_ATTR_LOCATION = "location";

	private static final String MARKER_TYPE = "org.eclipse.stp.xml_problem_marker";

	VRuleEngine engine;

	public XmlValidator() {
		super();
		setupRuleEngine();
	}

	public void setupRuleEngine() {
		String rulePath = XmlValidatorPlugin.getDefault().getStateLocation()
				.makeAbsolute().toOSString();
		LOG.debug("rule path set to :" + rulePath);
		System.setProperty(VRuleManager.DEFAULT_RULE_PATH_PROPERTY, rulePath);
		PreferenceInitializer.importDefaultRules();
		engine = new VRuleEngine();
		engine.init();
	}

	
	
	@SuppressWarnings("unchecked")
	@Override
	protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
			throws CoreException {
		if (kind == IncrementalProjectBuilder.FULL_BUILD) {
			fullBuild(monitor);
		} else {
			IResourceDelta delta = getDelta(getProject());

			if (delta == null) {
				fullBuild(monitor);
			} else {
				incrementalBuild(delta, monitor);
			}
		}

		return null;
	}

	protected void fullBuild(final IProgressMonitor monitor)
			throws CoreException {
		LOG.debug("xml validator fullBuild for project:"
				+ getProject().getName());
		getProject().accept(new XmlValidatorVisitor());
		getProject().refreshLocal(IProject.DEPTH_INFINITE, monitor);
	}

	protected void incrementalBuild(IResourceDelta delta,
			IProgressMonitor monitor) throws CoreException {
		// the visitor does the work.
		LOG.debug("xml validator incrementalBuild for project:"
				+ getProject().getName());
		delta.accept(new XmlValidatorDeltaVisitor());
		getProject().refreshLocal(IProject.DEPTH_INFINITE, monitor);
	}

	/**
	 * clean the xml problem markers
	 * 
	 * @param monitor,
	 *            the progress monitor
	 * @throws CoreException
	 */
	protected void clean(IProgressMonitor monitor) throws CoreException {
		LOG.debug("xml validator clean for the project:"
				+ getProject().getName());
		IProject proj = getProject();
		deleteMarkerOnResource(proj);
	}

	private void validOneXMLFile(IFile xmlFile) throws CoreException {
		// clean problem marker on this file first
		deleteMarkerOnResource(xmlFile);
		try {
			Document dom = loadXMLFile(xmlFile);
			if (dom == null) {
				return;
			}
			VRuleError[] errors = engine.validate(dom);
			if (errors == null | errors.length == 0) {
				return;
			}
			for (VRuleError error : errors) {
				createMarkerOnResource(xmlFile, error);
			}
		} catch (Exception e) {
			LOG.error("error during validating xml", e);
		}
	}

	/**
	 * check the file extension to see the builder need to validate this file or
	 * not
	 * 
	 * @param xmlFile,
	 *            the resource file to check
	 * @return, true if we need to call rule engine to validate.
	 */
	protected boolean needToValidate(IFile xmlFile) {
		if (xmlFile.getFileExtension().equals("xml")) { // todo. put this
														// extension as
														// extension parameter
			return true;
		}
		return false;
	}

	/**
	 * load the input file as xml document. for example, user can override this
	 * method to load xml annotation tree from java source code
	 * 
	 * @param xmlFile
	 * @return null if there are any problems
	 */
	protected Document loadXMLFile(IFile xmlFile) {
		Document dom = null;
		String xmlFilePath = "";
		try {
			xmlFilePath = xmlFile.getRawLocation().toOSString();
			dom = XMLUtils.loadXmlDocument(xmlFilePath);
		} catch (Exception e) {
			LOG.error("error during load xml file." + xmlFilePath, e);
		}
		return dom;
	}

	/**
	 * visit the resource file within project. call javatowsdl if it is java
	 * file and has WebService annotation
	 * 
	 * @param res
	 * @throws CoreException
	 */
	private void visitResourceFile(IResource res) throws CoreException {
		if (!(res instanceof IFile)) {
			return;
		}

		IFile file = (IFile) res;

		if (!needToValidate(file)) {
			return;
		}
		validOneXMLFile(file);
	}

	/**
	 * 
	 * @param res
	 * @throws CoreException
	 */
	protected void removeResourceFile(IResource res) throws CoreException {
		if (!(res instanceof IFile)) {
			return;
		}

		IFile file = (IFile) res;
		deleteMarkerOnResource(res);
	}

	protected void deleteMarkerOnResource(IResource res) {
		int depth = IResource.DEPTH_INFINITE;
		try {
			if (!res.exists()) {
				return;
			}
			res.deleteMarkers(MARKER_TYPE, true, depth);
		} catch (CoreException e) {
			LOG.error("error during delete xml problem marker", e);
		}
	}

	protected void createMarkerOnResource(IResource res, VRuleError error) {
		try {
			IMarker marker = res.createMarker(MARKER_TYPE);
			marker.setAttribute(IMarker.MESSAGE, error.getErrorMsg());
			marker.setAttribute(IMarker.LINE_NUMBER, error.getLineNumber());
			marker.setAttribute(IMarker.SEVERITY, error.getSeverity());
			marker.setAttribute(IMarker.LOCATION, XmlValidatorResources
					.getString("builder.marker.location") + 10);
		} catch (Exception e) {
			LOG.error("error during create problem marker", e);
		}
	}

	class XmlValidatorVisitor implements IResourceVisitor {
		public boolean visit(IResource res) throws CoreException {
			// build the specified resource.
			visitResourceFile(res);

			// return true to continue visiting children.
			return true;
		}
	}

	class XmlValidatorDeltaVisitor implements IResourceDeltaVisitor {
		public boolean visit(IResourceDelta delta) throws CoreException {
			IResource res = delta.getResource();

			switch (delta.getKind()) {
			case IResourceDelta.ADDED:
			case IResourceDelta.CHANGED:
				visitResourceFile(res);

				break;

			case IResourceDelta.REMOVED:
				LOG.debug("DeltaVistor, removed resource:"
						+ res.getFullPath());
				try {
					removeResourceFile(res);
				} catch (Exception e) {
					LOG.error("builder error", e);
				}

				break;
			default:
				break;
			}
			return true;
		}
	}

}
