// ---------------------------------------------------------------------------
// - Instance.cpp                                                            -
// - afnix engine - afnix instance 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-2006 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Cons.hpp"
#include "Vector.hpp"
#include "Method.hpp"
#include "Closure.hpp"
#include "Runnable.hpp"
#include "Instance.hpp"
#include "Exception.hpp"

namespace afnix {

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

  // the instance eval quarks
  static const long QUARK_THIS   = String::intern ("this");
  static const long QUARK_META   = String::intern ("meta");
  static const long QUARK_MUTE   = String::intern ("mute");
  static const long QUARK_SUPER  = String::intern ("super");
  static const long QUARK_PRESET = String::intern ("preset");

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

  // create a default instance

  Instance::Instance (void) {
    p_iset  = nilp;
    p_meta  = nilp;
    d_ctmta = false;
    p_super = nilp;
    d_ctsup = false;
  }

  // create an instance wih a meta class

  Instance::Instance (Class* meta) {
    p_iset  = nilp;
    p_meta  = nilp;
    d_ctmta = false;
    p_super = nilp;
    d_ctsup = false;
    setmeta (meta, false);
  }

  // destroy this instance

  Instance::~Instance (void) {
    if (p_iset != nilp) p_iset->reset ();
    Object::dref (p_iset);
    Object::dref (p_meta);
    Object::dref (p_super);
  }

  // return the class name

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

  // make this instance a shared object

  void Instance::mksho (void) {
    if (p_shared != nilp) return;
    Object::mksho ();
    if (p_meta  != nilp) p_meta->mksho ();
    if (p_super != nilp) p_super->mksho ();
    if (p_iset  != nilp) p_iset->mksho  (); 
  }

  // set the instance meta class

  Object* Instance::setmeta (Object* object, const bool flag) {
    // check for a class
    Class* meta = dynamic_cast <Class*> (object);
    if ((meta == nilp) && (object != nilp)) {
      throw Exception ("type-error", "invalid object to set as meta class",
		       Object::repr (object));
    }
    // check for constant
    if (d_ctmta == true) {
      unlock ();
      throw Exception ("const-error", "const violation with meta class");
    }
    // set the meta class
    Object::iref (meta);
    Object::dref (p_meta);
    p_meta  = meta;
    d_ctmta = flag;
    unlock ();
    return meta;
  }

  // set the instance super value

  Object* Instance::setsuper (Object* object, const bool flag) {
    wrlock ();
    if (d_ctsup == true) {
      unlock ();
      throw Exception ("const-error", "const violation with super member");
    }
    Object::iref (object);
    Object::dref (p_super);
    p_super = object;
    d_ctsup = flag;
    unlock ();
    return object;
  }

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

  // create a new object in a generic way

  Object* Instance::mknew (Vector* argv) {
    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();

    // check 0 argument
    if (argc == 0) return new Instance;
    // illegal arguments
    throw Exception ("argument-error", "too many arguments with instance");
  }

  // return true if the given quark is defined

  bool Instance::isquark (const long quark, const bool hflg) const {
    // check for local quarks
    if (quark == QUARK_THIS)   return true;
    if (quark == QUARK_META)   return true;
    if (quark == QUARK_MUTE)   return true;
    if (quark == QUARK_SUPER)  return true;
    if (quark == QUARK_PRESET) return true;
    // lock and check in the instance set
    rdlock ();
    if ((p_iset != nilp) && (p_iset->exists (quark) == true)) {
      unlock ();
      return true;
    }
    // check in the class
    if ((p_meta != nilp) && (p_meta->isquark (quark, hflg) == true)) {
      unlock ();
      return true;
    }
    // check in the super instance if hierarchical
    if (p_super != nilp) {
      bool result = hflg ? p_super->isquark (quark, hflg) : false;
      unlock ();
      return result;
    }
    // check in the instance base object
    bool result = hflg ? Object::isquark (quark, hflg) : false;
    unlock ();
    return result;
  }

  // preset the instance with a set of arguments

  Object* Instance::pdef (Runnable* robj, Nameset* nset, Cons* args) {
    wrlock ();
    // clean any remaining localset
    if (p_iset != nilp) {
      p_iset->reset ();
      Object::dref (p_iset);
      p_iset= nilp;
    }
    // check if we have a meta class
    if (p_meta == nilp) {
      unlock ();
      return nilp;
    }
    // evaluate the arguments in the original nameset
    Cons* carg = nilp;
    try {
      carg = Cons::mkcons (robj, nset, args);
    } catch (...) {
      delete carg;
      unlock ();
      throw;
    }
    // create the instance local set and bind this
    Object::iref (p_iset = new Localset);
    p_iset->symcst (QUARK_THIS, this);
    // bind the default symbols from the meta class
    try {
      const Qarray& mdata = p_meta->getmdata ();
      if (mdata.length () != 0) {
	long len = mdata.length ();
	for (long i = 0; i < len; i++)
	  p_iset->symdef (mdata.get (i), (Object*) nilp);
      }
    } catch (...) {
      unlock ();
      throw;
    }
    // evaluate the preset method
    try {
      // get the form
      Object* iobj = p_meta->find (QUARK_PRESET);
      Object* form = (iobj == nilp) ? nilp : iobj->eval (robj, nset);
      // compute the result
      Object* result = nilp;
      if (form != nilp) {
	p_iset->setparent (nset);
	result = form->apply (robj, p_iset, carg);
	p_iset->setparent ((Nameset*) nilp);
      } 
      robj->post     (result);
      // clean the localset
      Object::iref   (this);
      p_iset->remove (QUARK_THIS);
      Object::tref   (this);
      // clean evaluated args and unlock
      delete carg;
      unlock ();
      return result;
    } catch (...) {
      // clean the localset
      Object::iref   (this);
      p_iset->remove (QUARK_THIS);
      Object::tref   (this);
      // clear evaluated args and unlock
      delete carg;
      unlock ();
      throw;
    }
  }

  // mute the instance with a set of arguments
  
  Object* Instance::mute (Runnable* robj, Nameset* nset, Cons* args) {
    wrlock ();
    try {
      // trivial check first
      if ((args == nilp) || (args->length () < 1)) {
	throw Exception ("argument-error",
			 "missing or too many arguments with mute");
      }
      // try to get the class
      Object* car = args->getcar ();
      Object* obj = (car == nilp) ? nilp : car->eval (robj, nset);
      Class*  cls = dynamic_cast <Class*> (obj);
      if (cls == nilp) {
	throw Exception ("type-error", "invalid object to set as meta class",
			 Object::repr (obj));
      }
      // set the meta class
      setmeta (cls, false);
      // call the preset method with the remaining arguments
      Cons* cdr      = args->getcdr ();
      Object* result = pdef (robj, nset, cdr);
      robj->post (result);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // create a const object by quark

  Object* Instance::cdef (Runnable* robj, Nameset* nset, const long quark,
			  Object* object) {
    // check for reserved quark
    if (quark == QUARK_META)  return setmeta (object, true);
    // check for the super quark
    if (quark == QUARK_SUPER) return setsuper (object, true);
    wrlock ();
    try {
      // create a local instance set if needed
      if (p_iset == nilp) {
	Object::iref (p_iset = new Localset);
	if ((p_shared != nilp) && (p_iset != nilp)) p_iset->mksho ();
      }
      // get the object from the localset
      Object* result = p_iset->cdef (robj, nset, quark, object);
      robj->post (result);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // create an object by quark

  Object* Instance::vdef (Runnable* robj, Nameset* nset, const long quark,
			  Object* object) {
    // check for reserved quark
    if (quark == QUARK_META)  return setmeta  (object, false);
    // check for super
    if (quark == QUARK_SUPER) return setsuper (object, false);
    wrlock ();
    try {
      // create a local instance set if needed
      if (p_iset == nilp) {
	Object::iref (p_iset = new Localset);
	if ((p_shared != nilp) && (p_iset!= nilp)) p_iset->mksho ();
      }
      // look in the instance local set
      if (p_iset != nilp) {
	Object* iobj = p_iset->find (quark);
	if (iobj != nilp) {
	  Object* result = iobj->vdef (robj, nset, object);
	  robj->post (result);
	  unlock ();
	  return result;
	}
      }
      // look in the class
      if (p_meta != nilp) {
	Object* cobj = p_meta->find (quark);
	if (cobj != nilp) {
	  Object* result = cobj->vdef (robj, nset, object);
	  robj->post (result);
	  unlock ();
	  return result;
	}
      }
      // bind locally
      if (p_iset != nilp) {
	Object* result = p_iset->vdef (robj, nset, quark, object);
	robj->post (result);
	unlock ();
	return result;
      }
      throw Exception ("instance-error", "cannot access local instance set");
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // evaluate an instance member

  Object* Instance::eval (Runnable* robj, Nameset* nset, const long quark) {
    rdlock ();
    // check for super 
    if (quark == QUARK_SUPER) {
      Object* result = p_super;
      robj->post (result);
      unlock ();
      return result;
    }
    // check for meta and set-meta
    if (quark == QUARK_META) {
      Object* result = p_meta;
      robj->post (result);
      unlock ();
      return result;
    }
    if (quark == QUARK_MUTE) {
      unlock ();
      return new Method (quark, this, false);
    }
    unlock ();
    try {
      wrlock ();
      // create a local instance set if needed
      if (p_iset == nilp) {
	Object::iref (p_iset = new Localset);
	if ((p_shared != nilp) && (p_iset != nilp)) p_iset->mksho ();
      }
      unlock ();
      rdlock ();
      // check in the instance local set
      if (p_iset != nilp) {
	Object* obj = p_iset->find (quark);
	if (obj != nilp) {
	  // evaluate the result object
	  Object* result = obj->eval (robj, nset);
	  // check for a closure
	  if (dynamic_cast <Closure*> (result) == nilp) {
	    robj->post (result);
	    unlock ();
	    return result;
	  }
	  // default to a method
	  Object* method = new Method (result, this);
	  robj->post (method);
	  unlock ();
	  return method;
	}
      }
      // check in the class
      if (p_meta != nilp) {
	Object* obj = p_meta->find (quark);
	if (obj != nilp) {
	  // evaluate the result object
	  Object* result = obj->eval (robj, nset);
	  // check for a closure
	  if (dynamic_cast <Closure*> (result) == nilp) {
	    robj->post (result);
	    unlock ();
	    return result;
	  }
	  // default to a method
	  Object* method = new Method (result, this);
	  robj->post (method);
	  unlock ();
	  return method;
	}
      }
      // check in the super instance
      if (p_super != nilp) {
	// evaluate the result object
	Object* result = p_super->eval (robj, nset, quark);
	// check for a closure
	if (dynamic_cast <Closure*> (result) == nilp) {
	  robj->post (result);
	  unlock ();
	  return result;
	}
	// default to a mathod
	Object* method = new Method (result, this);
	robj->post (method);
	unlock ();
	return method;
      }
      // last resort is a method
      Object* method = new Method (quark, this, true);
      robj->post (method);
      unlock ();
      return method;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // apply an object with a set of arguments by quark

  Object* Instance::apply (Runnable* robj, Nameset* nset, const long quark,
			   Cons* args) {
    // check for set-meta quark
    if (quark == QUARK_MUTE) {
      wrlock ();
      try {
	Object* result = mute (robj, nset, args);
	robj->post (result);
	unlock ();
	return result;
      } catch (...) {
	unlock ();
	throw;
      }
    }
    // evaluate and apply
    Object* obj = eval (robj, nset, quark);
    return apply (robj, nset, obj, args);
  }
    
  // apply an object with an object within this instance

  Object* Instance::apply (Runnable* robj, Nameset* nset, Object* object,
			   Cons* args) {
    // basic reject - as usual
    if (object == nilp) return nilp;
    // create a local instance set if needed
    wrlock ();
    Localset* lset  = nilp;
    try {
      if (p_iset == nilp) {
	Object::iref (p_iset = new Localset);
	if ((p_shared != nilp) && (p_iset != nilp)) p_iset->mksho ();
      }
      // rebind the local set before the call
      lset = new Localset (p_iset);
      lset->setparent (nset);
      lset->symcst    (QUARK_THIS, this);

    } catch (...) {
      unlock ();
      throw;
    }
    // let's make the call
    try {
      Object* result = object->apply (robj, lset, args);
      robj->post (result);
      lset->reset ();
      delete lset;
      unlock ();
      return result;
    } catch (...) {
      lset->reset ();
      delete lset;
      unlock ();
      throw;
    }
  }
}
