/* Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The  above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

package net.thauvin.j2me.googlme;

import javax.microedition.rms.*;
import java.io.*;

/**
 * An Rms based BTree index. Currently, the functionality is similar to a
 * persistent hashtable.
 */
public class RmsIndex {

    static byte[] DUMMY = new byte[0];

    RecordStore store;

    Node root;

    int n = 32;

    String cachedKey;

    Node cachedNode;

    int cachedIndex;

    /** Node helper class */
    class Node {
        int index;

        int size;

        String[] keys = new String[n + n + 1];

        String[] values = new String[n + n + 1];

        int[] children = new int[n + n + 2];

        /** Create a new Node */

        Node() throws RecordStoreException {
            index = store.addRecord(DUMMY, 0, 0);
        }

        Node(Node split) throws RecordStoreException {
            this();
            System.arraycopy(split.keys, n + 1, keys, 0, n);
            System.arraycopy(split.values, n + 1, values, 0, n);
            System.arraycopy(split.children, n + 1, children, 0, n + 1);
            size = n;
            split.size = n;
            split.store();
            store();
        }

        /** Load the node at the given index position */

        Node(int index) throws RecordStoreException {
            this.index = index;
            byte[] data = store.getRecord(index);

            if (data == null || data.length == 0)
                return;

            try {
                DataInputStream dis = new DataInputStream(
                        new ByteArrayInputStream(data));

                size = dis.readInt();

                for (int i = 0; i < size; i++) {
                    children[i] = dis.readInt();
                    keys[i] = dis.readUTF();
                    values[i] = dis.readUTF();
                }

                children[size] = dis.readInt();
            } catch (IOException e) {
                throw new RuntimeException(e.toString());
            }
        }

        public void put(String key, String value) throws RecordStoreException {

            int i;
            for (i = 0; i < size; i++) {
                int cmp = key.compareTo(keys[i]);
                if (cmp == 0) {
                    values[i] = value;
                    store();
                    return;
                } else if (cmp < 0)
                    break;
            }

            int newIndex = 0;

            if (children[i] != 0) {
                Node child = new Node(children[i]);
                child.put(key, value);

                if (child.size < n + n + 1)
                    return;

                Node split = new Node(child);
                newIndex = split.index;

                key = child.keys[n];
                value = child.values[n];
            }

            System.arraycopy(keys, i, keys, i + 1, size - i);
            System.arraycopy(values, i, values, i + 1, size - i);
            System.arraycopy(children, i + 1, children, i + 2, size - i);

            keys[i] = key;
            values[i] = value;
            children[i + 1] = newIndex;
            size++;

            if (size < n + n + 1)
                // otherwise, store will be performed by uplink
                store();
        }

        public void store() throws RecordStoreException {

            try {
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                DataOutputStream dos = new DataOutputStream(bos);
                dos.writeInt(size);
                for (int i = 0; i < size; i++) {
                    dos.writeInt(children[i]);
                    dos.writeUTF(keys[i]);
                    dos.writeUTF(values[i]);
                }
                dos.writeInt(children[size]);
                byte[] data = bos.toByteArray();
                store.setRecord(index, data, 0, data.length);
            } catch (IOException e) {
                throw new RuntimeException(e.toString());
            }
        }
    }

    /**
     * Creates a new index with the given Name
     */
    public RmsIndex(String name) throws RecordStoreException {

        store = RecordStore.openRecordStore(name, true);
        root = store.getNumRecords() == 0 ? new Node() : new Node(1);
    }

    /**
     * Returns the value for the given key
     *
     * @param key: The key
     * @return String: The value associated with the key, or null if none.
     * @throws RecordStoreException
     */
    public String get(String key) throws RecordStoreException {

        Node current = root;

        while (true) {
            int i;
            for (i = 0; i < current.size; i++) {
                int cmp = key.compareTo(current.keys[i]);
                if (cmp == 0)
                    return current.values[i];
                else if (cmp < 0)
                    break;
            }
            if (current.children[i] == 0)
                return null;
            current = new Node(current.children[i]);
        }
    }

    /**
     * Assigns the given value to the given key persistently
     *
     * @param key: The key
     * @param value: The value
     * @throws RecordStoreException
     */
    public void put(String key, String value) throws RecordStoreException {

        root.put(key, value);
        if (root.size < n + n + 1)
            return;

        // split root....

        Node left = root;
        root = new Node();

        left.index = root.index;
        root.index = 1;

        Node right = new Node(left);

        root.keys[0] = left.keys[n];
        root.values[0] = left.values[n];
        root.children[0] = left.index;
        root.children[1] = right.index;
        root.size = 1;
        root.store();
    }

    public void close() throws RecordStoreException {
        store.closeRecordStore();
    }

    public int size() throws RecordStoreException {

        Node current = root;

        return current.size;

    }

    public String get(int recordNum) throws RecordStoreException {

        Node current = root;

        return current.values[recordNum];

    }

    public String getKey(int recordNum) throws RecordStoreException {

        Node current = root;

        return current.keys[recordNum];

    }

    public void deleteAll(String name) throws RecordStoreException {

        store.closeRecordStore();
        RecordStore.deleteRecordStore(name);
        store = RecordStore.openRecordStore(name, true);
        root = store.getNumRecords() == 0 ? new Node() : new Node(1);

    }

}