// ---------------------------------------------------------------------------
// - 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-2009 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Cons.hpp"
#include "Vector.hpp"
#include "Method.hpp"
#include "Closure.hpp"
#include "Multiset.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;
    reset ();
  }

  // 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;
    reset   ();
    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";
  }

  // reset this instance

  void Instance::reset (void) {
    wrlock ();
    try {
      Object::iref (this);
      if (p_iset != nilp) p_iset->reset ();
      Object::dref (p_iset);
      Object::iref (p_iset = new Localset);
      p_iset->symcst (QUARK_THIS, this);
      Object::tref (this);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // 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->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 ();
    Cons* carg = nilp;
    try {
      // clean any remaining localset
      reset ();
      // check if we have a meta class
      if (p_meta == nilp) {
	unlock ();
	return nilp;
      }
      // evaluate the arguments in the original nameset
      carg = Cons::eval (robj, nset, args);
      // get the preset form
      Object* iobj = p_meta->get (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 (nilp);
      } 
      robj->post (result);
      // clean evaluated args and unlock
      delete carg;
      unlock ();
      return result;
    } catch (...) {
      p_iset->setparent (nilp);
      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 quarks
    if (quark == QUARK_META)  return setmeta (object, true);
    if (quark == QUARK_SUPER) return setsuper (object, true);
    // lock and process
    arlock ();
    try {
      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 quarks
    if (quark == QUARK_META)  return setmeta  (object, false);
    if (quark == QUARK_SUPER) return setsuper (object, false);
    // lock and process
    arlock ();
    try {
      // check in the instance localset
      Object* iobj = p_iset->get (quark);
      if (iobj != nilp) {
	Object* result = iobj->vdef (robj, nset, object);
	robj->post (result);
	unlock ();
	return result;
      }
      // check in the class object
      if (p_meta != nilp) {
	Object* cobj = p_meta->get (quark);
	if (cobj != nilp) {
	  Object* result = cobj->vdef (robj, nset, object);
	  robj->post (result);
	  unlock ();
	  return result;
	}
      }
      // bind locally
      Object* result = p_iset->vdef (robj, nset, quark, object);
      robj->post (result);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // evaluate an instance member

  Object* Instance::eval (Runnable* robj, Nameset* nset, const long quark) {
    arlock ();
    try {
      // check for super 
      if (quark == QUARK_SUPER) {
	Object* result = p_super;
	robj->post (result);
	unlock ();
	return result;
      }
      // check for meta
      if (quark == QUARK_META) {
	Object* result = p_meta;
	robj->post (result);
	unlock ();
	return result;
      }
      // check for mute
      if (quark == QUARK_MUTE) {
	unlock ();
	return new Method (quark, this, false);
      }
      // check in the instance localset
      Object* iobj = p_iset->get (quark);
      if (iobj != nilp) {
	// evaluate the result object
	Object* result = iobj->eval (robj, nset);
	// check for not a closure
	if (dynamic_cast <Closure*> (result) == nilp) {
	  robj->post (result);
	  unlock ();
	  return result;
	}
	// bind to a method
	Object* method = new Method (result, this);
	robj->post (method);
	unlock ();
	return method;
      }
      // check in the class
      if (p_meta != nilp) {
	Object* cobj = p_meta->get (quark);
	if (cobj != nilp) {
	  // evaluate the result object
	  Object* result = cobj->eval (robj, nset);
	  // check for not a closure
	  if (dynamic_cast <Closure*> (result) == nilp) {
	    robj->post (result);
	    unlock ();
	    return result;
	  }
	  // bind 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 not a closure
	if (dynamic_cast <Closure*> (result) == nilp) {
	  robj->post (result);
	  unlock ();
	  return result;
	}
	// bind to a method
	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 mute 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) {
    // check for a valid object
    if (object == nilp) return nilp;
    // the apply multiset
    Multiset* mset = nilp;
    arlock ();
    try {
      // create a new multiset
      Object::iref (mset = new Multiset (nset, p_iset));
      unlock ();
    } catch (...) {
      mset->reset  ();
      Object::dref (mset);
      unlock ();
      throw;
    }
    try {
      // let's make the call
      Object* result = object->apply (robj, mset, args);
      // post and clean
      robj->post (result);
      mset->reset  ();
      Object::dref (mset);
      return result;
    } catch (...) {
      mset->reset  ();
      Object::dref (mset);
      throw;
    } 
  }
}
