/* $Id: rtobjvar.c,v 1.8 2009/02/25 18:09:13 kiesling Exp $ -*-c-*-*/

/*
  This file is part of Ctalk.
  Copyright © 2005-2009  Robert Kiesling, ctalk@ctalklang.org.
  Permission is granted to copy this software provided that this copyright
  notice is included in all source code modules.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library 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.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software Foundation, 
  Inc., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "lex.h"
#include "typeof.h"
#include "../classes/ctalklib"

/*
 *  Add or replace an instance variable.  If the variable
 *  we're replacing is, "value," then we only use the,
 * "value" instance variable of, "var."
 *
 *  NOTE - The instance variable might need to get the same 
 *  reference count as the receiver if it is not in a 
 *  dictionary.
 *
 *  We must be careful here because the, "value," instance
 *  variable does not itself contain instance variables, and
 *  in the cases where this function receives a, "value,"
 *  instance variable as the third argument, var and the new 
 *  value_var need to be the same, and the function needs to 
 *  check for it.
 *
 *  NOTE  (2) - This function assumes that the, "value," instance 
 *  variable exists, so it should not be called by a 
 *  constructor.  Instead, the constructors simply point
 *  object -> instancevars to the, "value," instance variable 
 *  object.  
 *
 *  This simplifies things considerably.  Refer to 
 *  _ctalk_addInstanceVariable_old (), below, to see how 
 *  confusing the function can become otherwise.
 */
OBJECT *__ctalkAddInstanceVariable (OBJECT *receiver, char *name, OBJECT *var) {

  OBJECT *t, *old_var, *value_var, *new_var;

  old_var = NULL;

  for (t = receiver -> instancevars; 
       t && (old_var == NULL); t = t -> next) {
    if (!strcmp (t -> __o_name, name))
      old_var = t;
  }

  /*
   *  If we're returning from a method, then var might be 
   *  the value var itself, so first check whether the value_var
   *  is an instance variable of var, and then if var is the value
   *  variable itself.
   */
  if (!strcmp (name, "value")) {
    if ((value_var = __ctalkGetInstanceVariable (var, "value", FALSE))
	== NULL) {
      if (!strcmp (var -> __o_name, "value")) {
	__ctalkCopyObject (OBJREF(var), OBJREF(value_var));
	__objRefCntSet (OBJREF (value_var), receiver -> nrefs);
      }
    } else {
      __ctalkCopyObject (OBJREF(value_var), OBJREF(new_var));
      value_var = new_var;
    }
  } else {
    value_var = NULL;
  }

  /*
   *  Replace a previous object, making sure that it is not
   *  in a dictionary.
   */
  if (old_var && IS_OBJECT (old_var)) {

    if (!strcmp (old_var -> __o_name, "value")) {
      
      if (receiver -> instancevars == old_var)
	receiver -> instancevars = value_var;
      value_var -> next = old_var -> next; 
      if (value_var -> next) value_var -> next -> prev = value_var;
      value_var -> prev = old_var -> prev;
      if (value_var -> prev) value_var -> prev -> next = value_var;
      __objRefCntSet (OBJREF (value_var), receiver -> nrefs);
      value_var -> __o_p_obj = receiver;
      /*
       *  The old variable _should_ be unique, so delete it.
       */
      __ctalkDeleteObject (old_var);

    } else {
      OBJECT *v, *v_next;
      for (v = receiver -> instancevars; v && IS_OBJECT(v);) {
	v_next = v -> next;
	if (!strcmp (v -> __o_name, name)) {
	  OBJECT *v_1_org, *v_1_sub;
	  OBJECT *var_copy;
	  __ctalkCopyObject (OBJREF(var), OBJREF(var_copy));
	  __objRefCntSet (OBJREF(var_copy), receiver -> nrefs);
 	  __ctalkSetObjectName(var_copy, name);
	  var_copy -> __o_p_obj = receiver;
	  var_copy -> prev = v -> prev;
	  var_copy -> next = v -> next;
	  if (v -> prev && v -> prev -> next) v -> prev -> next = var_copy;
	  if (v -> next && v -> next -> prev) v -> next -> prev = var_copy;
	  /*
	   *  Set the reference count of the new object.
	   *  Also set the reference count of the new objects
	   *  instance variable(s) separately if they match
	   *  variables of the old object.
	   *
	   *  I don't like this.  This is slow.
	   */
	  for (v_1_org = v -> instancevars; v_1_org; v_1_org = v_1_org -> next) {
	    for (v_1_sub = var -> instancevars; v_1_sub; 
		 v_1_sub = v_1_sub -> next) {
	      if (!strcmp (v_1_org -> __o_name, v_1_sub -> __o_name))
		__objRefCntSet (OBJREF (v_1_sub), v_1_org -> nrefs);
	    }
	  }
	  __ctalkDeleteObject (v);
	}
	if ((v = v_next) == NULL)
	  break;
      }
    }

  } else {
    /*
     *   Add the variable object.   Create a copy of the variable 
     *   object first.
     */

    __ctalkCopyObject (OBJREF(var), OBJREF(new_var));
    __objRefCntSet (OBJREF (new_var), receiver -> nrefs);
    __ctalkSetObjectName (new_var, name);
    new_var -> __o_p_obj = receiver;
    if (!receiver -> instancevars) {
      receiver -> instancevars = new_var;
    } else {
      for (t = receiver -> instancevars; t && t -> next; t = t -> next)
	;
      t -> next = new_var;
      new_var -> prev = t;
    }
  }
  
  return var;

}

OBJECT *__ctalkAddClassVariable (OBJECT *class_obj, char *name, OBJECT *var) {

  OBJECT *t, *old_var;

  old_var = NULL;

  for (t = class_obj -> classvars; 
       t && (old_var == NULL); t = t -> next) {
    if (!strcmp (t -> __o_name, name))
      old_var = t;
  }

  /*
   *  Replace a previous object, making sure that it is not
   *  in a dictionary.
   */
  if (old_var && IS_OBJECT (old_var)) {

    __ctalkSetObjectName (var, name);
    if (class_obj -> classvars == old_var)
      class_obj -> classvars = var;
    var -> next = old_var -> next;
    var -> prev = old_var -> prev;
    if (old_var -> prev) old_var -> prev -> next = var;
    if (old_var -> next) old_var -> next -> prev = var;
    __objRefCntSet (OBJREF(var), class_obj -> nrefs);
    __ctalkDeleteObject (old_var);

  } else {
    __objRefCntSet (OBJREF (var), class_obj -> nrefs);
    __ctalkSetObjectName (var, name);

    if (!class_obj -> classvars) {
      class_obj -> classvars = var;
    } else {
      for (t = class_obj -> classvars; t && t -> next; t = t -> next)
	;
      t -> next = var;
      var -> prev = t;
    }
  }
  
  return var;

}

OBJECT *__ctalkGetInstanceVariableByName (char *rcvr_name, char *var_name,
					   int error) {
  OBJECT *rcvr_obj;

  if ((rcvr_obj = __ctalk_get_object (rcvr_name, NULL)) == NULL)
    _error ("__ctalkGetInstanceVariableByName: Undefined object (%s).\n",
	    rcvr_name);

  /*
   *  In case the receiver is already an instance variable value.
   */
  if (!strcmp (rcvr_obj->__o_name, var_name) && 
      rcvr_obj->instancevars == NULL) 
    return rcvr_obj;

  return __ctalkGetInstanceVariable (rcvr_obj, var_name, error);

}

OBJECT *__ctalkGetInstanceVariable (OBJECT *receiver, char *name, 
				 int error) {
  OBJECT *instance_var;

  if (receiver && receiver -> instancevars && 
      IS_OBJECT (receiver -> instancevars)) {
    for (instance_var = receiver -> instancevars; 
	 instance_var && IS_OBJECT(instance_var); 
	 instance_var = instance_var -> next) {
      if (!strcmp (instance_var -> __o_name, name))
 	return instance_var;
    }
  }

  if (!strcmp (receiver -> __o_name, name) &&
      (!receiver -> instancevars) &&
      (receiver -> __o_p_obj))
    return receiver;

  if (error)
    _error ("__ctalkGetInstanceVariable: %s (Class %s) variable, \"%s,\" undefined.\n", receiver -> __o_name, receiver -> __o_classname, name);

  return NULL;
}

OBJECT *__ctalkGetClassVariable (OBJECT *receiver, char *name, 
				 int error) {
  OBJECT *class_var, *receiver_class_object;

  if (!receiver) return NULL;

  if ((receiver_class_object = receiver -> __o_class) == NULL)
    return NULL;

  if (receiver_class_object && 
      receiver_class_object -> classvars && 
      IS_OBJECT (receiver_class_object -> classvars)) {
    for (class_var = receiver -> classvars; 
	 class_var; class_var = class_var -> next) {
      if (!strcmp (class_var -> __o_name, name))
 	return class_var;
    }
  }

  if (error)
    _error ("__ctalkGetClassVariable: %s (Class %s) variable, \"%s,\" undefined.\n", receiver -> __o_name, receiver -> __o_classname, name);

  return NULL;
}

OBJECT *__ctalkFindClassVariable (char *name, int error) {

  OBJECT *r, *t;

  for (r = __ctalk_classes; r; r = r -> next) {
    for (t = r -> classvars; t; t = t -> next) {
      if (!strcmp (t -> __o_name, name))
	return t;
    }
  }

  if (error)
    _error ("__ctalkFindClassVariable: \"%s,\" undefined.\n", name);

  /* Not reached. */
  return NULL;
}

/*
 *  Check the class object for an instance variable of the 
 *  name given as the argument.
 */
int __ctalkIsClassVariableOf (OBJECT *class, const char *varname) {
  OBJECT *o;
  for (o = class -> classvars; o; o = o -> next)
    if (!strcmp (o -> __o_name, varname))
      return TRUE;

    return FALSE;
}

/*
 *  Check the class object for an instance variable of the 
 *  name given as the argument.
 */
int __ctalkIsInstanceVariableOf (OBJECT *class, const char *varname) {
  OBJECT *o;
  for (o = class -> instancevars; o; o = o -> next)
    if (!strcmp (o -> __o_name, varname))
      return TRUE;

    return FALSE;
}

void __remove_var_from_dictionary (VARENTRY *var) {

  if (var == __ctalk_last_object) {
    if (var -> var_object && IS_OBJECT(var -> var_object)) {
      var -> var_object -> __o_varentry = NULL;
    }
    if (__ctalk_last_object == __ctalk_dictionary) {
      delete_varentry (__ctalk_dictionary);
      __ctalk_dictionary = __ctalk_last_object = NULL;
    } else {
      if (__ctalk_last_object -> prev)
	__ctalk_last_object -> prev -> next = __ctalk_last_object -> next;
      if (__ctalk_last_object -> next)
	__ctalk_last_object -> next -> prev = __ctalk_last_object -> prev;
      __ctalk_last_object -> next = NULL;
      delete_varentry (var);
    }
  } else {
   _warning ("__remove_var_from_dictionary: var is not most recent object.\n");
  }
}

/*
 *  this should no longer be needed.
 */
OBJECT *__var_primitive_constructor (char *varname, 
				     METHOD *constructor_method,
				     OBJECT *class_object, 
				     OBJECT *constructor_class_object) {
  /*   OBJECT *var; */
  VARENTRY *var;
  OBJECT *var_object;

  constructor_method -> args[0] = 
    __ctalkCreateObjectInit (varname, 
			     class_object -> __o_name,
			     class_object -> __o_superclassname,
			     LOCAL_VAR, NULL);
  __ctalk_receiver_push (class_object);
  __ctalk_arg (constructor_class_object -> __o_name, 
	       constructor_method -> name, varname);
  __ctalk_primitive_method (constructor_class_object -> __o_name,
			    constructor_method -> name, 0);
  __ctalk_receiver_pop ();
  constructor_method -> args[0] = NULL;
  var = __ctalk_last_object;
  var_object = var -> var_object;
  __remove_var_from_dictionary (var);
  delete_varentry (var);
  return var_object;
}

OBJECT *__var_constructor (char *varname, METHOD *constructor_method,
			   OBJECT *class_object, 
			   OBJECT *constructor_class_object) {
  OBJECT *var_result;
  OBJECT *arg_name_object;

  arg_name_object = 
    __ctalkCreateObjectInit (varname, 
			     constructor_class_object -> __o_name,
			     constructor_class_object -> __o_superclassname, 
			     LOCAL_VAR, NULL);
  __ctalkSetObjectValueVar (arg_name_object, varname);
  __ctalk_arg (constructor_class_object -> __o_name, "new", varname);
  var_result = 
    __ctalk_method (constructor_class_object -> __o_name, constructor_method -> cfunc);
  __ctalk_arg_pop_deref ();
  if (arg_name_object != var_result) {
    /*
     *  var_result should also be removed from the dictionary 
     *  if it has the same name and class name as the argument 
     *  object.  Check if the result is still in the dictionary.
     *  If the argument and the result are the same object, don't
     *  delete.
     */
    __ctalkDeleteObjectInternal (arg_name_object);
    if (var_result -> __o_varentry == __ctalk_last_object) {
      if (var_result -> __o_varentry) {
	__remove_var_from_dictionary (var_result -> __o_varentry);
      }
    }
  }
  if (var_result -> instancevars)
    var_result -> instancevars -> __o_p_obj = var_result;
  return var_result;
}
OBJECT *__ctalkDefineInstanceVariable (char *classname, char *varname, 
				   char *varclass, char *init_expr) {
  OBJECT *class_object,
    *var_class_object,
    *var, *var_ptr, *o_t, *constructor_class_object = NULL;
  METHOD *m_t, *constructor_method = NULL;

  if ((class_object = __ctalkGetClass (classname)) == NULL) {
    _warning ("__ctalkDefineInstanceVariable: Undefined class %s.\n", 
	      classname);
    return NULL;
  }

  if ((var_class_object = __ctalkGetClass (varclass)) == NULL) {
    _warning ("__ctalkDefineInstanceVariable: Undefined variable class %s.\n", 
	      classname);
    return NULL;
  }

  for (o_t = class_object -> __o_superclass;
       o_t && !constructor_method; o_t = o_t -> __o_superclass) {
    for (m_t = o_t -> instance_methods; m_t; m_t = m_t -> next)
      if (IS_CONSTRUCTOR(m_t)) {
	constructor_method = m_t;
	constructor_class_object = o_t;
      }
  }

  /*
   *  Check if the constructor is Object : new, which is a primitive.
   */
  if (!strcmp (constructor_class_object -> __o_name, OBJECT_CLASSNAME)) {
    var = __ctalkCreateObjectInit (varname, 
			     class_object -> __o_name,
			     class_object -> __o_superclassname,
			     LOCAL_VAR, NULL);
    __ctalkSetObjectValueClass (var, var_class_object);
    __ctalkInstanceVarsFromClassObject (var);
  } else {
    var = __var_constructor (varname, constructor_method, class_object,
			     constructor_class_object);
  }

  __objRefCntSet (OBJREF(var), class_object -> nrefs);
  strcpy (var -> __o_classname, class_object -> __o_name);
  strcpy (var -> __o_superclassname, class_object -> __o_superclassname);
  var -> __o_class = class_object -> __o_class;
  var -> __o_superclass = class_object -> __o_superclass;
  __ctalkSetObjectValueClass (var, var_class_object);
  __ctalkSetObjectValue (var, init_expr);
  __ctalkSetObjectValueVar (var, init_expr);

  if (class_object -> instancevars == NULL) {
    class_object -> instancevars = var;
  } else {
    for (var_ptr = class_object -> instancevars; var_ptr -> next; 
	 var_ptr = var_ptr -> next)
      ;
    var_ptr -> next = var;
    var -> prev = var_ptr;
  }

  return NULL;
}

OBJECT *var_check;

OBJECT *__ctalkDefineClassVariable (char *classname, char *varname, 
				   char *varclass, char *init_expr) {
  OBJECT *class_object, *constructor_class_object = NULL,
    *var_class_object, *o_t, *var, *var_ptr;
  METHOD *m_t, *constructor_method = NULL;

  if ((class_object = __ctalkGetClass (classname)) == NULL) {
    _warning ("__ctalkDefineClassVariable: Undefined class %s.\n", 
	      classname);
    return NULL;
  }

  if (strcmp (classname, varclass)) {
    if ((var_class_object = __ctalkGetClass (varclass)) == NULL) {
      _warning ("__ctalkDefineClassVariable: Undefined variable class %s.\n", 
		classname);
      return NULL;
    }
  } else {
    var_class_object = class_object;
  }

  /*
   *  If the variable class is the same as the class we are 
   *  defining, then we have to start looking for a constructor
   *  in the superclass.  In this case, we will add the instance
   *  variables manually below.
   */
  for (o_t = ((class_object == var_class_object) ? 
	 var_class_object -> __o_superclass : var_class_object -> __o_class);
       o_t && !constructor_method; o_t = o_t -> __o_superclass) {
    
    for (m_t = o_t -> instance_methods; m_t; m_t = m_t -> next)
      if (IS_CONSTRUCTOR(m_t)) {
	constructor_method = m_t;
	constructor_class_object = o_t;
      }
  }


  /*
   *  If in the unlikely event the class of the class variable is
   *  Object (or something even more strange), try to use that
   *  constructor.
   */
  if (!constructor_method) {
    o_t = class_object;
    for (m_t = o_t -> instance_methods; m_t; m_t = m_t -> next)
      if (IS_CONSTRUCTOR(m_t)) {
	constructor_method = m_t;
	constructor_class_object = o_t;
      }
  }

  /*
   *  Check if the constructor is Object : new, which is a primitive.
   */
  if (!strcmp (constructor_class_object -> __o_name, OBJECT_CLASSNAME)) {
    var = __ctalkCreateObjectInit (varname, 
			     class_object -> __o_name,
			     class_object -> __o_superclassname,
			     LOCAL_VAR, NULL);
  } else {
    var = __var_constructor (varname, constructor_method, class_object,
			     constructor_class_object);
  }

  __objRefCntSet (OBJREF(var), class_object -> nrefs);

  strcpy (var -> __o_classname, var_class_object -> __o_name);
  strcpy (var -> __o_superclassname, var_class_object -> __o_superclassname);
  var -> __o_class = var_class_object -> __o_class;
  var -> __o_superclass = var_class_object -> __o_superclass;
  __ctalkSetObjectValueClass (var, var_class_object);
  __ctalkSetObjectValueVar (var, init_expr);


  /*
   *  Add the instance variables from this class, except for
   *  value.
   */
  if (class_object == var_class_object) {
    __ctalkInstanceVarsFromClassObject (var);
  }


  if (class_object -> classvars == NULL) {
    class_object -> classvars = var;
  } else {
    for (var_ptr = class_object -> classvars; var_ptr -> next; 
	 var_ptr = var_ptr -> next)
      ;
    var_ptr -> next = var;
    var -> prev = var_ptr;
  }

  return NULL;
}

OBJECT *__ctalkFindMemberClass (OBJECT *o) {
  OBJECT *r, *t;
  for (r = __ctalk_classes; r; r = r -> next) {
    for (t = r -> classvars; t; t = t -> next) {
      if (t == o) return r;
    }
  }
  return NULL;
}

int __ctalkAddInstanceVariables (OBJECT *class, OBJECT *o) {
  OBJECT *var;

  for (var = class -> instancevars; var; var = var -> next) {
    if (!__ctalkGetInstanceVariable (o, var -> __o_name, FALSE)) {
      __ctalkAddInstanceVariable (o, var -> __o_name, var);
    }
  }
  return SUCCESS;
}

/*
 *  Add instance variables for normal objects by finding
 *  the argument's class object.  If initializing a class
 *  variable, also search for the variable's parent class
 *  object and initialize the object from the actual 
 *  parent class.
 */
int __ctalkInstanceVarsFromClassObject (OBJECT *o) {

  OBJECT *class_object, *superclass_object;
  OBJECT *r;

  if ((class_object = ((o -> instancevars) ? 
		       o -> instancevars -> __o_class :
		       o -> __o_class)) == NULL) {
    __ctalkExceptionInternal (NULL, undefined_class_x, o -> __o_classname);
    return ERROR;
  }

  __ctalkAddInstanceVariables (class_object, o);

  for (superclass_object = 
	 __ctalk_get_object (class_object -> __o_superclassname, "Class");
       superclass_object && *superclass_object -> __o_superclassname;
       superclass_object = 
	 __ctalk_get_object (superclass_object -> __o_superclassname, "Class"))
    __ctalkAddInstanceVariables (superclass_object, o);


  if ((r = __ctalkFindMemberClass (o)) != NULL) {
    __ctalkAddInstanceVariables (class_object, o);
    for (superclass_object =
	   __ctalk_get_object (r -> __o_superclassname,
			       "Class");
	 superclass_object && *superclass_object -> __o_superclassname;
	 superclass_object = 
	   __ctalk_get_object (superclass_object -> __o_superclassname,
 				   "Class")) {
      __ctalkAddInstanceVariables (superclass_object, o);
    }
  }

  return SUCCESS;
}

int __ctalkDeleteObjectList (OBJECT *list) {
  if (!list)
    return SUCCESS;
  if (list -> __o_p_obj) {
    if (list -> __o_p_obj -> instancevars == list)
      list -> __o_p_obj -> instancevars = NULL;
    if (list -> __o_p_obj -> classvars == list)
      list -> __o_p_obj -> classvars = NULL;
  }
  DELETE_OBJECT_LIST(list)
  return SUCCESS;
}
