/*
 * com.e4graph.Storage:
 *
 *	This file contains the Java implementation of a class to wrap
 *	around e4Graph storage objects.
 *
 * Copyright (c) 2000-2003, JYL Software Inc.
 * 
 * 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, EVEN IF
 * JYL SOFTWARE INC. IS MADE AWARE OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.e4graph;

public final class Storage
{
    /*
     * Static section to load the library and initialize the natives:
     */

    static {
	System.loadLibrary("e4java");
	initNativeIDs();
    }

    /*
     * Native method to initialize everything so that the native code
     * can be used.
     */

    static native private void initNativeIDs();

    /**
     * COMMITATCLOSE may be set in the state of a Storage, meaning
     * that the storage should be committed when closed (on by default).
     */
    public static final int COMMITATCLOSE	= (1<<0);

    /**
     * AUTOCOMMIT may be set in the state of a Storage, meaning that
     * the storage is auto-committed from time to time by the e4Graph
     * runtime system (off by default).
     */
    public static final int AUTOCOMMIT		= (1<<1);

    /**
     * OPENGC may be set in the state of a Storage, meaning that a GC
     * should be performed (as necessary) when the storage is opened
     * (on by default).
     */
    public static final int OPENGC		= (1<<2);

    /**
     * GCBEFORECOMMIT may be set in the state of a Storage, meaning that
     * a GC is performed on this storage before it is committed (off by
     * default).
     */
    public static final int GCBEFORECOMMIT	= (1<<3);

    /**
     * AUTOGC may be set in the state of a Storage, meaning that the
     * e4Graph runtime performs GCs on this storage as needed (on by
     * default).
     */
    public static final int AUTOGC		= (1<<4);

    /**
     * BIGPREALLOC may be set in the state of a Storage, meaning that
     * big areas are pre-allocated to hold newly created nodes and
     * vertices (off by default).
     */
    public static final int BIGPREALLOC		= (1<<5);

    /**
     * COMPACTATCLOSE may be set in the state of a Storage, meaning
     * that the storag is compacted to its smallest possible on-disk
     * size when it is closed (off by default).
     */
    public static final int COMPACTATCLOSE	= (1<<7);

    /**
     * DEFAULTSTATE denotes the default state of a Storage, consisting
     * of COMMITATCLOSE, OPENGC and AUTOGC.
     */
    public static final int DEFAULTSTATE	= 
	(COMMITATCLOSE | OPENGC | AUTOGC);

    /**
     * This value represents the storageIndex of an invalid storage.
     */
    public static final int INVALID		= -1;

    /**
     * This is the name of the default driver to use to manage the
     * persistence of this storage.
     */
    public static final String METAKIT		= "Metakit 2.4";

    /*
     * Fields to hold the public data for this storage: its name
     * and driver.
     */

    private String name;
    private String driver;

    /*
     * This field holds the key under which the actual e4Graph
     * storage object is cached. This implementation relies on
     * the key being 32 bits only.
     */

    private int storageIndex;

    /*
     * This field holds the generation number for this storage. This
     * is a monotonically increasing counter -- no two storages are
     * created with the same generation. This allows recyclable cache
     * space to be efficiently reused without worrying about stray
     * left over Java objects pointing at storages stored into reused
     * spaces.
     */

    private int generation;

    /*
     * Method to create a storage:
     */

    private native static int createStorage(String name,
					    String driver,
					    int state)
	throws StorageCreationException;

    /*
     * Method to retrieve the generation of a storage:
     */

    private native static int getStorageGeneration(int index)
	throws StorageIsNotOpenException;

    /*
     * Method to retrieve the name of a storage given its index.
     */

    private native static String getNameOfStorage(int index, int gen)
	throws StorageIsNotOpenException;

    /*
     * Method to retrieve the driver name of a storage given its index.
     */

    private native static String getDriverOfStorage(int index, int gen)
	throws StorageIsNotOpenException;

    /**
     * Private constructor, for use by native code only.
     */

    private Storage(int si, int g)
    {
	storageIndex = si;
	generation = g;
	try {
	    name = getNameOfStorage(si, g);
	    driver = getDriverOfStorage(si, g);
	} catch (StorageIsNotOpenException e) {
	    storageIndex = INVALID;
	    generation = -1;
	}
    }

    /**
     * Default constructor, constructs an invalid storage.
     */
    public Storage()
    {
	storageIndex = INVALID;
	generation = -1;
	name = null;
	driver = null;
    }

    /**
     * Constructor that uses the given name and driver to construct
     * a storage.
     */
    public Storage(String name, String driver) 
    {
	try {
	    storageIndex = createStorage(name, driver, DEFAULTSTATE);
	    generation = getStorageGeneration(storageIndex);
	} catch (StorageCreationException e) {
	    storageIndex = INVALID;
	    generation = -1;
	} catch (StorageIsNotOpenException e) {
	    storageIndex = INVALID;
	    generation = -1;
	}
	if (storageIndex == INVALID) {
	    this.name = null;
	    this.driver = null;
	} else {
	    this.name = name;
	    this.driver = driver;
	}
    }

    /**
     * Constructor that in addition to a name and driver for the storage
     * also accepts an initial configuration state.
     */
    public Storage(String name, String driver, int configuration)
    {
	try {
	    storageIndex = createStorage(name, driver, configuration);
	    generation = getStorageGeneration(storageIndex);
	} catch (StorageCreationException e) {
	    storageIndex = INVALID;
	    generation = -1;
	} catch (StorageIsNotOpenException e) {
	    storageIndex = INVALID;
	    generation = -1;
	}
	if (storageIndex == INVALID) {
	    this.name = null;
	    this.driver = null;
	} else {
	    this.name = name;
	    this.driver = driver;
	}
    }

    /**
     * Create an iterator that iterates over all storages that are
     * accessible in Java.
     */
    public static StorageIterator getStorageIterator() {
	return new StorageIterator();
    }

    /**
     * Retrieves a Storage object for a currently open storage, given
     * its storageIndex.
     */
    public static native Storage getStorage(int index, int generation)
	throws StorageIsNotOpenException;

    /**
     * Retrieve the storageIndex for this storage.
     */
    public int getIndex() {return storageIndex;}

    /**
     * Retrieve the storageIndex for this storage.
     */
    public int getStorageIndex() {return storageIndex;}

    /**
     * Retrieve the generation index for this storage.
     */

    public int getGeneration() {return generation;}

    /**
     * Retrieves the version string for the e4Graph library in use.
     */
    public static native String version();

    /**
     * A storage is valid if the underlying e4Graph storage is valid and
     * its storageIndex is not the constant INVALID.
     */
    public boolean isValid()
    {
	return isValid1(storageIndex, generation);
    }
    private native boolean isValid1(int index, int gen);

    /**
     * Two storages are equal if their storageIndex values are the same.
     */
    public boolean equals(Storage otherStorage)
    {
	if ((storageIndex == otherStorage.storageIndex) &&
	    (generation == otherStorage.generation)) {
	    return true;
	}
	return false;
    }

    /**
     * Set the storage state to a new state, with changes taking effect
     * immediately and affecting subsequent behavior.
     */
    public int setState(int statemask)
    {
	return setState1(storageIndex, generation, statemask);
    }

    /**
     * Retrieve the current storage state.
     */
    public int getState()
    {
	return getState1(storageIndex, generation);
    }
    private native int setState1(int index, int gen, int statemask);
    private native int getState1(int index, int gen);

    /**
     * Commit any unsaved changes in this storage to persistent storage.
     */
    public boolean commit()
    {
	return commit1(storageIndex, generation);
    }
    private native boolean commit1(int index, int gen);

    /**
     * Close the storage, potentially committing any unsaved changes.
     */

    public void close()
    {
	close1(storageIndex, generation);
    }
    private native void close1(int index, int gen);

    /**
     * Copy the contents of this storage (including any uncommitted
     * changes) to the other storage, and if forceCommit is true,
     * force a commit action on the other storage after the copy.
     */
    public boolean copyTo(Storage otherStorage, boolean forceCommit)
    {
	return copyTo1(storageIndex, generation,
		       otherStorage.storageIndex, otherStorage.generation,
		       forceCommit);
    }
    private native boolean copyTo1(int fromidx, int fromgen,
				   int toidx, int togen,
				   boolean forceCommit);

    /**
     * Delete the persistent form of this storage.
     */
    public boolean delete()
    {
	return delete1(storageIndex, generation);
    }
    private native boolean delete1(int index, int gen);

    /**
     * Retrieve a Node representing the currently designated root node
     * of this storage.
     */
    public Node getRoot()
    {
	return getRoot1(storageIndex, generation);
    }

    /**
     * Set the currently designated root node of this storage to the e4Graph
     * node denoted by the given newRoot.
     */
    public boolean setRoot(Node newRoot)
    {
	return setRoot1(storageIndex,
			generation,
			newRoot.getIndex(),
			newRoot.getStorageIndex());
    }
    private native Node getRoot1(int index, int gen);
    private native boolean setRoot1(int index, int gen,
				    int rootindex, int rsindex);

    /**
     * Are there uncommitted changes in this storage?
     */
    public boolean isStable()
    {
	return isStable1(storageIndex, generation);
    }

    /**
     * Mark this storage as having uncommitted changes that have not yet been
     * saved to persistent storage.
     */
    public void markUnstable()
    {
	markUnstable1(storageIndex, generation);
    }
    private native boolean isStable1(int index, int gen);
    private native void markUnstable1(int index, int gen);

    /**
     * Retrieve the String name of this storage.
     */
    public String name() {return this.name;};

    /**
     * Retrieve the String for the driver used to manage persistence for
     * this storage.
     */
    public String driver() {return this.driver;};

    /**
     * Create a new detached node in this storage and return the Node
     * object representing it.
     */
    public Node getNode()
    {
	return getNode1(storageIndex, generation);
    }
    public Vertex getVertex(String name, Node n)
    {
	return getNodeVertex1(storageIndex, generation,
			      name,
			      n.getIndex(), n.getStorageIndex(), 
			      n.getGeneration());
    }

    /**
     * Create a new detached vertex with the given name and integer value i,
     * and return the Vertex object representing it.
     */
    public Vertex getVertex(String name, int i)
    {
	return getIntVertex1(storageIndex, generation, name, i);
    }

    /**
     * Create a new detached vertex with the given name and the double value d,
     * and return the Vertex object representing it.
     */
    public Vertex getVertex(String name, double d)
    {
	return getDoubleVertex1(storageIndex, generation, name, d);
    }

    /**
     * Create a new detached vertex with the given name and the string value s,
     * and return the Vertex object representing it.
     */
    public Vertex getVertex(String name, String s)
    {
	return getStringVertex1(storageIndex, generation, name, s);
    }

    /**
     * Create a new detached vertex with the given name and the binary value
     * bytes, and return the Vertex object representing it.
     */
    public Vertex getVertex(String name, byte [] bytes)
    {
	return getBytesVertex1(storageIndex, generation, name, bytes);
    }

    private native Node getNode1(int index, int gen);
    private native Vertex getNodeVertex1(int index, int gen,
					 String name,
					 int nodeidx, int nsidx, int nsg);
    private native Vertex getIntVertex1(int index, int gen, 
					String name, int i);
    private native Vertex getDoubleVertex1(int index, int gen,
					   String name, double d);
    private native Vertex getStringVertex1(int index, int gen,
					   String name, String s);
    private native Vertex getBytesVertex1(int index, int gen,
					  String name, byte [] bytes);

    /**
     * Do a garbage collection on this storage now.
     */
    public void doGC()
    {
	doGC1(storageIndex, generation);
    }
    private native void doGC1(int index, int gen);

    /**
     * Is a garbage collection needed for this storage (it is needed
     * if there may be detached nodes and vertices which are not
     * referenced by the user program).
     */
    public boolean needsGC()
    {
	return needsGC1(storageIndex, generation);
    }
    private native boolean needsGC1(int index, int gen);

    /*
     * TODO: Callback mechanism.
     */

    /*
     * TODO: Iterator mechanism.
     */
}
