/*******************************************************************************
 * 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.core.search.index;

import org.eclipse.dltk.compiler.util.HashtableOfObject;
import org.eclipse.dltk.compiler.util.SimpleLookupTable;
import org.eclipse.dltk.compiler.util.SimpleSet;
import org.eclipse.dltk.core.search.SearchPattern;
import org.eclipse.dltk.internal.core.util.SimpleWordSet;

public class MemoryIndex {

	public int NUM_CHANGES = 500; // number of separate document changes...
									// used to decide when to merge

	SimpleLookupTable docsToReferences; // document paths ->
										// HashtableOfObject(category names ->
										// set of words)

	SimpleWordSet allWords; // save space by locally interning the referenced
							// words, since an indexer can generate numerous
							// duplicates

	String lastDocumentName;

	HashtableOfObject lastReferenceTable;

	MemoryIndex() {
		this.docsToReferences = new SimpleLookupTable(7);
		this.allWords = new SimpleWordSet(7);
	}

	void addDocumentNames(String substring, SimpleSet results) {
		// assumed the disk index already skipped over documents which have been
		// added/changed/deleted
		Object[] paths = this.docsToReferences.keyTable;
		Object[] referenceTables = this.docsToReferences.valueTable;
		if (substring == null) { // add all new/changed documents
			for (int i = 0, l = referenceTables.length; i < l; i++)
				if (referenceTables[i] != null)
					results.add(paths[i]);
		} else {
			for (int i = 0, l = referenceTables.length; i < l; i++)
				if (referenceTables[i] != null
						&& ((String) paths[i]).startsWith(substring, 0))
					results.add(paths[i]);
		}
	}

	void addIndexEntry(char[] category, char[] key, String documentName) {
		HashtableOfObject referenceTable;
		if (documentName.equals(this.lastDocumentName))
			referenceTable = this.lastReferenceTable;
		else {
			// assumed a document was removed before its reindexed
			referenceTable = (HashtableOfObject) this.docsToReferences
					.get(documentName);
			if (referenceTable == null)
				this.docsToReferences.put(documentName,
						referenceTable = new HashtableOfObject(3));
			this.lastDocumentName = documentName;
			this.lastReferenceTable = referenceTable;
		}

		SimpleWordSet existingWords = (SimpleWordSet) referenceTable
				.get(category);
		if (existingWords == null)
			referenceTable.put(category, existingWords = new SimpleWordSet(1));

		existingWords.add(this.allWords.add(key));
	}

	HashtableOfObject addQueryResults(char[][] categories, char[] key,
			int matchRule, HashtableOfObject results) {
		// assumed the disk index already skipped over documents which have been
		// added/changed/deleted
		// results maps a word -> EntryResult
		Object[] paths = this.docsToReferences.keyTable;
		Object[] referenceTables = this.docsToReferences.valueTable;
		if (matchRule == (SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE)
				&& key != null) {
			nextPath: for (int i = 0, l = referenceTables.length; i < l; i++) {
				HashtableOfObject categoryToWords = (HashtableOfObject) referenceTables[i];
				if (categoryToWords != null) {
					for (int j = 0, m = categories.length; j < m; j++) {
						SimpleWordSet wordSet = (SimpleWordSet) categoryToWords
								.get(categories[j]);
						if (wordSet != null && wordSet.includes(key)) {
							if (results == null)
								results = new HashtableOfObject(13);
							EntryResult result = (EntryResult) results.get(key);
							if (result == null)
								results.put(key, result = new EntryResult(key,
										null));
							result.addDocumentName((String) paths[i]);
							continue nextPath;
						}
					}
				}
			}
		} else {
			for (int i = 0, l = referenceTables.length; i < l; i++) {
				HashtableOfObject categoryToWords = (HashtableOfObject) referenceTables[i];
				if (categoryToWords != null) {
					for (int j = 0, m = categories.length; j < m; j++) {
						SimpleWordSet wordSet = (SimpleWordSet) categoryToWords
								.get(categories[j]);
						if (wordSet != null) {
							char[][] words = wordSet.words;
							for (int k = 0, n = words.length; k < n; k++) {
								char[] word = words[k];
								if (word != null
										&& Index.isMatch(key, word, matchRule)) {
									if (results == null)
										results = new HashtableOfObject(13);
									EntryResult result = (EntryResult) results
											.get(word);
									if (result == null)
										results.put(word,
												result = new EntryResult(word,
														null));
									result.addDocumentName((String) paths[i]);
								}
							}
						}
					}
				}
			}
		}
		return results;
	}

	boolean hasChanged() {
		return this.docsToReferences.elementSize > 0;
	}

	void remove(String documentName) {
		if (documentName.equals(this.lastDocumentName)) {
			this.lastDocumentName = null;
			this.lastReferenceTable = null;
		}
		this.docsToReferences.put(documentName, null);
	}

	boolean shouldMerge() {
		return this.docsToReferences.elementSize >= NUM_CHANGES;
	}
}
