/*******************************************************************************
 * Copyright (c) 2000, 2005 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 API and implementation
 *******************************************************************************/
package org.eclipse.core.internal.indexing;

/**
 * An IndexAnchor provides a place to hang index-wide information in a fixed spot, especially
 * since the root node may change due to a root node split.
 */

class IndexAnchor extends IndexedStoreObject {

	public static final int SIZE = 32;
	public static final int TYPE = 1;

	protected static final int RootNodeAddressOffset = 2;
	protected static final int RootNodeAddressLength = 4;

	protected static final int NumberOfEntriesOffset = 14;
	protected static final int NumberOfEntriesLength = 4;

	protected Field numberOfEntriesField;
	protected int numberOfEntries;

	protected Field rootNodeAddressField;
	protected ObjectAddress rootNodeAddress;

	/** 
	 * Constructs a new index anchor from nothing.
	 */
	public IndexAnchor() {
		super();
		numberOfEntries = 0;
		rootNodeAddress = ObjectAddress.Null;
	}

	/** 
	 * Constructs a new index anchor from a field read from the store.  Used by the factory.
	 */
	public IndexAnchor(Field f, ObjectStore store, ObjectAddress address) throws ObjectStoreException {
		super(f, store, address);
	}

	/**
	 * Sets the fields definitions.  Done after the contents are set.
	 */
	private void setFields(Field f) {
		rootNodeAddressField = f.subfield(RootNodeAddressOffset, RootNodeAddressLength);
		numberOfEntriesField = f.subfield(NumberOfEntriesOffset, NumberOfEntriesLength);
	}

	/**
	 * Places the contents of the fields into the buffer.
	 * Subclasses should implement and call super.
	 */
	protected void insertValues(Field f) {
		super.insertValues(f);
		setFields(f);
		numberOfEntriesField.put(numberOfEntries);
		rootNodeAddressField.put(rootNodeAddress);
	}

	/**
	 * Places the contents of the buffer into the fields.
	 * Subclasses should implement and call super.
	 */
	protected void extractValues(Field f) throws ObjectStoreException {
		super.extractValues(f);
		setFields(f);
		numberOfEntries = numberOfEntriesField.getInt();
		rootNodeAddress = new ObjectAddress(rootNodeAddressField.get());
	}

	/**
	 * Returns the minimum size of this object's instance -- including its type field.
	 * Subclasses should override.
	 */
	protected int getMinimumSize() {
		return SIZE;
	}

	/**
	 * Returns the required type of this class of object.
	 * Subclasses must override.
	 */
	protected int getRequiredType() {
		return TYPE;
	}

	/**
	 * Returns a printable representation of this object.
	 */
	public String toString() {
		StringBuffer b = new StringBuffer();
		b.append("Anchor("); //$NON-NLS-1$
		b.append(numberOfEntries);
		b.append(","); //$NON-NLS-1$
		b.append(rootNodeAddress);
		b.append(")"); //$NON-NLS-1$
		return b.toString();
	}

	/**
	 * Processes the notification that an entry was inserted.
	 */
	void entryInserted(IndexNode node) {
		if (node.isLeaf()) {
			numberOfEntries++;
			setChanged();
		}
	}

	/**
	 * Processes the notification by a leaf node that an entry was removed.
	 */
	void entryRemoved(IndexNode node) {
		if (node.isLeaf()) {
			numberOfEntries--;
			setChanged();
		}
	}

	/**
	 * Sets the root node address.  Set when root node is initialized or split.
	 */
	void setRootNodeAddress(ObjectAddress rootNodeAddress) {
		this.rootNodeAddress = rootNodeAddress;
		setChanged();
	}

	/**
	 * This method returns a cursor set to the first entry in the index whose key 
	 * is greater than or equal to the key provided.  To set a cursor to the beginning 
	 * of the index use a key of zero length.
	 */
	void find(byte key[], IndexCursor cursor) throws IndexedStoreException {
		if (rootNodeAddress.isNull()) {
			cursor.reset();
		} else {
			IndexNode rootNode = acquireNode(rootNodeAddress);
			rootNode.find(key, cursor);
			rootNode.release();
		}
	}

	/**
	 * This method returns a cursor set to the first entry in the index.
	 */
	void findFirstEntry(IndexCursor cursor) throws IndexedStoreException {
		if (rootNodeAddress.isNull()) {
			cursor.reset();
		} else {
			IndexNode rootNode = acquireNode(rootNodeAddress);
			rootNode.findFirstEntry(cursor);
			rootNode.release();
		}
	}

	/**
	 * Insert an entry into an index.  
	 */
	void insert(byte[] key, byte[] value) throws IndexedStoreException {
		if (rootNodeAddress.isNull()) {
			IndexNode rootNode = new IndexNode(this.address);
			try {
				store.insertObject(rootNode);
			} catch (ObjectStoreException e) {
				throw new IndexedStoreException(IndexedStoreException.IndexNodeNotCreated, e);
			}
			rootNodeAddress = rootNode.getAddress();
		}
		IndexNode rootNode = acquireNode(rootNodeAddress);
		rootNode.insertEntry(key, value);
		rootNode.release();
	}
}
