/*
 * e4storage.cpp --
 *
 *	Implementation of the e4_Storage class defined in e4graph.h.
 *
 *	Authors: Jacob Levy and Jean-Claude Wippler.
 *		 jyl@best.com	jcw@equi4.com
 *
 * 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.
 */

#include "e4graphimpl.h"

/*
 * This instance of e4_Storage can be used (by assigning it to fields or
 * variables of type e4_Storage) to remove remaining references to instances
 * that you want to discard. Especially useful for removing remaining
 * references in malloc'ed memory.
 */

e4_Storage const invalidStorage;

/*
 * Static methods to return version of e4Graph implemented.
 */

const char *
e4_Storage::version()
{
    return E4GRAPH_VERSION;
}

int
e4_Storage::major_version()
{
    return E4GRAPH_MAJOR;
}

int
e4_Storage::minor_version()
{
    return E4GRAPH_MINOR;
}

e4_ReleaseStatus
e4_Storage::release_status()
{
    return E4GRAPH_RELEASESTATUS;
}

int
e4_Storage::release_iteration()
{
    return E4GRAPH_RELEASEITER;
}

/*
 * Compute the version string for the version of e4Graph that wrote
 * a specific storage.
 */

const char *
e4_Storage::storage_version(const char *fname,
			    const char *drivername)
{
    static char verbuf[128];
    static char statstrings[] = {'a','b','.','p'};
    int majver, minver, reliter;
    e4_ReleaseStatus relstat;

    if (!storage_version_info(fname,
			      drivername, 
			      majver,
			      minver,
			      relstat,
			      reliter)) {
	return NULL;
    }
    sprintf(verbuf,
	    "%d.%d%c%d",
	    majver,
	    minver,
	    statstrings[(int) relstat],
	    reliter);
    return (const char *) verbuf;
}

/*
 * Return numeric constants representing the version of e4Graph that
 * wrote a specific storage.
 */

bool
e4_Storage::storage_version_info(const char *fname,
				 const char *drivername,
				 int &maj,
				 int &min,
				 e4_ReleaseStatus &rs,
				 int &ri)
{
    return e4_StorageImpl::GetStorageVersionInfo(fname,
						 drivername,
						 maj,
						 min,
						 rs,
						 ri);
}

/*
 * Default constructor:
 */

e4_Storage::e4_Storage()
    : e4_RefCount()
{}

/*
 * Constructor which assigns a value to the the implementation pointer.
 */

e4_Storage::e4_Storage(e4_StorageImpl *ip)
    : e4_RefCount(ip)
{}

/*
 * Copying constructor:
 */

e4_Storage::e4_Storage(const e4_Storage &referrer)
    : e4_RefCount(referrer)
{}

/*
 * Copying constructor that's given an e4_RefCount:
 */

e4_Storage::e4_Storage(const e4_RefCount &referrer)
    : e4_RefCount(referrer)
{
    if ((impl != NULL) && (impl->Kind() != E4_RKSTORAGE)) {
	(void) e4_RefCount::operator=(invalidStorage);
    }
}

/*
 * Assignment operator:
 */

e4_Storage & e4_Storage::operator=(const e4_Storage &referrer)
{
    return (e4_Storage &) e4_RefCount::operator=(referrer);
}

/*
 * Constructor that chooses a concrete representation for the underlying
 * implementation.
 */

e4_Storage::e4_Storage(const char *nm, const char *drivername)
    : e4_RefCount()
{
    impl = e4_StorageImpl::GetStorage(nm,
				      drivername,
				      -1,
				      E4_SPDEFAULTMASK);
    if (impl != NULL) {
	impl->IncrRefCount();
    }
}

/*
 * Constructor as above that also allows the user to set the storage state.
 */

e4_Storage::e4_Storage(const char *nm, const char *drivername, int state)
    : e4_RefCount()
{
    impl = e4_StorageImpl::GetStorage(nm,
				      drivername,
				      state, 
				      E4_SPDEFAULTMASK);
    if (impl != NULL) {
	impl->IncrRefCount();
    }
}

/*
 * Constructor as above that also sets the storage permissions.
 */

e4_Storage::e4_Storage(const char *nm,
		       const char *drivername,
		       int state,
		       int sp)
    : e4_RefCount()
{
    impl = e4_StorageImpl::GetStorage(nm, drivername, state, sp);
    if (impl != NULL) {
	impl->IncrRefCount();
    }
}

/*
 * Set and get the states for this storage.
 */

int
e4_Storage::SetState(int statemask) const
{
    if (impl == NULL) {
	return -1;
    }
    return ((e4_StorageImpl *) impl)->SetState(statemask);
}

int
e4_Storage::GetState() const
{
    if (impl == NULL) {
	return -1;
    }
    return ((e4_StorageImpl *) impl)->GetState();
}

/*
 * Get the storage permissions. Setting is only supported during construction.
 */

int
e4_Storage::GetPermissions() const
{
    if (impl == NULL) {
	return 0;
    }
    return ((e4_StorageImpl *) impl)->GetPermissions();
}

/*
 * Commit the storage now.
 */

bool
e4_Storage::Commit() const
{
    if (impl != NULL) {
	return ((e4_StorageImpl *) impl)->Commit();
    }
    return false;
}

/*
 * Copy this storage to another storage.
 */

bool
e4_Storage::CopyTo(e4_Storage otherStorage, bool forceCommit) const
{
    if (!IsValid() || !otherStorage.IsValid()) {
	return false;
    }
    if (!((e4_StorageImpl *) impl)->
		CopyTo((e4_StorageImpl *) otherStorage.impl)) {
	return false;
    }
    if (forceCommit) {
	return otherStorage.Commit();
    }
    return true;
}

/*
 * Destroy this storage. If auto-commit is set, first commit.
 */

bool
e4_Storage::Delete()
{
    if (impl != NULL) {
	if ((((e4_StorageImpl *) impl)->GetState() & E4_COMMITATCLOSE) ==
	    E4_COMMITATCLOSE) {
	    ((e4_StorageImpl *) impl)->Commit();
	}
	((e4_StorageImpl *) impl)->Destroy();
	impl->DecrRefCount();
    }
    impl = NULL;

    return true;
}

/*
 * Get and set the root node.
 */

bool
e4_Storage::GetRootNode(e4_Node &n) const
{
    e4_NodeImpl *nnip;

    if (impl == NULL) {
	return false;
    }

    nnip = ((e4_StorageImpl *) impl)->GetRootNode();
    if ((nnip == NULL) || (!nnip->IsValid())) {
	return false;
    }

    e4_Node nn(nnip);

    n = nn;

    return true;
}

bool
e4_Storage::SetRootNode(e4_Node n) const
{
    e4_Storage hisstorage;

    if (!n.IsValid() || (impl == NULL) || (!n.GetStorage(hisstorage)) ||
	(*this != hisstorage)) {
	return false;
    }

    ((e4_StorageImpl *) impl)->SetRootNode((e4_NodeImpl *) n.impl);

    return true;
}

/*
 * Given a unique ID, obtain the referenced node.
 */

bool
e4_Storage::GetNodeFromID(e4_NodeUniqueID nid, e4_Node &n) const
{
    e4_NodeImpl *nnip;

    if (impl == NULL) {
	return false;
    }
    nnip = ((e4_StorageImpl *) impl)->FindNode(nid.GetUniqueID());
    if (nnip == NULL) {
	return false;
    }
    e4_Node nn(nnip);

    n = nn;

    return true;
}

/*
 * Given a unique ID, obtain the vertex referenced by the ID.
 */

bool
e4_Storage::GetVertexFromID(e4_VertexUniqueID vid, e4_Vertex &v) const
{
    e4_VertexImpl *vvip;

    if (impl == NULL) {
	return false;
    }
    vvip = ((e4_StorageImpl *) impl)->GetVertex(vid.GetUniqueID());
    if (vvip == NULL) {
	return false;
    }

    e4_Vertex vv(vvip);

    v = vv;

    return true;
}

/*
 * Given a unique ID for a vertex, get the next vertex after it in the same
 * node. The e4_DetachChoice argument determines which kind of vertex we
 * are looking for: detached, attached or both.
 */

bool
e4_Storage::FindNextVertex(int vertexID,
			   e4_VisitMethod vm,
			   int vf,
			   int nameID, 
			   int nodeID,
			   int parentID,
			   e4_VertexType typeID,
			   e4_DetachChoice dc,
			   e4_Vertex &f) const
{
    e4_VertexImpl *fi;

    if (impl == NULL) {
	return false;
    }
    fi = ((e4_StorageImpl *) impl)->FindNextVertex(vertexID,
						   vm,
						   vf,
						   nameID,
						   nodeID,
						   parentID,
						   typeID,
						   dc);
    if (fi == NULL) {
	return false;
    }

    e4_Vertex ff(fi);

    f = ff;

    return true;
}

/*
 * Given a unique ID for a node, find the next one after it in
 * the storage. The e4_DetachChoice argument determines which kind of
 * node we are looking for: detached (including the root), attached (+ root),
 * or both.
 */

bool
e4_Storage::FindNextNode(int nodeID, e4_DetachChoice dc, e4_Node &n) const
{
    e4_NodeImpl *ni;

    if (impl == NULL) {
	return false;
    }
    while (1) {
	ni = ((e4_StorageImpl *) impl)->FindNextNode(nodeID);
	if (ni == NULL) {
	    return false;
	}

	/*
	 * Whether or not the node is detached, create the wrapper.
	 * This ensures that if the node is not one we want, its
	 * reference count and referenced status is correctly maintained.
	 */

	e4_Node nn(ni);

	/*
	 * Is this node what we are searching for?
	 */

	switch (dc) {
	case E4_DCDETACHED:
	    if (ni->IsDetached()) {
		n = nn;
		return true;
	    }
	    break;
	case E4_DCATTACHED:
	    if (!ni->IsDetached() || ni->IsRoot()) {
		n = nn;
		return true;
	    }
	    break;
	case E4_DCBOTH:
	    n = nn;
	    return true;
	}

	/*
	 * The current node is not one we want to return, so set up
	 * the next iteration by updating the starting nodeID.
	 */

	nodeID = ni->GetUniqueID();
    }

    /*
     * Some compilers insist on this, so..
     */

    return false;
}

/*
 * Is this storage stable?
 */

bool
e4_Storage::IsStable() const
{
    if (impl == NULL) {
	return false;
    }
    return ((e4_StorageImpl *) impl)->IsStable();
}

void
e4_Storage::MarkUnstable() const
{
    if (impl != NULL) {
	((e4_StorageImpl *) impl)->MarkUnstable();
    }
}

/*
 * Get the name of this storage.
 */

const char *
e4_Storage::GetName() const
{
    if (impl == NULL) {
	return NULL;
    }
    return ((e4_StorageImpl *) impl)->GetName();
}

/*
 * Get the ID of the storage driver used by this storage.
 */

const char *
e4_Storage::GetDriver() const
{
    if (impl == NULL) {
	return NULL;
    }
    return ((e4_StorageImpl *) impl)->GetDriver();
}

bool
e4_Storage::CreateDetachedNode(e4_Node &n) const
{
    e4_NodeImpl *nip;

    if (impl == NULL) {
	return false;
    }
    nip = ((e4_StorageImpl *) impl)->CreateDetachedNode();
    if (nip == NULL) {
	return false;
    }
    
    e4_Node nn(nip);

    n = nn;

    if (((e4_StorageImpl *) impl)->HasCallbacks(E4_ECADDNODE)) {
	((e4_StorageImpl *) impl)->CauseEventInternal(E4_ECADDNODE, nip, NULL);
    }

    return true;
}

bool
e4_Storage::CreateDetachedVertex(const char *nm, e4_Node n,
				 e4_Vertex &v) const
{
    e4_VertexImpl *vip;

    if (impl == NULL) {
	return false;
    }
    vip = ((e4_StorageImpl *) impl)->CreateDetachedVertex(nm, 
	(e4_NodeImpl *) n.impl);
    if (vip == NULL) {
	return false;
    }

    e4_Vertex vv(vip);

    v = vv;

    if (((e4_StorageImpl *) impl)->HasCallbacks(E4_ECADDVERTEX)) {
	((e4_StorageImpl *) impl)->CauseEventInternal(E4_ECADDVERTEX,
						      vip,
						      NULL);
    }

    return true;
}

bool
e4_Storage::CreateDetachedVertex(const char *nm, int i, e4_Vertex &v) const
{
    e4_VertexImpl *vip;

    if (impl == NULL) {
	return false;
    }
    vip = ((e4_StorageImpl *) impl)->CreateDetachedVertex(nm, i);
    if (vip == NULL) {
	return false;
    }

    e4_Vertex vv(vip);

    v = vv;

    if (((e4_StorageImpl *) impl)->HasCallbacks(E4_ECADDVERTEX)) {
	((e4_StorageImpl *) impl)->CauseEventInternal(E4_ECADDVERTEX,
						      vip,
						      NULL);
    }

    return true;
}

bool
e4_Storage::CreateDetachedVertex(const char *nm, double d, e4_Vertex &v)
	const
{
    e4_VertexImpl *vip;

    if (impl == NULL) {
	return false;
    }
    vip = ((e4_StorageImpl *) impl)->CreateDetachedVertex(nm, d);
    if (vip == NULL) {
	return false;
    }

    e4_Vertex vv(vip);

    v = vv;

    if (((e4_StorageImpl *) impl)->HasCallbacks(E4_ECADDVERTEX)) {
	((e4_StorageImpl *) impl)->CauseEventInternal(E4_ECADDVERTEX,
						      vip,
						      NULL);
    }

    return true;
}

bool
e4_Storage::CreateDetachedVertex(const char *nm, const char *s, e4_Vertex &v)
	const
{
    e4_VertexImpl *vip;

    if (impl == NULL) {
	return false;
    }
    vip = ((e4_StorageImpl *) impl)->CreateDetachedVertex(nm, s);
    if (vip == NULL) {
	return false;
    }
    
    e4_Vertex vv(vip);

    v = vv;

    if (((e4_StorageImpl *) impl)->HasCallbacks(E4_ECADDVERTEX)) {
	((e4_StorageImpl *) impl)->CauseEventInternal(E4_ECADDVERTEX, 
						      vip,
						      NULL);
    }

    return true;
}

bool
e4_Storage::CreateDetachedVertex(const char *nm, const void *b, int nb,
				 e4_Vertex &v) const
{
    e4_VertexImpl *vip;

    if (impl == NULL) {
	return false;
    }
    vip = ((e4_StorageImpl *) impl)->CreateDetachedVertex(nm, b, nb);
    if (vip == NULL) {
	return false;
    }

    e4_Vertex vv(vip);

    v = vv;

    if (((e4_StorageImpl *) impl)->HasCallbacks(E4_ECADDVERTEX)) {
	((e4_StorageImpl *) impl)->CauseEventInternal(E4_ECADDVERTEX,
						      vip,
						      NULL);
    }

    return true;
}

bool
e4_Storage::CreateDetachedVertex(const char *nm,
				 const e4_Value &vv,
				 e4_Vertex &v)
	const
{
    if (impl == NULL) {
	return false;
    }
    switch (vv.vertexType) {
    case E4_VTNODE:
	return CreateDetachedVertex(nm, vv.n, v);
    case E4_VTINT:
	return CreateDetachedVertex(nm, vv.u.i, v);
    case E4_VTDOUBLE:
	return CreateDetachedVertex(nm, vv.u.d, v);
    case E4_VTSTRING:
	return CreateDetachedVertex(nm, vv.u.s, v);
    case E4_VTBINARY:
	return CreateDetachedVertex(nm, vv.u.b.bytes, vv.u.b.nbytes, v);
    default:
	return false;
    }
}

/*
 * Get a value for a statistic being collected for this storage.
 */

bool
e4_Storage::GetStatistic(e4_Space sp, e4_SpaceStat st, int &v) const
{
    if (impl == NULL) {
	return false;
    }
    return ((e4_StorageImpl *) impl)->GetStatistic(sp, st, v);
}

/*
 * Intern a name.
 */

int
e4_Storage::InternName(const char *nm) const
{
    if (impl == NULL) {
	return E4_VERTEXNOTFOUND;
    }
    return ((e4_StorageImpl *) impl)->InternName(nm, true);
}

/*
 * Get a node given its node ID:
 */

e4_NodeImpl *
e4_Storage::GetNode(int nodeID) const
{
    if (!IsValid()) {
	return NULL;
    }
    return ((e4_StorageImpl *) impl)->FindOrCreateNode(nodeID);
}

/*
 * Get the name of a vertex given the ID of the name.
 */

const char *
e4_Storage::GetName(int nameID) const
{
    if (!IsValid()) {
	return NULL;
    }
    return ((e4_StorageImpl *) impl)->NameFromNameID(nameID);
}

/*
 * Return the storage implementation for this storage:
 */

e4_StorageImpl *
e4_Storage::GetStorageImpl() const
{
    if (!IsValid()) {
	return NULL;
    }
    return (e4_StorageImpl *) impl;
}

/*
 * Do a GC.
 */

void
e4_Storage::DoGC() const
{
    if (IsValid()) {
	((e4_StorageImpl *) impl)->DoGC();
    }
}

/*
 * Is a GC needed?
 */

bool
e4_Storage::NeedsGC() const
{
    if (!IsValid()) {
	return false;
    }
    return ((e4_StorageImpl *) impl)->NeedsGC();
}

/*
 * Return the kind identifier for this instance.
 */

e4_RefKind
e4_Storage::Kind() const
{
    return E4_RKSTORAGE;
}

/*
 * Callback facility: Declare or remove callbacks for specific kinds of
 * events.
 */

bool
e4_Storage::DeclareCallback(int eventCode,
			    e4_CallbackFunction fn,
			    void *cd)
{
    if (!IsValid()) {
	return false;
    }
    if (((e4_StorageImpl *) impl)->AddCallback(eventCode, (void *) fn, cd)) {
	return true;
    }
    return false;
}

bool
e4_Storage::DeleteCallback(int eventCode,
			   e4_CallbackFunction fn,
			   void *cd)
{
    if (!IsValid()) {
	return false;
    }
    if (((e4_StorageImpl *) impl)->DelCallback(eventCode, (void *) fn, cd)) {
	return true;
    }
    return false;
}

bool
e4_Storage::CauseEvent(int eventCode, const e4_RefCount &r, void *csdata)
{
    if (!IsValid() ||
	!r.IsValid() ||
	(eventCode < E4_FIRSTUSERDEFINEDEVENTCODE) ||
	(eventCode > E4_LASTUSERDEFINEDEVENTCODE)) {
	return false;
    }
    if (!((e4_StorageImpl *) impl)->HasCallbacks(eventCode)) {
	return true;
    }
    if (!((e4_StorageImpl *) impl)->CauseEvent(eventCode, r, csdata)) {
	return false;
    }
    return true;
}

/*
 * Timestamp facility:
 */

int
e4_Storage::GetTimeStamp() const
{
    if (!IsValid()) {
	return -1;
    }
    return ((e4_StorageImpl *) impl)->GetTimeStamp();
}

int
e4_Storage::GetTimeStampFor(int em) const
{
    if (!IsValid()) {
	return -1;
    }
    return ((e4_StorageImpl *) impl)->GetTimeStampFor(em);
}

bool
e4_Storage::HasOccurredSince(int timestamp, int em) const
{
    if (!IsValid()) {
	return false;
    }
    if ((timestamp < 0) || (timestamp > GetTimeStamp())) {
	return false;
    }
    return ((e4_StorageImpl *) impl)->HasOccurredSince(timestamp, em);
}

/*
 * Facility for managing user defined event codes:
 */

static int userDefinedEventCodes = 0;

bool
e4_Storage::DefineEventCode(int &eventcode)
{
    int i;

    for (i = E4_FIRSTUSERDEFINEDEVENTCODE;
	 i <= E4_LASTUSERDEFINEDEVENTCODE;
	 i++) {
	if ((userDefinedEventCodes & (1 << i)) == 0) {
	    userDefinedEventCodes |= (1 << i);
	    eventcode = (1<<i);
	    return true;
	}
    }
    return false;
}

bool
e4_Storage::UndefineEventCode(int eventcode)
{
    if ((eventcode < E4_FIRSTUSERDEFINEDEVENTCODE) ||
	(eventcode > E4_LASTUSERDEFINEDEVENTCODE)) {
	return false;
    }
    if ((userDefinedEventCodes & eventcode) == 0) {
	return false;
    }
    userDefinedEventCodes &= (~(eventcode));
    return true;
}

bool
e4_Storage::IsEventCodeDefined(int eventcode)
{
    if ((eventcode < E4_FIRSTUSERDEFINEDEVENTCODE) ||
	(eventcode > E4_LASTUSERDEFINEDEVENTCODE)) {
	return false;
    }
    if ((userDefinedEventCodes & eventcode) == 0) {
	return false;
    }
    return true;
}
