// ---------------------------------------------------------------------------
// - Globalset.cpp                                                           -
// - afnix engine - global set class implementation                          -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - This program  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.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2008 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Cons.hpp"
#include "Symbol.hpp"
#include "Runnable.hpp"
#include "Globalset.hpp"
#include "Exception.hpp"

namespace afnix {

  // -------------------------------------------------------------------------
  // - private section                                                       -
  // -------------------------------------------------------------------------

  // this global set quarks
  static const long QUARK_THIS = String::intern (".");

  // -------------------------------------------------------------------------
  // - class section                                                         -
  // -------------------------------------------------------------------------

  // create a new global set

  Globalset::Globalset (void) {
    p_table = new QuarkTable;
    symcst (QUARK_THIS, this);
  }

  // create a new global set with a parent nameset

  Globalset::Globalset (Nameset* nset) {
    p_table = new QuarkTable;
    setparent (nset);
    symcst (QUARK_THIS, this);
  }

  // destroy this global set

  Globalset::~Globalset (void) {
    // protect ourself
    Object::iref (this);
    // delete everything
    delete p_table;
  }

  // return the class name

  String Globalset::repr (void) const {
    return "Globalset";
  }

  // reset this global set

  void Globalset::reset (void) {
    wrlock ();
    try {
      // protect us before reset
      Object::iref (this);
      // reset the table and parent
      if (p_table != nilp) p_table->reset ();
      setparent (nilp);
      // release reference count
      Object::tref (this);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // create a child global set

  Nameset* Globalset::dup (void) {
    rdlock ();
    Nameset* result = new Globalset;
    result->setparent (this);
    unlock ();
    return result;
  }

  // bind a new object by quark

  void Globalset::bind (const long quark, Object* object) {
    wrlock ();
    try {
      p_table->add (quark, object);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return true if the quark exists in the global set

  bool Globalset::exists (const long quark) const {
    rdlock ();
    try {
      bool result = p_table->exists (quark);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // find an object by quark but do not evaluate

  Object* Globalset::find (const long quark) const {
    // get the read lock
    rdlock ();
    // protect against any exception
    try {
      // find the object
      Object* obj = p_table->get (quark);
      if (obj != nilp) {
	unlock ();
	return obj;
      }
      Object* result = (p_parent == nilp) ? nilp : p_parent->find (quark);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // remove an object by quark in this global set

  void Globalset::remove (const long quark) {
    wrlock ();
    try {
      p_table->remove (quark);
      unlock ();
    } catch(...) {
      unlock ();
      throw;
    }
  }

  // -------------------------------------------------------------------------
  // - object section                                                        -
  // -------------------------------------------------------------------------

  // set an object as a const object by quark

  Object* Globalset::cdef (Runnable* robj, Nameset* nset, const long quark,
			   Object* object) {
    // get the write lock
    wrlock ();
    try {
      // try first to find the object with a write lock
      Object* obj = p_table->get (quark);
      if (obj != nilp) {
	obj->cdef (robj, nset, object);
	robj->post (object);
	unlock ();
	return object;
      }
      // the object is not found - create a symbol reference and bind it
      Symbol* sym = new Symbol (quark, object);
      sym->setconst (true);
      p_table->add (quark, sym);
      robj->post (object);
      unlock ();
      return object;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // set an object to this object by quark

  Object* Globalset::vdef (Runnable* robj, Nameset* nset, const long quark,
			   Object* object) {
    // get the write lock
    wrlock ();
    try {
      // try first to find the object
      Object* obj = p_table->get (quark);
      if (obj != nilp) {
	obj->vdef (robj, nset, object);
	robj->post (object);
	unlock ();
	return object;
      }
      // the object is not found - create a symbol reference and bind it
      Symbol* sym = new Symbol (quark, object);
      p_table->add (quark, sym);
      robj->post (object);
      unlock ();
      return object;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // evaluate an object in the current nameset by quark

  Object* Globalset::eval (Runnable* robj, Nameset* nset, const long quark) {
    // get the read lock
    rdlock ();
    try {
      // try first to find the object
      Object* obj = find (quark);
      // eval or error
      if (obj != nilp) {
	Object* result = obj->eval (robj, nset);
	robj->post (result);
	unlock ();
	return result;
      }
      // not found
      throw Exception ("eval-error", "unbound symbol", String::qmap (quark));
    } catch (...) {
      unlock ();
      throw;
    }
  }
}
