// ---------------------------------------------------------------------------
// - Localset.cpp                                                            -
// - afnix engine - local 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 "Symbol.hpp"
#include "Runnable.hpp"
#include "Localset.hpp"
#include "Exception.hpp"

namespace afnix {

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

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

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

  // create a new local set

  Localset::Localset (void) {
    p_ptbl = new NameTable;
    p_stbl = nilp;
    Object::iref (p_ptbl);
  }

  // create a new local set but use only the primary table

  Localset::Localset (Localset* lset) {
    if (lset == nilp) {
      p_ptbl = new NameTable;
      p_stbl = nilp;
    } else {
      p_ptbl = lset->p_ptbl;
      p_stbl = new NameTable;
      symcst (QUARK_THIS, this);
    }
    Object::iref (p_ptbl);
    Object::iref (p_stbl);
  }

  // destroy this local set

  Localset::~Localset (void) {
    // protect us
    Object::iref (this);
    // destroy everything
    Object::dref (p_ptbl);
    Object::dref (p_stbl);
  }

  // return the class name

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

  // reset the secondary name table / may be the first

  void Localset::reset (void) {
    wrlock ();
    try {
      // protect us before reset
      Object::iref (this);
      if (p_stbl != nilp) {
	p_stbl->reset ();
	Object::tref  (this);
	unlock ();
	return;
      }
      if (p_ptbl != nilp) p_ptbl->reset ();
      Object::tref (this);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // create a child local set

  Nameset* Localset::dup (void) {
    rdlock ();
    try {
      Nameset* result = new Localset;
      result->setparent (this);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // bind a new object by quark

  void Localset::bind (const long quark, Object* object) {
    wrlock ();
    try {
      if (p_stbl != nilp)
	p_stbl->add (quark, object);
      else
	p_ptbl->add (quark, object);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

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

  bool Localset::exists (const long quark) const {
    rdlock ();
    try {
      if (p_stbl != nilp) return p_stbl->exists (quark);
      bool result = p_ptbl->exists (quark);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // find an object in this local set by quark

  Object* Localset::find (const long quark) const {
    rdlock ();
    try {
      Object* obj = nilp;
      if (p_stbl != nilp) {
	obj = p_stbl->get (quark);
	if (obj == nilp) obj = p_ptbl->get (quark);
      } else {
	obj = p_ptbl->get (quark);
      }
      if (obj != nilp) {
	unlock ();
	return obj;
      }
      if (p_parent != nilp) {
	Object* result = p_parent->find (quark);
	unlock ();
	return result;
      }
      unlock ();
      return nilp;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // remove an object by quark in this localset

  void Localset::remove (const long quark) {
    wrlock ();
    try {
      Object::iref (this);
      if ((p_stbl != nilp) && (p_stbl->exists (quark) == true)) {
	p_stbl->remove (quark);
	Object::tref (this);
	unlock ();
	return;
      }
      p_ptbl->remove (quark);
      Object::tref (this);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // set a constant object by quark

  Object* Localset::cdef (Runnable* robj, Nameset* nset, const long quark,
			  Object* object) {
    wrlock ();
    try {
      Object* obj = nilp;
      if (p_stbl != nilp) {
	obj = p_stbl->get (quark);
	if (obj == nilp) obj = p_ptbl->get (quark);
      } else {
	obj = p_ptbl->get (quark);
      }
      if (obj != nilp) {
	obj->cdef (robj, nset, object);
	robj->post (object);
	unlock ();
	return object;
      }
      // the object is not found - create a symbol and bind it
      Symbol* sym = new Symbol (quark, object);
      sym->setconst (true);
      if (p_stbl != nilp)
	p_stbl->add (quark, sym);
      else
	p_ptbl->add (quark, sym);
      robj->post (object);
      unlock ();
      return object;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // set an object by quark

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

  // evaluate an object in the current nameset by quark

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