/* $Id: become.c,v 1.20 2009/02/25 18:09:12 kiesling Exp $ */

/*
  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 "object.h"
#include "message.h"
#include "ctalk.h"

extern VARENTRY *__ctalk_dictionary;
extern VARENTRY *__ctalk_last_object;

extern RT_INFO *__call_stack[MAXARGS+1];
extern int __call_stack_ptr;

int object_become_delete = FALSE;

int __ctalkIsCallersReceiver (void) {
  /* Object itself */
  if (__call_stack[__call_stack_ptr + 1] -> rcvr_obj == 
      __call_stack[__call_stack_ptr + 2] -> rcvr_obj)
    return TRUE;
  return FALSE;
}

int __ctalkInstanceVarIsCallersReceiver (void) {
  if (__call_stack[__call_stack_ptr + 1] -> rcvr_obj && 
      __call_stack[__call_stack_ptr+1]->rcvr_obj->__o_p_obj &&
      __ctalkGetInstanceVariable 
      (__call_stack[__call_stack_ptr+1]->rcvr_obj->__o_p_obj,
       __call_stack[__call_stack_ptr+1]->rcvr_obj -> __o_name, FALSE))
    return TRUE;
  return FALSE;
}

/*
 *  Look first for an actual object, then the entry declaration
 *  if there is no object.
 */
static OBJECT *__varlist_match_object (VARENTRY *__v, 
				       char *name, char *classname) {
  VARENTRY *__v_2;
  for (__v_2 = __v; __v_2; __v_2 = __v_2 -> next) {
    if (__v_2 -> var_object && IS_OBJECT(__v_2 -> var_object)) {
      if (!strcmp (__v_2 -> var_object->__o_name, name) && 
	  !strcmp (__v_2 -> var_object->__o_classname, classname))
	return __v_2 -> var_object;
    }
  }
  for (__v_2 = __v; __v_2; __v_2 = __v_2 -> next) {
    if (!strcmp (__v_2 -> var_decl->name, name) && 
	!strcmp (__v_2 -> var_decl->class, classname))
      return __v_2 -> var_object;
  }
  return NULL;
}


OBJECT *__ctalkGetCallingFnObject (char *name, char *classname) {
  RT_FN *fn;
  VARENTRY *__v;
  if ((fn = get_calling_fn ()) != NULL) {
    return __varlist_match_object (fn -> local_objects.vars,
				   name, classname);
  }
  return NULL;
}

OBJECT *__ctalkGetCallingMethodObject (char *name, char *classname) {
  METHOD *m;
  VARENTRY *__v;
  if ((m = get_calling_method ()) != NULL) {
    return __varlist_match_object (m -> local_objects.vars,
				   name, classname);
  }
  return NULL;
}

static OBJECT *__receiver_become (OBJECT *__new) {
  OBJECT *old_rcvr;
  object_become_delete = TRUE;
  old_rcvr = __ctalk_receiver_pop_deref ();
  if (old_rcvr -> __o_varentry)
    __new -> __o_varentry = old_rcvr -> __o_varentry;
  if (old_rcvr -> nrefs == 0) {
    old_rcvr -> __o_varentry -> var_object = NULL;
    __ctalkDeleteObjectInternal (old_rcvr);
  }
  __ctalk_receiver_push_ref (__new);
  if (__call_stack[__call_stack_ptr + 1] -> rcvr_obj) {
    __call_stack[__call_stack_ptr + 1] -> rcvr_obj = __new;
    __call_stack[__call_stack_ptr + 1] -> rcvr_class_obj = 
      __new -> __o_class;
  }
  object_become_delete = FALSE;
  return __new;
}

static OBJECT *__become_this_object (VARENTRY *v, 
				     OBJECT *old, OBJECT *new) {
  OBJECT *new_copy;
  int old_nrefs;
  object_become_delete = TRUE;
  __ctalkCopyObject (OBJREF(new), OBJREF(new_copy));
  __ctalkSetObjectScope (new_copy, old -> scope);
  if (old -> __o_varentry)
    new_copy -> __o_varentry = old -> __o_varentry;
  old_nrefs = old -> nrefs;
  (void)__receiver_become (new_copy);
  __refObj (OBJREF(v -> var_object), OBJREF(new_copy));
  if (old_nrefs > 1) {  /* Wasn't deleted by __receiver_become */
    if (old -> scope & LOCAL_VAR) {
      if (old -> nrefs <= 1)
	__ctalkDeleteObject (old);
    } else {
      if (old -> nrefs <= 0)
	__ctalkDeleteObjectInternal (old);
    }
  }
  strcpy (v -> var_decl -> class, new_copy -> __o_classname);
  object_become_delete = FALSE;
  return new_copy;
}

static OBJECT *__become_this_instance_variable (VARENTRY *v, 
				     OBJECT *old, OBJECT *new) {
  OBJECT *new_copy;
  int old_nrefs;
  object_become_delete = TRUE;
  __ctalkCopyObject (OBJREF(new), OBJREF(new_copy));
  __ctalkSetObjectScope (new_copy, old -> scope);
  old_nrefs = old -> nrefs;
  if (old_nrefs > 1) {  /* Wasn't deleted by __receiver_become */
    if (old -> scope & LOCAL_VAR) {
      if (old -> nrefs <= 1)
	__ctalkDeleteObject (old);
    } else {
      if (old -> nrefs <= 0)
	__ctalkDeleteObjectInternal (old);
    }
  }
  strcpy (new_copy -> __o_name, old -> __o_name);
  strcpy (new_copy -> __o_classname, old -> __o_classname);
  strcpy (new_copy -> __o_superclassname, old -> __o_superclassname);
  new_copy -> __o_class = old -> __o_class;
  new_copy -> __o_superclass = old -> __o_superclass;
  if (old -> prev) {
    old -> prev -> next = new_copy;
    new_copy ->prev = old -> prev;
  }
  if (old -> next) {
    old -> next -> prev = new_copy;
    new_copy -> next = old -> next;
  }
  object_become_delete = FALSE;
  return new_copy;
}

OBJECT *__ctalkCallingMethodObjectBecome (OBJECT *__old, OBJECT *__new) {
  METHOD *m;
  VARENTRY *__v;
  if ((m = get_calling_method ()) != NULL) {
    for (__v = m -> local_objects.vars; __v; __v = __v -> next) {
      if (__v -> var_object == __old) {
	return __become_this_object (__v, __old, __new);
      }
    }
  }
  return NULL;
}

OBJECT *__ctalkCallingFnObjectBecome (OBJECT *__old, OBJECT *__new) {
  RT_FN *fn;
  VARENTRY *__v;
  if ((fn = get_calling_fn ()) != NULL) {
    for (__v = fn -> local_objects.vars; __v; __v = __v -> next) {
      if (__v -> var_object == __old) {
	return __become_this_object (__v, __old, __new);
      }
    }
  }
  return NULL;
}

OBJECT *__ctalkCallingReceiverBecome (OBJECT *__old, OBJECT *__new) {
  METHOD *m;
  RT_FN *fn;
  VARENTRY *__v;
  OBJECT *__old_i_var;
  if (__call_stack[__call_stack_ptr + 2] &&
      (__call_stack[__call_stack_ptr + 2] -> rcvr_obj)) {
    if ((m = __call_stack[__call_stack_ptr + 3] -> method) != NULL) {
      for (__v = m -> local_objects.vars; __v; __v = __v -> next) {
	if (!strcmp (__v -> var_decl -> name, __old -> __o_name) && 
	    !strcmp (__v -> var_decl -> class, __old -> __o_classname)) {
	  if (__v -> var_object -> __o_varentry)
	    __new -> __o_varentry = __v;
	  __ctalkDeleteObjectInternal (__v -> var_object);
	  __refObj (OBJREF(__v -> var_object), OBJREF(__new));
	  strcpy (__v -> var_decl -> class, __new -> __o_classname);
	  return __new;
	} else {
	  if ((__old_i_var = 
	       __ctalkGetInstanceVariable (__v -> var_object, __old->__o_name,
					   FALSE)) != NULL) {
	    return __become_this_instance_variable (__v, __old, __new);
	  }
	}
      }
    } else {
      /* Replacing an instance variable. */
      if ((m = __call_stack[__call_stack_ptr + 2] -> method) != NULL) {
	for (__v = m -> local_objects.vars; __v; __v = __v -> next) {
	  if ((__old_i_var = 
	       __ctalkGetInstanceVariable (__v -> var_object, __old->__o_name,
					   FALSE)) != NULL) {
	    return __become_this_object (__v, __old, __new);
	  }
	}
      }
    }


    /* Fall through. */
    if ((fn = __call_stack[__call_stack_ptr + 3] -> _rt_fn) != NULL) {
      for (__v = fn -> local_objects.vars; __v; __v = __v -> next) {
	if (__v -> var_object == __old) {
	  return __become_this_object (__v, __old, __new);
	} else {
	  if ((__old_i_var = 
	       __ctalkGetInstanceVariable (__v -> var_object, 
					   __old->__o_name,
					   FALSE)) != NULL) {
	    return __become_this_object (__v, __old, __new);
	  }
	}
      }
    }
  }
  
  return NULL;
}

extern OBJECT *__ctalk_receivers[MAXARGS+1];
extern int __ctalk_receiver_ptr;

OBJECT *__ctalkCallingInstanceVarBecome (OBJECT *__old, OBJECT *__new) {
  OBJECT *__old_i_var, *parent_object, *new_copy;
  int rcvr_ptr;
  if ((__old_i_var = 
       __ctalkGetInstanceVariable (__old->__o_p_obj,
				   __old->__o_name,
				   FALSE)) != NULL) {
    if ((parent_object = __old_i_var -> __o_p_obj) != NULL) {
      __ctalkCopyObject(OBJREF(__new), OBJREF(new_copy));
      strcpy (new_copy -> __o_name, __old -> __o_name);
      strcpy (new_copy -> __o_classname, __old -> __o_classname);
      strcpy (new_copy -> __o_superclassname, __old -> __o_superclassname);
      new_copy -> __o_class = __old -> __o_class;
      new_copy -> __o_superclass = __old -> __o_superclass;
      if (__old -> prev) {
	__old -> prev -> next = new_copy;
	new_copy -> prev = __old -> prev;
      }
      if (__old -> next) {
	__old -> next -> prev = new_copy;
	new_copy -> next = __old -> next;
      }
      __objRefCntInc(OBJREF(new_copy));
      new_copy -> __o_p_obj = __old -> __o_p_obj;
      if (__old -> __o_p_obj && 
	  __old -> __o_p_obj -> instancevars == __old)
	new_copy -> __o_p_obj -> instancevars = new_copy;
      __objRefCntDec(OBJREF(__old));

      if (__call_stack[__call_stack_ptr+1]->rcvr_obj == __old) {
	__call_stack[__call_stack_ptr+1]->rcvr_obj = new_copy;
	__call_stack[__call_stack_ptr+1]->rcvr_class_obj = new_copy -> __o_class;
      }
      for (rcvr_ptr = __ctalkGetReceiverPtr () + 1; rcvr_ptr <= MAXARGS;
	   rcvr_ptr++) {
	if (__ctalk_receivers[rcvr_ptr] == __old) {
	  __objRefCntDec(OBJREF(__ctalk_receivers[rcvr_ptr]));
	  __objRefCntInc(OBJREF(new_copy));
	  __ctalk_receivers[rcvr_ptr] = new_copy;
	}
      }
      return new_copy;
    }
  }
  return NULL;
}

OBJECT *__ctalkReceiverReceiverBecome (OBJECT *__new) {
  OBJECT *old_rcvr;
  old_rcvr = __ctalk_receiver_pop_deref ();
  old_rcvr = __ctalk_receiver_pop_deref ();
  if (old_rcvr -> __o_varentry)
    __new -> __o_varentry = old_rcvr -> __o_varentry;
  if (old_rcvr -> nrefs <= 0)
    __ctalkDeleteObject (old_rcvr);

  __ctalk_receiver_push_ref (__new);
  __ctalk_receiver_push_ref (__new);

  if (__call_stack[__call_stack_ptr + 1] -> rcvr_obj) {
    __call_stack[__call_stack_ptr + 1] -> rcvr_obj = __new;
    __call_stack[__call_stack_ptr + 1] -> rcvr_class_obj = 
      __new -> __o_class;
  }

  if (__call_stack[__call_stack_ptr + 2] -> rcvr_obj) {
    __call_stack[__call_stack_ptr + 2] -> rcvr_obj = __new;
    __call_stack[__call_stack_ptr + 2] -> rcvr_class_obj = 
      __new -> __o_class;
  }

  return __new;
}


OBJECT *__ctalkGlobalObjectBecome (OBJECT *__old, OBJECT *__new) {
  VARENTRY *__v;
  if (__ctalk_dictionary != NULL) {
    for (__v = __ctalk_dictionary; __v; __v = __v -> next) {
      if (__v -> var_object == __old) {
	__become_this_object (__v, __old, __new);
      }
    }
  }
  return NULL;
}

