/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002-2005
*      Sleepycat Software.  All rights reserved.
*
* $Id: DBIN.java,v 1.56 2004/12/22 14:11:35 linda Exp $
*/

package com.sleepycat.je.tree;

import java.nio.ByteBuffer;
import java.util.Comparator;

import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.dbi.CursorImpl;
import com.sleepycat.je.dbi.DatabaseId;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.DbConfigManager;
import com.sleepycat.je.dbi.MemoryBudget;
import com.sleepycat.je.log.LogEntryType;
import com.sleepycat.je.log.LogException;
import com.sleepycat.je.log.LoggableObject;

/**
 * A DBIN represents an Duplicate Bottom Internal Node in the JE tree.
 */
public final class DBIN extends BIN implements LoggableObject {
    private static final String BEGIN_TAG = "<dbin>";
    private static final String END_TAG = "</dbin>";
    
    /**
     * Full key for this set of duplicates.
     */
    private Key dupKey;

    public DBIN() {
        super();
        dupKey = new Key();
    }

    public DBIN(DatabaseImpl db,
                Key identifierKey,
                int maxEntriesPerNode,
                Key dupKey,
                int level) {
        super(db, identifierKey, maxEntriesPerNode, level);
        this.dupKey = dupKey;
    }

    /**
     * Create a new DBIN.  Need this because we can't call newInstance()
     * without getting a 0 node.
     */
    protected IN createNewInstance(Key identifierKey,
                                   int maxEntries, 
                                   int level) {
        return new DBIN(getDatabase(),
                        identifierKey,
                        maxEntries,
                        dupKey,
                        level);
    }

    /* Duplicates have no mask on their levels. */
    protected int generateLevel(DatabaseId dbId, int newLevel) {
        return newLevel;
    }

    /**
     * Return the comparator function to be used for DBINs.  This is
     * the user defined duplicate comparison function, if defined.
     */
    public final Comparator getKeyComparator() {
        return getDatabase().getDuplicateComparator();
    }

    /**
     * Return the key for this duplicate set.
     */
    public Key getDupKey() {
        return dupKey;
    }

    /**
     * Get the key (dupe or identifier) in child that is used to locate
     * it in 'this' node.
     */
    public Key getChildKey(IN child)
        throws DatabaseException {

        return child.getIdentifierKey();
    }

    /*
     * A DBIN uses the dupTree key in its searches.
     */
    public Key selectKey(Key mainTreeKey, Key dupTreeKey) {
        return dupTreeKey;
    }

    /**
     * Return the key for navigating through the duplicate tree.
     */
    public Key getDupTreeKey() {
        return getIdentifierKey();
    }

    /**
     * Return the key for navigating through the main tree.
     */
    public Key getMainTreeKey() {
        return dupKey;
    }

    /**
     * @return true if this node is a duplicate-bearing node type, false
     * if otherwise.
     */
    public boolean containsDuplicates() {
        return true;
    }
    
    /**
     * @return the log entry type to use for bin delta log entries.
     */
    public LogEntryType getBINDeltaType() {
        return LogEntryType.LOG_DUP_BIN_DELTA;
    }

    public BINReference createReference() {
        return new DBINReference(getNodeId(), getDatabase().getId(),
                                 getIdentifierKey(), dupKey);
    }

    /**
     * Count up the memory usage attributable to this node alone.
     */
    protected long computeMemorySize() {
        long size = super.computeMemorySize();
        /* XXX Need to update size when changing the dupKey.
        if (dupKey != null && dupKey.getKey() != null) {
            size += MemoryBudget.byteArraySize(dupKey.getKey().length);
        }
        */
        return size;
    }

    /* Called once at environment startup by MemoryBudget. */
    public static long computeOverhead(DbConfigManager configManager) 
        throws DatabaseException {

        /* 
	 * Overhead consists of all the fields in this class plus the
	 * entry arrays in the IN class.
         */
        return MemoryBudget.DBIN_FIXED_OVERHEAD +
               IN.computeArraysOverhead(configManager);
    }
    
    protected long getMemoryOverhead(MemoryBudget mb) {
        return mb.getDBINOverhead();
    }

    /* 
     * A DBIN cannot be the ancestor of any IN.
     */
    protected boolean canBeAncestor(boolean targetContainsDuplicates) {
        return false;
    }

    /*
     * A DBIN's children are always evictable because they are only LNs.
     */
    protected boolean childrenAreEvictable() {
        return true;
    }

    /**
     * The following four methods access the correct fields in a
     * cursor depending on whether "this" is a BIN or DBIN.  For
     * BIN's, the CursorImpl.index and CursorImpl.bin fields should be
     * used.  For DBIN's, the CursorImpl.dupIndex and CursorImpl.dupBin
     * fields should be used.
     */
    BIN getCursorBIN(CursorImpl cursor) {
        return cursor.getDupBIN();
    }

    int getCursorIndex(CursorImpl cursor) {
        return cursor.getDupIndex();
    }

    void setCursorBIN(CursorImpl cursor, BIN bin) {
        cursor.setDupBIN((DBIN) bin);
    }

    void setCursorIndex(CursorImpl cursor, int index) {
        cursor.setDupIndex(index);
    }

    /*
     * Depth first search through a duplicate tree looking for an LN that
     * has nodeId.  When we find it, set location.bin and index and return
     * true.  If we don't find it, return false.
     *
     * No latching is performed.
     */
    boolean matchLNByNodeId(TreeLocation location, long nodeId)
	throws DatabaseException {

        for (int i = 0; i < getNEntries(); i++) {
	    LN ln = (LN) fetchTarget(i);
		if (ln != null) {
		    if (ln.getNodeId() == nodeId) {
			location.bin = this;
			location.index = i;
			location.lnKey = new Key(ln.getData());
		    location.childLsn = getLsn(i);
			return true;
		}
	    }
        }

	return false;
    }

    /*
     * DbStat support.
     */
    void accumulateStats(TreeWalkerStatsAccumulator acc) {
	acc.processDBIN(this, new Long(getNodeId()), getLevel());
    }

    public String beginTag() {
        return BEGIN_TAG;
    }

    public String endTag() {
        return END_TAG;
    }

    /**
     * For unit test support:
     * @return a string that dumps information about this IN, without
     */
    public String dumpString(int nSpaces, boolean dumpTags) {
        StringBuffer sb = new StringBuffer();
        sb.append(TreeUtils.indent(nSpaces));
        sb.append(beginTag());
        sb.append('\n');

        sb.append(TreeUtils.indent(nSpaces+2));
        sb.append("<dupkey>");
        sb.append((dupKey == null ? "" : dupKey.toString()));
        sb.append("</dupkey>");
        sb.append('\n');

        sb.append(super.dumpString(nSpaces, false));

        sb.append(TreeUtils.indent(nSpaces));
        sb.append(endTag());
        return sb.toString();
    }

    /*
     * Logging support
     */

    /**
     * @see LoggableObject#getLogType
     */
    public LogEntryType getLogType() {
        return LogEntryType.LOG_DBIN;
    }

    /**
     * @see LoggableObject#getLogSize
     */
    public int getLogSize() {
        int size = super.getLogSize();          // ancestors
        size += dupKey.getLogSize();           // identifier key
        return size;
    }

    /**
     * @see LoggableObject#writeToLog
     */
    public void writeToLog(ByteBuffer logBuffer) {

        // ancestors
        super.writeToLog(logBuffer); 

        // identifier key
        dupKey.writeToLog(logBuffer);
    }

    /**
     * @see BIN#readFromLog
     */
    public void readFromLog(ByteBuffer itemBuffer)
        throws LogException {

        // ancestors 
        super.readFromLog(itemBuffer);

        // identifier key
        dupKey.readFromLog(itemBuffer);
    }
    
    /**
     * DBINS need to dump their dup key
     */
    protected void dumpLogAdditional(StringBuffer sb) {
        super.dumpLogAdditional(sb);
        dupKey.dumpLog(sb, true);
    }

    public String shortClassName() {
        return "DBIN";
    }
}
