/*******************************************************************************
 * Copyright (c) 2000, 2007 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
 *
 
 *******************************************************************************/
package org.eclipse.dltk.internal.core.search.matching;

import java.util.ArrayList;

import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.compiler.util.HashtableOfLong;
import org.eclipse.dltk.compiler.util.SimpleLookupTable;
import org.eclipse.dltk.compiler.util.SimpleSet;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.search.SearchMatch;
import org.eclipse.dltk.core.search.SearchPattern;
import org.eclipse.dltk.core.search.matching.PatternLocator;
import org.eclipse.dltk.internal.core.util.Util;


/**
 * A set of matches and possible matches, which need to be resolved.
 */
public class MatchingNodeSet {
	/**
	 * Map of matching ast nodes that don't need to be resolved to their
	 * accuracy level. Each node is removed as it is reported.
	 */
	public SimpleLookupTable matchingNodes = new SimpleLookupTable(3); // node ->
																// accuracy
	private HashtableOfLong matchingNodesKeys = new HashtableOfLong(3); // sourceRange
																		// ->
																		// node
	static Integer EXACT_MATCH = new Integer(SearchMatch.A_ACCURATE);
	static Integer POTENTIAL_MATCH = new Integer(SearchMatch.A_INACCURATE);
	static Integer ERASURE_MATCH = new Integer(SearchPattern.R_ERASURE_MATCH);
	/**
	 * Set of possible matching ast nodes. They need to be resolved to determine
	 * if they really match the search pattern.
	 */
	public SimpleSet possibleMatchingNodesSet = new SimpleSet(7);
	private HashtableOfLong possibleMatchingNodesKeys = new HashtableOfLong(7);

	public MatchingNodeSet() {
		super();
	}

	public int addMatch(ASTNode node, int matchLevel) {
		switch (matchLevel & PatternLocator.NODE_SET_MASK) {
			case PatternLocator.INACCURATE_MATCH:
				addTrustedMatch(node, POTENTIAL_MATCH);
				break;
			case PatternLocator.POSSIBLE_MATCH:
				addPossibleMatch(node);
				break;
			case PatternLocator.ERASURE_MATCH:
				addTrustedMatch(node, ERASURE_MATCH);
				break;
			case PatternLocator.ACCURATE_MATCH:
				addTrustedMatch(node, EXACT_MATCH);
		}
		return matchLevel;
	}

	public void addPossibleMatch(ASTNode node) {
		// remove existing node at same position from set
		// (case of recovery that created the same node several time
		// see http://bugs.eclipse.org/bugs/show_bug.cgi?id=29366)
		long key = (((long) node.sourceStart()) << 32) + node.sourceEnd();
		ASTNode existing = (ASTNode) this.possibleMatchingNodesKeys.get(key);
		if (existing != null && existing.getClass().equals(node.getClass()))
			this.possibleMatchingNodesSet.remove(existing);
		// add node to set
		this.possibleMatchingNodesSet.add(node);
		this.possibleMatchingNodesKeys.put(key, node);
	}

	public void addTrustedMatch(ASTNode node, boolean isExact) {
		addTrustedMatch(node, isExact ? EXACT_MATCH : POTENTIAL_MATCH);
	}

	void addTrustedMatch(ASTNode node, Integer level) {
		// remove existing node at same position from set
		// (case of recovery that created the same node several time
		// see http://bugs.eclipse.org/bugs/show_bug.cgi?id=29366)
		long key = (((long) node.sourceStart()) << 32) + node.sourceEnd();
		ASTNode existing = (ASTNode) this.matchingNodesKeys.get(key);
		if (existing != null && existing.getClass().equals(node.getClass()))
			this.matchingNodes.removeKey(existing);
		// map node to its accuracy level
		this.matchingNodes.put(node, level);
		this.matchingNodesKeys.put(key, node);
	}

	protected boolean hasPossibleNodes(int start, int end) {
		Object[] nodes = this.possibleMatchingNodesSet.values;
		for (int i = 0, l = nodes.length; i < l; i++) {
			ASTNode node = (ASTNode) nodes[i];
			if (node != null && start <= node.sourceStart() && node.sourceEnd() <= end)
				return true;
		}
		nodes = this.matchingNodes.keyTable;
		for (int i = 0, l = nodes.length; i < l; i++) {
			ASTNode node = (ASTNode) nodes[i];
			if (node != null && start <= node.sourceStart() && node.sourceEnd() <= end)
				return true;
		}
		return false;
	}

	/**
	 * Returns the matching nodes that are in the given range in the source
	 * order.
	 */
	public ASTNode[] matchingNodes(int start, int end) {
		ArrayList nodes = null;
		Object[] keyTable = this.matchingNodes.keyTable;
		for (int i = 0, l = keyTable.length; i < l; i++) {
			ASTNode node = (ASTNode) keyTable[i];
			if (node != null && start <= node.sourceStart() && node.sourceEnd() <= end) {
				if (nodes == null)
					nodes = new ArrayList();
				nodes.add(node);
			}
		}
		if (nodes == null)
			return null;
		ASTNode[] result = new ASTNode[nodes.size()];
		nodes.toArray(result);
		// sort nodes by source starts
		Util.Comparer comparer = new Util.Comparer() {
			public int compare(Object o1, Object o2) {
				return ((ASTNode) o1).sourceStart() - ((ASTNode) o2).sourceStart();
			}
		};
		Util.sort(result, comparer);
		return result;
	}

	public Object removePossibleMatch(ASTNode node) {
		long key = (((long) node.sourceStart()) << 32) + node.sourceEnd();
		ASTNode existing = (ASTNode) this.possibleMatchingNodesKeys.get(key);
		if (existing == null)
			return null;
		this.possibleMatchingNodesKeys.put(key, null);
		return this.possibleMatchingNodesSet.remove(node);
	}

	public Object removeTrustedMatch(ASTNode node) {
		long key = (((long) node.sourceStart()) << 32) + node.sourceEnd();
		ASTNode existing = (ASTNode) this.matchingNodesKeys.get(key);
		if (existing == null)
			return null;
		this.matchingNodesKeys.put(key, null);
		return this.matchingNodes.removeKey(node);
	}

	public String toString() {
		// TODO (jerome) should show both tables
		StringBuffer result = new StringBuffer();
		result.append("Exact matches:"); //$NON-NLS-1$
		Object[] keyTable = this.matchingNodes.keyTable;
		Object[] valueTable = this.matchingNodes.valueTable;
		for (int i = 0, l = keyTable.length; i < l; i++) {
			ASTNode node = (ASTNode) keyTable[i];
			if (node == null)
				continue;
			result.append("\n\t"); //$NON-NLS-1$
			switch (((Integer) valueTable[i]).intValue()) {
				case SearchMatch.A_ACCURATE:
					result.append("ACCURATE_MATCH: "); //$NON-NLS-1$
					break;
				case SearchMatch.A_INACCURATE:
					result.append("INACCURATE_MATCH: "); //$NON-NLS-1$
					break;
				case SearchPattern.R_ERASURE_MATCH:
					result.append("ERASURE_MATCH: "); //$NON-NLS-1$
					break;
			}
			//node.print(0, result);
			if (DLTKCore.DEBUG) {
				System.err.println("TODO: Add node print..."); //$NON-NLS-1$
			}
		}
		result.append("\nPossible matches:"); //$NON-NLS-1$
		Object[] nodes = this.possibleMatchingNodesSet.values;
		for (int i = 0, l = nodes.length; i < l; i++) {
			ASTNode node = (ASTNode) nodes[i];
			if (node == null)
				continue;
			result.append("\nPOSSIBLE_MATCH: "); //$NON-NLS-1$
			//node.print(0, result);
			if (DLTKCore.DEBUG) {
				System.err.println("TODO: Add node print..."); //$NON-NLS-1$
			}
		}
		return result.toString();
	}
}
