/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: BTree.java
 *
 * Copyright (c) 2009, Static Free Software. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.database.geometry.btree;

import java.io.*;
import java.util.*;
import com.sun.electric.database.geometry.btree.unboxed.*;
import com.sun.electric.database.geometry.btree.CachingPageStorage.CachedPage;

/**
 *   Internal use only; kind of a hack.  This is just a "parser"
 *   for the page format.
 *
 *   Possible feature: store the buckets of an interior node
 *   internally as a simple balanced tree (a splay tree?).  The
 *   System.arraycopy()'s are scaling very poorly as the page size
 *   increases.
 *
 *     - once we do this, we can probably afford to move to 32Kb
 *       pages, which will give us great performance on nearly any
 *       filesystem or storage device, even RAID storage (which has
 *       huge block sizes).
 */
abstract class NodeCursor
    <K extends Serializable & Comparable,
     V extends Serializable,
     S extends Serializable> {

    protected              boolean      dirty = false;
    protected              CachingPageStorage  ps;
    protected static final int          SIZEOF_INT = 4;
    protected        final BTree<K,V,S> bt;
    protected              CachedPage   cp = null;

    protected NodeCursor(BTree<K,V,S> bt) {
        this.bt = bt;
        this.ps = bt.ps;
    }
    public abstract void initBuf(CachedPage cp, int parent, boolean isRightMost);
    protected abstract void setNumBuckets(int num);
    public void setBuf(CachedPage cp) {
        assert !dirty;
        this.cp = cp;
    }
    public CachedPage getCachedPage() { return cp; }
    public void       forgetCachedPage() { cp = null; }
    public void writeBack() {
        dirty = false;
        cp.setDirty();
    }

    public int getParent() { return UnboxedInt.instance.deserializeInt(cp.getBuf(), 0*SIZEOF_INT); }
    public void setParent(int pageid) { bt.ui.serializeInt(pageid, getBuf(), 0*SIZEOF_INT); }

    /**
     *  This method writes back the first half of the node's contents,
     *  deposits the second half on a new page, and (if key!=null)
     *  writes the least key beneath the right half into key[key_ofs];
     *  it then returns the number of values appearing anywhere below
     *  the left page.  After returning the cursor will be pointing at
     *  the right half.
     */
    public int split(byte[] key, int key_ofs, int splitPoint) {
        assert isFull();
        int endOfBuf = endOfBuf();

        int ret = 0;
        for(int i=0; i<splitPoint; i++)
            ret += getNumValsBelowBucket(i);

        // chop off our second half, point our parent at the page-to-be, and write back
        setNumBuckets(splitPoint);
        boolean wasRightMost = isRightMost();
        setRightMost(false);
        writeBack();

        if (key!=null)
            getKey(splitPoint, key, key_ofs);

        // move the second half of our entries to the front of the block, and write back
        byte[] oldbuf = cp.getBuf();
        initBuf(ps.getPage(ps.createPage(), false), getParent(), wasRightMost);
        setNumBuckets(getMaxBuckets()-splitPoint);
        scoot(oldbuf, endOfBuf, splitPoint);
        writeBack();
        return ret;
    }

    public boolean isFull() { return getNumBuckets() >= getMaxBuckets(); }
    public int getPageId() { return cp.getPageId(); }
    public byte[] getBuf() { return cp.getBuf(); }

    /**
     *  Each node has a number of buckets, separated by keys; keys
     *  appear between buckets as well as before the first bucket and
     *  after the last.  All values in a bucket are greater than or
     *  equal to the key to the left of the bucket and strictly less
     *  than the key to the right.
     *
     *  Leaf nodes have a real key immediately before each entry --
     *  this is the entry's actual key -- plus an additional imaginary
     *  key after the last child.
     *
     *  Interior nodes have an imaginary key before the first node, a
     *  real key between each pair of nodes, and an imaginary key
     *  after the last node.
     *
     *  Imaginary keys are either infinitely large or infinitely
     *  small, depending on which end of the sequence they appear on.
     *
     *  This numbering scheme might seem strange, but it really helps
     *  avoid fencepost bugs -- I tried several conventions before
     *  settling on this one.
     *
     *  Key zero is to the left of bucket zero, and the last key is to
     *  the right of the last child (and has an index one greater than
     *  it).
     */
    public abstract int getNumBuckets();

    public abstract int getMaxBuckets();

    /**
     *  Compares the key at position keynum to the key represented by
     *  the bytes provided.  Same semantics as Comparable.compareTo().
     *  To simplify other codepaths, if keynum<0, the return value is
     *  always positive and if keynum>=getNumBuckets()+1 the return value is
     *  always negative.
     */
    public abstract int compare(byte[] key, int key_ofs, int keynum);

    public abstract boolean isLeafNode();

    /** i is the position of the leftmost key which is less than or equal to the key we are testing */
    public int search(byte[] key, int key_ofs) {
        int left = -1;
        int right = getNumBuckets();
        while(left+1<right) {
            assert compare(key, key_ofs, left) >= 0;
            int i = (left+right)/2;
            int comp = compare(key, key_ofs, i);
            if      (comp==0)  return i;
            else if (comp>0)   left = i;
            else if (comp<0)   right = i;
        }
        return left;
    }

    protected abstract int endOfBuf();

    /**
     *  Returns the least key appearing anywhere as a child of the
     *  keynum^th bucket.  Must not call getKey(0,,) on an interior
     *  node.
     */
    public abstract void getKey(int keynum, byte[] key, int key_ofs);

    /** kludge */
    protected abstract void scoot(byte[] oldbuf, int endOfBuf, int splitPoint);

    /** the total number of values stored in bucket or any descendent thereof */
    public abstract int  getNumValsBelowBucket(int bucket);

    public abstract void getSummary(int bucket, byte[] buf, int ofs);
    public abstract void getSummary(byte[] buf, int ofs);

    public boolean isRightMost() { return bt.ui.deserializeInt(getBuf(), 1*SIZEOF_INT)!=0; }
    protected void setRightMost(boolean r) { bt.ui.serializeInt(r?1:0, getBuf(), 1*SIZEOF_INT); }
}
