/* $Id: rt_obj.c,v 1.11 2008/11/02 23:58:08 kiesling Exp $ */

/*
  This file is part of ctalk.
  Copyright © 2005-2008  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 "object.h"
#include "message.h"
#include "ctalk.h"

extern VARENTRY *__ctalk_dictionary;
extern VARENTRY *__ctalk_last_object;

I_PASS interpreter_pass;             /* Declared in rtinfo.c.   */
extern RT_INFO *__call_stack[MAXARGS+1];
extern int __call_stack_ptr;

static METHOD *obj_method_by_fn (const OBJECT *__class_obj, OBJECT *(*fn)()) {

  static METHOD *__m;

  for (__m = __class_obj -> instance_methods; __m; __m = __m -> next)
    if (__m -> cfunc == fn)
      return __m;

  if (__class_obj -> __o_superclass) {
    return obj_method_by_fn (__class_obj -> __o_superclass, fn);
  }

  return NULL;
}

/* 
 *   TO DO - For now, if the class name is NULL, simply return 
 *   the first object that matches the name.  Add a 
 *   contextualization facility that allows the language to 
 *   qualify an ambiguous object with its class name.
 *
 *   NOTE - The return object should be checked in cases where,
 *   "name," refers to a method parameter name.
 *
 *   FIXME! -
 *     1. If, "name," refers to a parameter, also check local
 *        objects if a parameter is matched.
 */
OBJECT *__ctalk_get_object (const char *__name, const char *__classname) {

  OBJECT *__o,
    *__rcvr_obj,
    *(*parent_method_fn)();
  VARENTRY *__v;
  METHOD *__m;
  RT_FN *rt_fn;
  int i;

  /*
   *  Check for "self" keyword and parameter replacements.
   */

  if (!strcmp (__name, "self")) {
    __o = __ctalk_self_internal ();
    if (__classname) {
      if (!strcmp (__o -> __o_classname, __classname))
	return __o;
      else
	return NULL;
    } else {
      return __o;
    }
  } else {
    if (!strncmp (__name, "__ctalk_arg_internal", 
		  strlen ("__ctalk_arg_internal"))) {
      int arg_n;
      sscanf (__name, "__ctalk_arg_internal (%d)", &arg_n);
      return __ctalk_arg_internal (arg_n);
    }
  }

  /*
   *  If we're in a method, try to return a parameter, then local
   *  object.  The function should not need to check all of the
   *  receivers here, and __ctalk_getMethod* and __ctalk_findMethod*
   *  all call __ctalk_get_object (), so it's necessary to search for
   *  the method manually.
   *
   *  If the method is an inline method, first check the method's class
   *  if it is not NULL, because that is where local objects are stored.
   *
   */

  if (((__rcvr_obj = __ctalkRtMethodClassObject ()) != NULL) &&
      ((parent_method_fn = __ctalkRtGetMethodFn ()) != NULL)) {
    OBJECT *rcvr_class_obj;

    if (IS_CLASS_OBJECT(__rcvr_obj)) {
      rcvr_class_obj = __rcvr_obj;
    } else {
      rcvr_class_obj = __rcvr_obj -> __o_class;
    }

    if ((__m = obj_method_by_fn (rcvr_class_obj, parent_method_fn)) != NULL) {

      for (i = 0; i < __m -> n_params; i++) {
	if (!strcmp (__m -> params[i] -> name, __name))
	  return __m -> args[__m -> n_params - i - 1];
      }


      if (__m -> local_objects.vars) {
	for (__v = __m -> local_objects.vars; __v; __v = __v -> next) {
	  if (__classname) {
	    if (!strcmp (__v -> var_decl -> name, __name) && 
		!strcmp (__v -> var_decl -> class, __classname))
	      return __v -> var_object;
	  } else {
	    if (!strcmp (__v -> var_decl -> name, __name))
	      return __v -> var_object;
	  }
	}
      }
    }
  }

  /*
   *  If not found, look up the parent method on the call stack if 
   *  the object is local to an inline method caller like map.
   */
  if (__call_stack_ptr < MAXARGS) {
    if (((__rcvr_obj = __call_stack[__call_stack_ptr+1]->rcvr_obj) != NULL) &&
	((parent_method_fn = __call_stack[__call_stack_ptr+1]->method_fn) 
	 != NULL)) {
      OBJECT *rcvr_class_obj;
      if (!strcmp (__rcvr_obj -> __o_classname, "Class")) {
	rcvr_class_obj = __rcvr_obj;
      } else {
	rcvr_class_obj = __rcvr_obj -> __o_class;
      }

      if (rcvr_class_obj) {
	if ((__m = obj_method_by_fn (rcvr_class_obj, parent_method_fn)) 
	    != NULL) {
	
	  for (i = 0; i < __m -> n_params; i++) {
	    if (!strcmp (__m -> params[i] -> name, __name))
	      return __m -> args[__m -> n_params - i - 1];
	  }

	  if (__m -> local_objects.vars) {
	    for (__v = __m -> local_objects.vars; 
		 __v && __v -> var_object && IS_OBJECT(__v->var_object); 
		 __v = __v -> next) {
	      if (__classname) {
		if (!strcmp (__v -> var_decl -> name, __name) && 
		    !strcmp (__v -> var_decl -> class, __classname))
		  return __v -> var_object;
	      } else {
		if (!strcmp (__v -> var_decl -> name, __name))
		  return __v -> var_object;
	      }
	    }
	  }
	}
      }

      /*
       *  Check for a global object.
       */

      if (__ctalk_dictionary) {
	for (__v = __ctalk_dictionary; __v; __v = __v -> next) {
	  if (__classname) {
	    if (!strcmp (__v -> var_decl -> name, __name) &&
		!strcmp (__v -> var_decl -> class, __classname))
	      return __v -> var_object;
	  } else {
	    if (!strcmp (__v -> var_decl -> name, __name))
	      return __v -> var_object;
	  }
	  if (!__v || !__v -> next)
	    break;
	}
      }

      if (((rt_fn = get_fn ()) != NULL) ||
	  ((rt_fn = get_calling_fn ()) != NULL)) {
	for (__v = rt_fn -> local_objects.vars; __v; __v = __v -> next) {
	  if (__classname) {
	    if (!strcmp (__name, __v -> var_decl -> name) && 
		!strcmp (__classname, __v -> var_decl -> class))
	      return __v -> var_object;
	  } else {
	    if (!strcmp (__name, __v -> var_decl -> name))
	      return __v -> var_object;
	  }
	}
      }

      /*
       * Also check for a class object.  TO DO - This is a duplication
       * of the code below, and should be factored out 
       * if this function gets more complicated.
       */

      if (__classname && !strcmp (__classname, "Class")) {
	return __ctalkGetClass (__name);
      }
      return NULL;
    }
  }

  /*
   *  If we're in a function, try to find the object there.
   */
  if (interpreter_pass == run_time_pass) {
    if ((rt_fn = get_fn ()) != NULL) {
      for (__v = rt_fn -> local_objects.vars; __v; __v = __v -> next) {
 	if (__classname) {
 	  if (!strcmp (__name, __v -> var_decl -> name) && 
 	      !strcmp (__classname, __v -> var_decl -> class))
 	    return __v -> var_object;
 	} else {
 	  if (!strcmp (__name, __v -> var_decl -> name))
 	    return __v -> var_object;
 	}
      }
    }
  }

  if (__classname && !strcmp (__classname, "Class")) {
    return __ctalkGetClass (__name);
  } else {
    for (__v = __ctalk_dictionary; __v; __v = __v -> next) {
      if (__classname) {
	if (!strcmp (__v -> var_decl -> name, __name) &&
	    !strcmp (__v -> var_decl -> class, __classname))
	  return __v -> var_object;
      } else {
	if (!strcmp (__v -> var_decl -> name, __name))
	  return __v -> var_object;
      }
    }
  }

  /*
   *  Check for a class on its own.  Only CFunction class 
   *  methods need this at the moment.
   */
  if ((__o = __ctalkGetClass (__name)) != NULL)
    return __o;

  /*
   *  Finally, check for a class variable.
   */
  if ((__o = __ctalk_find_classvar (__name, __classname)) != NULL)
    return __o;

  return NULL;
}

/*
 *  This function should not need to check for, "self," and, "super,"
 *  because at this time it is only called from 
 *  __ctalkDeleteObjectInternal () and __ctalk_set_local ().
 */
int __ctalk_remove_object (OBJECT *o) {

  if (IS_CLASS_OBJECT(o)) {
    __ctalkRemoveClass (o);
  } else {
    if (__ctalk_last_object && (o == __ctalk_last_object -> var_object))
      __ctalk_last_object = __ctalk_last_object -> prev;
    if (__ctalk_dictionary && (o == __ctalk_dictionary -> var_object))
      __ctalk_dictionary = __ctalk_dictionary -> next;
    if (o -> prev) o -> prev -> next = o -> next;
    if (o -> next) o -> next -> prev = o -> prev;
    o -> next = o -> prev = NULL;
  }

  return SUCCESS;
}

OBJECT *__ctalk_self_internal (void) {

  OBJECT *self_obj;

  self_obj = __ctalk_receiver_pop ();
  __ctalk_receiver_push (self_obj);
  return self_obj;
}

void __ctalk_set_global (char *name, char *classname) {

  OBJECT *obj;

  if ((obj = __ctalk_get_object (name, classname)) == NULL)
    _error ("__ctalk_set_global (): Undefined object %s (Class %s).\n", 
	    name, ((classname) ? classname : NULLSTR));

  obj -> scope = GLOBAL_VAR;

}


#define __THIS_METHOD (__call_stack[__call_stack_ptr+1]->method)
void __ctalk_set_local (OBJECT *obj) {

  RT_FN *rt_fn;
  VARENTRY *v, *v_ptr;

  __ctalkSetObjectScope (obj, LOCAL_VAR);

  __objRefCntSet(OBJREF(obj), 1);

  /*
   *  If we're in a method, retrieve the method, and add the object
   *  there.
   */
  if (__THIS_METHOD) {
    __ctalk_remove_object (obj);
    obj -> next = obj -> prev = NULL;
    if (obj -> __o_varentry) {
      if (obj -> __o_varentry -> next)
	obj -> __o_varentry -> next -> prev = obj -> __o_varentry -> prev;
      if (obj -> __o_varentry -> prev)
	obj -> __o_varentry -> prev -> next = obj -> __o_varentry -> next;
      delete_varentry (obj -> __o_varentry);
      obj -> __o_varentry = NULL;
    }

    v = new_varentry (obj);
    if (__THIS_METHOD -> local_objects.vars) {
      for ( v_ptr = __THIS_METHOD -> local_objects.vars; 
	    v_ptr -> next; v_ptr = v_ptr -> next)
	;
      v_ptr -> next = v;
      v -> prev = v_ptr;
    } else {
      __THIS_METHOD -> local_objects.vars = v;
    }
    return;
  }
  
  /*
   *  If there's no receiver object, then we're in a C function.
   */

  if ((rt_fn = get_fn ()) == NULL) 
    return;

  __ctalk_remove_object (obj);

  if (obj -> __o_varentry) {
    if (obj -> __o_varentry -> next)
      obj -> __o_varentry -> next -> prev = obj -> __o_varentry -> prev;
    if (obj -> __o_varentry -> prev)
      obj -> __o_varentry -> prev -> next = obj -> __o_varentry -> next;
    delete_varentry (obj -> __o_varentry);
    obj -> __o_varentry = NULL;
  }

  obj -> next = obj -> prev = NULL;

  if (rt_fn -> local_objects.vars) {
    VARENTRY *v_1;
    v = new_varentry (obj);

    for (v_1 = rt_fn -> local_objects.vars; v_1 -> next; v_1 = v_1 -> next)
      ;
    v_1 -> next = v;
    v -> prev = v_1;

  } else {
    v = new_varentry (obj);
    rt_fn -> local_objects.vars = v;
  }
}

OBJECT *match_instancevar (OBJECT *varlist, OBJECT *var) {
  OBJECT *o, *p;
  for (o = varlist; o; o = o -> next) {
    if (o == var) {
      return o;
    } else {
      if (o -> instancevars)
	if ((p = match_instancevar (o -> instancevars, var)) != NULL)
	  return p;
    }
  }

  return NULL;
}

/*
 *  TO DO - Only used by __ctalkDeleteObjectInternal.  Should be 
 *  generalized.
 */
OBJECT *__ctalkInstanceVariableObject (OBJECT *var) {

  OBJECT *r;
  VARENTRY *v;

  for (v = __ctalk_dictionary; v; v = v -> next) {
    if ((r = match_instancevar (v -> var_object -> instancevars, var)) != NULL)
      return v -> var_object;
  }
  return NULL;
}

/*
 *  Like __ctalkInstanceVariableObject, but checks for 
 *  instance variable membership of objects on the receiver
 *  stack.
 */

OBJECT *__ctalkInstanceVariableReceiver (OBJECT *possible_var) {

  int i;
  OBJECT *rcvr, *v;

  for (i = __ctalkGetReceiverPtr () + 1; i <= MAXARGS; i++) {
    rcvr = __ctalk_receivers_at (i);
    for (v = rcvr -> instancevars; v; v = v -> next) {
      if (v == possible_var)
	return rcvr;
    }
  }
  return possible_var;
}

char *__ctalkCopyObjectRef(char *obj_ref_str) {
  OBJECT *src, *dest;
  static char dest_buf[MAXLABEL];
  sscanf (obj_ref_str, "0x%p", &src);
  if (!src || !IS_OBJECT(src)) {
    dest = 
      __ctalkCreateObjectInit (NULLSTR, 
			       INTEGER_CLASSNAME,
			       INTEGER_SUPERCLASSNAME, 
			       LOCAL_VAR, "0");
    sprintf (dest_buf, "%#x", (unsigned int)dest);
    return dest_buf;
  }
  /*
   *  Class objects often refer to themselves.
   */
  if (!strcmp (obj_ref_str, src->__o_value)) {
    if (src -> instancevars) {
      if (!strcmp (obj_ref_str, src->instancevars->__o_value)) {
	strcpy (dest_buf, obj_ref_str);
	return dest_buf;
      }
    } else {
      strcpy (dest_buf, obj_ref_str);
      return dest_buf;
    }
  }
  __ctalkCopyObject (OBJREF(src), OBJREF(dest));
  __objRefCntSet(OBJREF(dest), src->nrefs);
  sprintf (dest_buf, "%#x", (unsigned int)dest);
  return dest_buf;
}

OBJECT *__ctalkCopyObject (OBJREF_T src_ptr, OBJREF_T dest_ptr) {
  OBJECT *p_var, *p_var2, *new_var;

  *dest_ptr = __ctalkCreateObject ((*src_ptr) -> __o_name, 
				   (*src_ptr) -> __o_classname, 
				   (((*src_ptr) -> __o_superclassname) ? 
				    (*src_ptr) -> __o_superclassname : NULL),
				   (*src_ptr) -> scope);
  if (OBJ_REF_STR((*src_ptr)->__o_value)) {
    __ctalkSetObjectValue 
      (*dest_ptr, __ctalkCopyObjectRef
       ((*src_ptr)->__o_value));
  } else {
    __ctalkSetObjectValue (*dest_ptr, (*src_ptr) -> __o_value);
  }

  for (p_var = (*src_ptr) -> instancevars; p_var; p_var = p_var -> next) {
    /*
     *  The, "value," instance variable must already exist for 
     *  __ctalkAddInstanceVariable to be called, so add, "value," 
     *  manually here.
     */
    if (!strcmp (p_var -> __o_name, "value")) {
      if ((*dest_ptr) -> instancevars) {
	for (p_var2 = (*dest_ptr) -> instancevars; p_var2 && p_var2 -> next;
	     p_var2 = p_var2 -> next)
	  ;
	__ctalkCopyObject (OBJREF(p_var), OBJREF(new_var));
	p_var2 -> next = new_var;
	new_var -> prev = p_var2;
	if (p_var2 -> __o_p_obj)
	  new_var -> __o_p_obj = p_var2 -> __o_p_obj;
      } else {
	__ctalkCopyObject (OBJREF(p_var), 
			   OBJREF((*dest_ptr) -> instancevars));
	if (p_var -> __o_p_obj)
	  (*dest_ptr) -> instancevars -> __o_p_obj = (*dest_ptr);
      }
    } else {
      __ctalkCopyObject (OBJREF(p_var), OBJREF(new_var));
      __ctalkAddInstanceVariable (*dest_ptr, p_var -> __o_name, new_var);
    }
  }
    
  return *dest_ptr;
}

void __ctalkSetObjectScope (OBJECT *__o, int scope) {

  OBJECT *__var;

  for (__var = __o -> instancevars; __var; __var = __var -> next) 
    __ctalkSetObjectScope (__var, scope);
  
  __o -> scope = scope;
}

extern int __local_object_init;

int object_is_deletable (OBJECT *obj, OBJECT *result_obj, 
			 OBJECT *subexpr_obj){
  if ((obj -> scope & LOCAL_VAR) &&
      (__local_object_init == TRUE)) {
    if ((obj != result_obj) &&
	(obj != subexpr_obj)) {
      return TRUE;
    }
  }
  if ((obj -> scope == CREATED_PARAM) &&
      (obj != result_obj) &&
      (obj != subexpr_obj)) {
    return TRUE;
  }
  return FALSE;
}

int object_is_decrementable (OBJECT *obj, OBJECT *result_obj,
			     OBJECT *subexpr_obj) {
  if ((obj -> scope & CVAR_VAR_ALIAS_COPY)&&
      (obj != result_obj) &&
      (obj != subexpr_obj))
    return TRUE;
  if (obj && IS_OBJECT (obj) && 
      (obj != result_obj) && 
      (obj != subexpr_obj) && 
      !__ctalk_get_object(obj->__o_name, obj->__o_classname) &&
      !__ctalkFindClassVariable (obj->__o_name, FALSE) &&
      !is_receiver (obj) &&
      !is_arg (obj)) {
    return TRUE;
  }
  return FALSE;
}

VARENTRY *new_varentry (OBJECT *var_object) {
  VARENTRY *v;
  PARAM *p;
  v = (VARENTRY *)__xalloc (sizeof (struct _varentry));
  p = new_param ();
  strcpy (p -> class, var_object -> __o_classname);
  strcpy (p -> name, var_object -> __o_name);
  v -> var_decl = p;
  v -> var_object = var_object;
  var_object -> __o_varentry = v;
  return v;
}

void delete_varentry (VARENTRY *v) {
  __xfree (v -> var_decl);
  __xfree (v);
}

OBJECT *__ctalkReplaceVarEntry (VARENTRY *v, OBJECT *new_object) {
  OBJECT *old_object;
  old_object = v -> var_object;
  old_object -> __o_varentry = NULL;
  v -> var_object = new_object;
  new_object -> __o_varentry = v;
  return old_object;
}
