// ---------------------------------------------------------------------------
// - Class.cpp                                                               -
// - afnix engine - class 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 "Class.hpp"
#include "Method.hpp"
#include "Lexical.hpp"
#include "Boolean.hpp"
#include "Builtin.hpp"
#include "Instance.hpp"
#include "Runnable.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"

namespace afnix {

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

  // create a new class object

  Class::Class (void) {
    p_infer = nilp;
    d_ctinf = false;
    Object::iref (p_cset = new Localset);
  }

  // destroy this class object

  Class::~Class (void) {
    Object::dref (p_infer);
    Object::dref (p_cset);
  }

  // return the class name

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

  // make this class a shared object

  void Class::mksho (void) {
    if (p_shared != nilp) return;
    Object::mksho ();
    if (p_infer != nilp) p_infer->mksho ();
    if (p_cset  != nilp) p_cset->mksho  ();
  }

  // add a new data member by quark
  
  void Class::add (const long quark) {
    wrlock ();
    if (d_mdata.exists (quark) == true) {
      unlock ();
      throw Exception ("duplicate-error", "duplicate data member name", 
		       String::qmap (quark));
    }
    try {
      d_mdata.add (quark);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return the array of data members
    
  const Qarray& Class::getmdata (void) const {
    rdlock ();
    const Qarray& result = d_mdata;
    unlock ();
    return result;
  }

  // return an object in the class localset but do not evaluate
    
  Object* Class::find (const long quark) const {
    rdlock ();
    Object* result = p_cset->find (quark);
    unlock ();
    return result;
  }

  // set the class inferent object

  Object* Class::setinfer (Object* object, const bool flag) {
    // check for a class
    Class* infer = dynamic_cast <Class*> (object);
    if ((infer == nilp) && (object != nilp)) {
      throw Exception ("type-error", "invalid object to set as inferent class",
		       Object::repr (object));
    }
    return setinfer (infer, flag);
  }

  // set the class inferent value

  Object* Class::setinfer (Class* infer, const bool flag) {
    wrlock ();
    if (d_ctinf == true) {
      unlock ();
      throw Exception ("const-error", "const violation with infer member");
    }
    Object::iref (infer);
    Object::dref (p_infer);
    p_infer = infer;
    d_ctinf = flag;
    unlock ();
    return infer;
  }

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

  // the quark zone
  static const long QUARK_ZONE_LENGTH = 1;
  static QuarkZone  zone (QUARK_ZONE_LENGTH);

  // the symbol supported quarks
  static const long QUARK_INFER  = zone.intern ("infer");

  // return true if the given quark is defined

  bool Class::isquark (const long quark, const bool hflg) const {
    rdlock ();
    // check in the quark zone
    if (zone.exists(quark) == true) {
      unlock ();
      return true;
    }
    // check in the localset
    if (p_cset->exists (quark) == true) {
      unlock ();
      return true;
    }
    // check in the object
    bool result = hflg ? Object::isquark (quark, hflg) : false;
    unlock ();
    return result;
  }

  // operate this class with another object

  Object* Class::oper (t_oper type, Object* object) {
    Class* cobj = dynamic_cast <Class*> (object);
    switch (type) {
    case Object::EQL:
      if (cobj != nilp) return new Boolean (this == cobj);
      break;
    case Object::NEQ:
      if (cobj != nilp) return new Boolean (this != cobj);
      break;
    default:
      throw Exception ("operator-error", "unsupported class operator");
    }
    throw Exception ("type-error", "invalid operand with class",
		     Object::repr (object));
  }

  // create a new class symbol by quark

  Object* Class::cdef (Runnable* robj, Nameset* nset, const long quark, 
		       Object* object) {
    wrlock ();
    try {
      // check for reserved quark
      if (quark == QUARK_INFER) return setinfer  (object, true);
      // check in the localset
      Object* result = p_cset->cdef (robj, nset, quark, object);
      robj->post (result);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // create or set a new class symbol by quark

  Object* Class::vdef (Runnable* robj, Nameset* nset, const long quark, 
		       Object* object) {
    wrlock ();
    try {
      // check for reserved quark
      if (quark == QUARK_INFER) return setinfer  (object, false);
      // check in the localset
      Object* result = p_cset->vdef (robj, nset, quark, object);
      robj->post (result);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // evaluate an object member by quark

  Object* Class::eval (Runnable* robj, Nameset* nset, const long quark) {
    rdlock ();
    try {
      // look in the local set
      Object* obj = p_cset->find (quark);
      if (obj != nilp) {
	Object* result = obj->eval (robj, nset);
	robj->post (result);
	unlock ();
	return result;
      }
      // last resort is a method
      unlock ();
      return new Method (quark, this, true);
    } catch ( ...) {
      unlock ();
      throw;
    }
  }
  
  // apply this object with a set of arguments

  Object* Class::apply (Runnable* robj, Nameset* nset, Cons* args) {
    rdlock ();
    try {
      // create an instance with a class
      Instance* inst = new Instance (this);
      // loop with the inferent class
      Class* infer = p_infer;
      // check for an inferent class
      while (infer != nilp) {
	// create an inferent instance
	Instance* ii = new Instance (infer);
	// set the super instance
	ii->setsuper (inst, infer->d_ctinf);
	// here is the new result
	inst = ii;
	// get the next inferent class
	infer = infer->p_infer;
      };
      // call the preset method
      Object* result = inst->pdef (robj, nset, args);
      robj->post (inst);
      Object::cref (result);
      unlock ();
      return inst;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // apply a this object function with a set of arguments and a quark

  Object* Class::apply (Runnable* robj, Nameset* nset, const long quark,
			Cons* args) {
    // evaluate the object
    Object* obj = eval (robj, nset, quark);
    // apply with arguments
    return (obj == nilp) ? nilp : obj->apply (robj, nset, args);
  }

  // -------------------------------------------------------------------------
  // - builtin section                                                       -
  // -------------------------------------------------------------------------

  // create a new class object - this is the builtin function

  Object* builtin_class (Runnable* robj, Nameset* nset, Cons* args) {
    long len  = (args == nilp) ? 0 : (args->length ());
    // process 0 arguments
    if (len == 0) return new Class;
    // process data member list
    if (len == 1) {
      Class* cls = new Class;
      Cons* cons = dynamic_cast <Cons*> (args->getcar ());
      if (cons == nilp) throw Exception ("argument-error",
					 "only data member list with class");
      while (cons != nilp) {
	Lexical* lex = dynamic_cast <Lexical*> (cons->getcar ());
	if (lex == nilp) 
	  throw Exception ("argument-error",
			   "only lexical name with class data memeber list");
	cls->add (lex->toquark ());
	cons = cons->getcdr ();
      }
      return cls;
    }
    throw Exception ("argument-error",
		     "too many arguments with class definition");
  }
}
