//------------------------------------------------------------------------------
// Copyright (c) 2005, 2006 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 Corporation - initial implementation
//------------------------------------------------------------------------------
package org.eclipse.epf.search.ui.internal;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.epf.common.xml.AbstractSAXParser;
import org.eclipse.epf.library.LibraryService;
import org.eclipse.epf.library.edit.util.ModelStructure;
import org.eclipse.epf.library.util.LibraryUtil;
import org.eclipse.epf.search.ui.SearchUIPlugin;
import org.eclipse.epf.search.ui.SearchUIResources;
import org.eclipse.epf.uma.Activity;
import org.eclipse.epf.uma.Artifact;
import org.eclipse.epf.uma.ContentDescription;
import org.eclipse.epf.uma.ContentElement;
import org.eclipse.epf.uma.ContentPackage;
import org.eclipse.epf.uma.DescribableElement;
import org.eclipse.epf.uma.MethodElement;
import org.eclipse.epf.uma.MethodLibrary;
import org.eclipse.epf.uma.MethodPackage;
import org.eclipse.epf.uma.MethodPlugin;
import org.eclipse.epf.uma.Practice;
import org.eclipse.epf.uma.ProcessComponent;
import org.eclipse.epf.uma.ProcessElement;
import org.eclipse.epf.uma.ProcessPackage;
import org.eclipse.epf.uma.util.UmaUtil;
import org.xml.sax.Attributes;

/**
 * Performs a Method search by iterating the current method library and parsing
 * the content element XMI files where necessary.
 * 
 * @author Kelvin Low
 * @since 1.0
 */
public class MethodSearchOperation {

	private static final String SCAN_LIBRARY_TEXT = SearchUIResources.scanLibraryTask_name; //$NON-NLS-1$

	private static final String SCAN_ELEMENT_TEXT = SearchUIResources.scanElementTask_name; //$NON-NLS-1$

	private boolean debug;

	private MethodSearchInput searchInput;

	private ISearchResultCollector result;

	private String searchString;

	private boolean caseSensitive;

	private boolean regExp;

	private Pattern searchStringPattern;

	private Pattern elementNamePattern;

	private MethodElementScanner elementScanner;

	private List parsedFiles = new ArrayList();

	private IProgressMonitor progressMonitor;

	// Scans the content element XMI file associated with a Method element to
	// look for the search string.
	private class MethodElementScanner extends AbstractSAXParser {

		private MethodElement element;

		/**
		 * Creates a new instance.
		 */
		public MethodElementScanner() {
			super(null, false);
		}

		/**
		 * Parses the content XMI file associated with the given Method element.
		 * 
		 * @param element
		 *            a method element
		 */
		public void parse(MethodElement element) {
			this.element = element;
			String elementContentPath = getElementPath(element);
			String briefDescription = element.getBriefDescription();
			if (searchStringPattern.matcher(briefDescription).find()) {
				result.accept(element);
			}
			if (elementContentPath != null && !parsedFiles.contains(elementContentPath)) {
				super.parse(elementContentPath);
				parsedFiles.add(elementContentPath);				
			}
		}

		/**
		 * @see AbstractSAXParser#startElement(String, String, String,
		 *      Attributes)
		 */
		public void startElement(String namespaceURL, String localName,
				String qname, Attributes attributes) {
			for (int i = 0; i < attributes.getLength(); i++) {
				String name = attributes.getQName(i);
				if (!name.equals("briefDescription")) { //$NON-NLS-1$
					String value = attributes.getValue(i);
					if (searchStringPattern.matcher(value).find()) {
						result.accept(element);
					}
				}
			}
		}

		/**
		 * @see AbstractSAXParser#endElement(String, String, String)
		 */
		public void endElement(String namespaceURL, String localName,
				String qname) {
			String value = strBuf.toString();
			if (searchStringPattern.matcher(value).find()) {
				result.accept(element);
			}
			strBuf.setLength(0);
		}

	}

	/**
	 * Creates a new instance.
	 * 
	 * @param searchString
	 *            a search string
	 * @param namePattern
	 *            a name pattern
	 * @param result
	 *            a search result collector
	 */
	public MethodSearchOperation(MethodSearchInput searchInput,
			ISearchResultCollector result) {
		this.searchInput = searchInput;
		this.result = result;

		caseSensitive = searchInput.getCaseSensitive();
		regExp = searchInput.getRegularExpression();
		elementNamePattern = MethodSearchPattern.createPattern(searchInput
				.getNamePattern(), false, false);

		debug = SearchUIPlugin.getDefault().isDebugging();

		searchString = searchInput.getSearchString();
		if (searchString == null)
			searchString = ""; //$NON-NLS-1$
		if (searchString.length() > 0) {
			elementScanner = new MethodElementScanner();
			searchStringPattern = MethodSearchPattern.createPattern(
					searchString, caseSensitive, regExp);
		}

		if (debug) {
			System.out
					.println("MethodSearchOperation constructor: searchString=" //$NON-NLS-1$
							+ searchString
							+ ", elementNamePattern=" //$NON-NLS-1$
							+ searchInput.getNamePattern()
							+ ", caseSensitive=" + caseSensitive); //$NON-NLS-1$
		}
	}

	/**
	 * Executes the search operation.
	 * 
	 * @param progressMonitor
	 *            a progress monitor
	 */
	public void execute(IProgressMonitor progressMonitor) {
		this.progressMonitor = progressMonitor;
		this.progressMonitor.beginTask(SCAN_LIBRARY_TEXT, 7500);

		// Iterate the Method Library to look for elements that match the name
		// pattern.
		MethodLibrary library = LibraryService.getInstance()
				.getCurrentMethodLibrary();
		List methodPlugins = LibraryUtil.getMethodPlugins(library);
		for (Iterator i = methodPlugins.iterator(); i.hasNext()
				&& !progressMonitor.isCanceled();) {
			MethodPlugin methodPlugin = (MethodPlugin) i.next();
			matchPattern(methodPlugin);

			MethodPackage methodPackage;
			if (searchInput.getSearchScope().includeCoreContent()) {
				methodPackage = UmaUtil.findMethodPackage(methodPlugin,
						ModelStructure.DEFAULT.coreContentPath);
				searchMethodPackages(methodPackage);
			}

			if (searchInput.getSearchScope().include(
					MethodSearchScope.STANDARD_CATEGORY)) {
				methodPackage = UmaUtil.findMethodPackage(methodPlugin,
						ModelStructure.DEFAULT.disciplineDefinitionPath);
				searchMethodPackages(methodPackage);
				methodPackage = UmaUtil.findMethodPackage(methodPlugin,
						ModelStructure.DEFAULT.domainPath);
				searchMethodPackages(methodPackage);
				methodPackage = UmaUtil.findMethodPackage(methodPlugin,
						ModelStructure.DEFAULT.workProductTypePath);
				searchMethodPackages(methodPackage);
				methodPackage = UmaUtil.findMethodPackage(methodPlugin,
						ModelStructure.DEFAULT.roleSetPath);
				searchMethodPackages(methodPackage);
				methodPackage = UmaUtil.findMethodPackage(methodPlugin,
						ModelStructure.DEFAULT.toolPath);
				searchMethodPackages(methodPackage);
			}

			if (searchInput.getSearchScope().include(
					MethodSearchScope.CUSTOM_CATEGORY)) {
				methodPackage = UmaUtil.findMethodPackage(methodPlugin,
						ModelStructure.DEFAULT.customCategoryPath);
				searchMethodPackages(methodPackage);
			}

			if (searchInput.getSearchScope().include(
					MethodSearchScope.CAPABILITY_PATTERN)) {
				methodPackage = UmaUtil.findMethodPackage(methodPlugin,
						ModelStructure.DEFAULT.capabilityPatternPath);
				searchMethodPackages(methodPackage);
			}

			if (searchInput.getSearchScope().include(
					MethodSearchScope.DELIVERY_PROCESS)) {
				methodPackage = UmaUtil.findMethodPackage(methodPlugin,
						ModelStructure.DEFAULT.deliveryProcessPath);
				searchMethodPackages(methodPackage);
			}
		}
	}

	/**
	 * Searches a Method Package.
	 * 
	 * @param methodPackage
	 *            a method package
	 */
	protected void searchMethodPackages(MethodPackage methodPackage) {
		if (methodPackage == null
				|| methodPackage.getName().equals(
						ModelStructure.HIDDEN_PACKAGE_NAME)) {
			return;
		}
		if (methodPackage instanceof ProcessComponent) {
			// Add the associated CapabilityPattern or DeliveryProcess to the
			// search result.
			ProcessComponent processComponent = (ProcessComponent) methodPackage;
			matchPattern(processComponent.getProcess());
		} else if (!isInternalProcessPackage(methodPackage)) {
			matchPattern(methodPackage);
		}

		List childPackages = methodPackage.getChildPackages();
		for (Iterator i = childPackages.iterator(); i.hasNext()
				&& !progressMonitor.isCanceled();) {
			searchMethodPackages((MethodPackage) i.next());
		}
		if (methodPackage instanceof ContentPackage) {
			ContentPackage contentPackage = (ContentPackage) methodPackage;
			List contentElements = contentPackage.getContentElements();
			for (Iterator j = contentElements.iterator(); j.hasNext()
					&& !progressMonitor.isCanceled();) {
				ContentElement contentElement = (ContentElement) j.next();
				matchPattern(contentElement);
				if (contentElement instanceof Artifact) {
					searchContainedArtifacts((Artifact) contentElement);
				} else if (contentElement instanceof Practice) {
					searchSubPractices((Practice) contentElement);
				}
			}
		} else if (methodPackage instanceof ProcessPackage) {
			ProcessPackage processPackage = (ProcessPackage) methodPackage;
			List processElements = processPackage.getProcessElements();
			for (Iterator j = processElements.iterator(); j.hasNext()
					&& !progressMonitor.isCanceled();) {
				ProcessElement processElement = (ProcessElement) j.next();
				if (!(processElement instanceof Activity)) {
					matchPattern(processElement);
				}
			}
		}
	}

	/**
	 * Returns <code>true</code> if the given package is an internal Process
	 * Package.
	 * 
	 * @param methodPackage
	 *            a method or process package
	 * @return <code>true</code> if the given package is an internal process
	 *         package
	 */
	protected boolean isInternalProcessPackage(MethodPackage methodPackage) {
		if (methodPackage instanceof ProcessPackage) {
			if (UmaUtil.getProcessComponent(methodPackage) != null) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Searches the contained artifacts of an artifact.
	 * 
	 * @param artifact
	 *            an artifact
	 */
	protected void searchContainedArtifacts(Artifact artifact) {
		if (artifact != null) {
			for (Iterator i = artifact.getContainedArtifacts().iterator(); i
					.hasNext()
					&& !progressMonitor.isCanceled();) {
				Artifact containedArtifact = (Artifact) i.next();
				matchPattern(containedArtifact);
				searchContainedArtifacts(containedArtifact);
			}
		}
	}

	/**
	 * Searches the sub practices of a practice.
	 * 
	 * @param practice
	 *            a practice
	 */
	protected void searchSubPractices(Practice practice) {
		if (practice != null) {
			for (Iterator i = practice.getSubPractices().iterator(); i
					.hasNext()
					&& !progressMonitor.isCanceled();) {
				Practice subPractice = (Practice) i.next();
				matchPattern(subPractice);
				searchSubPractices(subPractice);
			}
		}
	}

	/**
	 * Adds the given method element to the search result collection if its name
	 * or presentation name matches the name pattern.
	 * 
	 * @param element
	 *            a method element
	 */
	protected void matchPattern(MethodElement element) {
		try {
			if (searchInput.getSearchScope().include(element)) {
				String name = element.getName();
				String taskName = MessageFormat.format(SCAN_ELEMENT_TEXT,
						new String[] { name });
				progressMonitor.setTaskName(taskName);
				boolean foundMatch = false;
				if (element instanceof DescribableElement) {
					String presentationName = ((DescribableElement) element)
							.getPresentationName();
					foundMatch = (name != null && elementNamePattern.matcher(
							name).matches())
							|| (presentationName != null && elementNamePattern
									.matcher(presentationName).matches());
				} else {
					foundMatch = (name != null && elementNamePattern.matcher(
							name).matches());
				}
				if (foundMatch) {
					if (searchString.length() == 0) {
						result.accept(element);
					} else {
						try {
							elementScanner.parse(element);
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
				progressMonitor.worked(1);
			}
		} catch (Exception e) {
			SearchUIPlugin.getDefault().getLogger().logError(e);
		}
	}

	/**
	 * Returns the path of the content XML file associated with a method
	 * element.
	 */
	protected String getElementPath(MethodElement element) {
		Resource resource = null;
		if (element instanceof DescribableElement) {
			DescribableElement describableElement = (DescribableElement) element;
			ContentDescription contentDescription = describableElement
					.getPresentation();
			if (contentDescription == null) {
				return null;
			}
			resource = contentDescription.eResource();
		} else {
			resource = element.eResource();
		}
		if (resource != null) {
			URI resourceURI = resource.getURI();
			if (resourceURI != null) {
				return resourceURI.toFileString();
			}
		}
		return null;
	}

}