/* $Id: rtclslib.c,v 1.7 2008/11/02 18:47:41 kiesling Exp $ -*-c-*-*/

/*
  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.
*/

/* 
 * Run time class dictionaries and functions.
 */

#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"
#ifdef __DJGPP__
#include "../classes/ctldjgpp"
#else
#include "../classes/ctalklib"
#endif

extern OBJECT *__ctalk_classes;
extern OBJECT *__ctalk_last_class;

/* extern OBJECT *__ctalk_dictionary; */
/* extern OBJECT *__ctalk_last_object; */
extern VARENTRY *__ctalk_dictionary;
extern VARENTRY *__ctalk_last_object;

void __ctalk_dictionary_add (OBJECT *__o) {

  OBJECT *p;
  VARENTRY *v;

  if (IS_CLASS_OBJECT(__o)) {
    for (p = __ctalk_classes; p; p = p -> next) {
      if (p  && IS_OBJECT(p)) {
	if (!strcmp (p -> __o_name, __o -> __o_name))
	  return;
      }
    }

    if (!__ctalk_classes) {
      __refObj (OBJREF(__ctalk_classes), OBJREF(__o));
      __ctalk_last_class = __o;
    } else {
      __refObj (OBJREF(__ctalk_last_class -> next), OBJREF(__o));
      __o -> prev = __ctalk_last_class;
      __ctalk_last_class = __ctalk_last_class -> next;
    }
  } else {
    v = new_varentry (__o);
    __objRefCntSet (OBJREF(__o), 1);
    if (!__ctalk_dictionary) {
      __ctalk_dictionary = __ctalk_last_object = v;
    } else {
      v -> prev = __ctalk_last_object;
      __ctalk_last_object -> next = v;
      __ctalk_last_object = __ctalk_last_object -> next;
    }
  }
}

OBJECT *__ctalkGetClass (const char *s) {

  OBJECT *__o;
  char buf[FILENAME_MAX];

  if (!__ctalk_classes)
    return NULL;

  for (__o = __ctalk_classes; __o; __o = __o -> next)
    if (!strcmp (__o -> __o_name, s))
      return __o;

  sprintf (buf, "%s/%s", CLASSLIBDIR, s);
  if ((__o = get_class_library_definition (buf)) != NULL)
     return __o;

  return NULL;
}

/* 
 *  All new objects have the instance variable, "value," 
 *  defined by default.
 *
 *  The name of the new class is on the argument stack.
 *  The name of the superclass is the receiver on the 
 *  receiver stack.
 */
OBJECT *__ctalk_define_class (OBJECT **args) {

  OBJECT *rcvr,
    *value_var;
  rcvr = __ctalk_receiver_pop_deref ();
  __ctalk_receiver_push_ref (rcvr);
  strcpy (args[0] -> __o_superclassname, rcvr->__o_name);
  strcpy (args[0] -> __o_classname, "Class");
  /* If arg is created by create_param (), then these should be reset. */
  args[0] -> __o_class = args[0];
  args[0] -> __o_superclass = rcvr;
  if ((value_var = __ctalkGetInstanceVariable (args[0], "value", FALSE))
      == NULL) {
    value_var = __ctalkCreateObject ("value", args[0] -> __o_name, 
			       ((args[0] -> __o_superclassname) ? 
				args[0] -> __o_superclassname : NULL),
				     args[0] -> scope);
    __objRefCntSet (OBJREF(value_var), args[0] -> nrefs);
    args[0] -> instancevars = value_var;
  }
  __ctalk_dictionary_add (args[0]);
  return args[0];
}

static MESSAGE *cl_messages[P_MESSAGES+1]; /* Eval message stack.        */
static int cl_message_ptr = P_MESSAGES;    /* Eval stack pointer.        */

int cl_message_push (MESSAGE *m) {
  if (cl_message_ptr == 0) {
    _warning (_("cl_message_push: stack overflow.\n"));
    return ERROR;
  }
#ifdef MACRO_STACK_TRACE
  debug (_("cl_message_push %d. %s."), cl_message_ptr, m -> name);
#endif
  cl_messages[cl_message_ptr--] = m;
  return cl_message_ptr + 1;
}

MESSAGE *cl_message_pop (void) {
  MESSAGE *m;
  if (cl_message_ptr == P_MESSAGES) {
    _warning (_("cl_message_pop: stack underflow.\n"));
    return (MESSAGE *)NULL;
  }
#ifdef MACRO_STACK_TRACE
  debug (_("cl_message_pop %d. %s."), cl_message_ptr, 
	 cl_messages[cl_message_ptr+1] -> name);
#endif
  if (cl_messages[cl_message_ptr + 1] && 
      IS_MESSAGE(cl_messages[cl_message_ptr + 1])) {
    m = cl_messages[cl_message_ptr + 1];
    cl_messages[++cl_message_ptr] = NULL;
    return m;
  } else {
    cl_messages[++cl_message_ptr] = NULL;
    return NULL;
  }
}

OBJECT *get_class_library_definition (char *fn) {

  static char *buf;
  int i, j, size,
    bytes_read,
    stack_top,
    stack_end,
    lookahead,
    lookback;
  MESSAGE *m;
  OBJECT *o = NULL;

  if (!file_exists (fn))
    return NULL;

  __setClassLibRead (True);
  __classLibName (fn);

  size = file_size (fn);
  buf = (char *)__xalloc (size + 1);

#ifdef __DJGPP__
  /*
   *  Read_file does line ending translation and error
   *  checking.
   */
  bytes_read = read_file (buf, fn);
#else
  if ((bytes_read = read_file (buf, fn)) != size) {
    _warning ("get_class_library: %s.\n", strerror (errno));
    return NULL;
  }
#endif

  stack_top = cl_message_ptr;
  stack_end = tokenize (cl_message_push, buf);

  free (buf);

  for (i = stack_top; i >= stack_end; i--) {

    m = cl_messages[i];

    switch (m -> tokentype)
      {
      case LABEL:
	if (!strcmp (m -> name, "class")) {
	  /*
	   *  The previous label is the receiver, and the 
	   *  next label is the argument.
	   */

	  for (j = i - 1, lookahead = -1; 
	       (j >= stack_end) && (lookahead == -1); 
	       j--) {
	    if (cl_messages[j] -> tokentype == LABEL)
	      lookahead = j;
	    if ((cl_messages[j] -> tokentype != WHITESPACE) &&
		(cl_messages[j] -> tokentype != NEWLINE) &&
		(cl_messages[j] -> tokentype != LABEL)) {
	      _warning ("get_class_library: missing argument label.\n");
	      return NULL;
	    }
	  }
	  for (j = i + 1, lookback = -1; 
	       (lookback == -1) && (j <= stack_top); 
	       j++) {
	    if (cl_messages[j] -> tokentype == LABEL)
	      lookback = j;
	    if ((cl_messages[j] -> tokentype != WHITESPACE) &&
		(cl_messages[j] -> tokentype != NEWLINE) &&
		(cl_messages[j] -> tokentype != LABEL)) {
	      _warning ("get_class_library: missing receiver label.\n");
	      return NULL;
	    }
	  }
	  /* Create the object manually.  Note that the object
	     does not contain the (null) default value. */
	  o = __xalloc (sizeof (struct _object));
	  strcpy (o -> sig, "OBJECT");
	  strcpy (o -> __o_name, M_NAME(cl_messages[lookahead]));
	  strcpy (o -> __o_classname, "Class");
	  o -> __o_class = o;
	  o -> scope = GLOBAL_VAR;
	  o -> __o_superclass = 
	    __ctalk_get_object (M_NAME(cl_messages[lookback]), "Class");
	  __ctalk_dictionary_add (o);
	}
	break;
      default:
	break;
      }
  }

  for (i = stack_end; i <= stack_top; i++)
    delete_message (cl_message_pop ());

  __setClassLibRead (False);

  return o;
}

/*
 *  Convenience function.  If the object is a
 *  class object, return itself.
 */
OBJECT *__ctalkClassObject (OBJECT *o) {
  if (!strcmp (o -> __o_classname, "Class")) return o;
  if (o -> __o_class && IS_OBJECT (o -> __o_class))
    return o -> __o_class;
  return __ctalk_get_object (o -> __o_classname, "Class");
}

int __ctalkIsSubClassOf (char *classname, char *superclassname) {

  OBJECT *class_object, *superclass_object;

  if ((class_object = __ctalkGetClass (classname)) == NULL) {
    __ctalkExceptionInternal (NULL, undefined_class_x, classname);
    return ERROR;
  }
    
  for (superclass_object = class_object -> __o_superclass;
       superclass_object && *superclass_object -> __o_superclassname;
       superclass_object = superclass_object -> __o_superclass)
    if (!strcmp (superclass_object -> __o_name, superclassname))
      return TRUE;

  return FALSE;
}

static void __delete_method_params (METHOD *m) {
  int n_th_param;
  for (n_th_param = 0; n_th_param < m -> n_params; n_th_param++)
    if (m->params[n_th_param])
      __xfree (m->params[n_th_param]);
}

static void __delete_method_local_objs (METHOD *m) {
  VARENTRY *v_ptr, *v_ptr_prev;

  for (v_ptr = m -> local_objects.vars; v_ptr && v_ptr -> next;
       v_ptr = v_ptr -> next)
    ;
  if (v_ptr == m -> local_objects.vars) {
    if (v_ptr && v_ptr -> var_object && 
	IS_OBJECT(v_ptr -> var_object)) {
      __ctalkDeleteObject (v_ptr -> var_object);
      delete_varentry (v_ptr);
    }
    m -> local_objects.vars = NULL;
  } else {
    while (v_ptr != m -> local_objects.vars) {
      v_ptr_prev = v_ptr -> prev;
      if (v_ptr && v_ptr -> var_object && 
	  IS_OBJECT(v_ptr -> var_object)) {
	__ctalkDeleteObject (v_ptr -> var_object);
      }
      delete_varentry (v_ptr);
      v_ptr = v_ptr_prev;
    }
    if (v_ptr && v_ptr -> var_object && 
	IS_OBJECT(v_ptr -> var_object)) {
      __ctalkDeleteObject (v_ptr -> var_object);
    }
    delete_varentry (v_ptr);
    m -> local_objects.vars = NULL;
  }
}

static void __delete_method_local_cvars (METHOD *m) {
  CVAR *c_ptr, *c_ptr_prev;

  for (c_ptr = m -> local_cvars; c_ptr && c_ptr -> next; 
       c_ptr = c_ptr -> next)
    ;
  if (c_ptr == m -> local_cvars) {
    _delete_cvar (c_ptr);
    m -> local_cvars = NULL;
  } else {
    while (c_ptr != m -> local_cvars) {
      c_ptr_prev = c_ptr -> prev;
      _delete_cvar (c_ptr);
      c_ptr = c_ptr_prev;
    }
    _delete_cvar (m -> local_cvars);
    m -> local_cvars = NULL;
  }
}

int __cleanup_deletion = FALSE;

void __ctalkClassDictionaryCleanup (void) {

  OBJECT *o;
  METHOD *m, *m_ptr;

  __cleanup_deletion = TRUE;
  if (__ctalk_classes) {
    for (o = __ctalk_classes; o; o = o -> next) {
      if (o -> instance_methods) {
	for (m_ptr = o -> instance_methods; m_ptr && m_ptr->next; 
	     m_ptr = m_ptr -> next)
	  ;
	if (m_ptr == o -> instance_methods) {
	  __delete_method_params (m_ptr);
	  if (m_ptr->local_objects.vars) __delete_method_local_objs (m_ptr);
	  if (m_ptr->local_cvars) __delete_method_local_cvars (m_ptr);
	  __xfree (o -> instance_methods);
	  o -> instance_methods = NULL;
	} else {
	  while (m_ptr != o -> instance_methods) {
	    m = m_ptr;
	    m_ptr = m_ptr -> prev;
	    __delete_method_params (m);
	    if (m->local_objects.vars) __delete_method_local_objs (m);
	    if (m->local_cvars) __delete_method_local_cvars (m);
	    __xfree (m);
	  }
	  __delete_method_params(m_ptr);
	  if (m_ptr->local_objects.vars) __delete_method_local_objs (m_ptr);
	  if (m_ptr->local_cvars) __delete_method_local_cvars (m_ptr);
	  __xfree (m_ptr);
	}
      }
      if (o -> class_methods) {
	for (m_ptr = o -> class_methods; m_ptr && m_ptr->next; 
	     m_ptr = m_ptr -> next)
	  ;
	if (m_ptr == o -> class_methods) {
	  __delete_method_params (o->class_methods);
	  if (o->class_methods->local_objects.vars) 
	    __delete_method_local_objs (o->class_methods);
	  if (o->class_methods->local_cvars) 
	    __delete_method_local_cvars (o->class_methods);
	  __xfree (o -> class_methods);
	  o -> class_methods = NULL;
	} else {
	  while (m_ptr != o -> class_methods) {
	    m = m_ptr;
	    m_ptr = m_ptr -> prev;
	    __delete_method_params (m);
	    if (m->local_objects.vars) __delete_method_local_objs (m);
	    if (m->local_cvars) __delete_method_local_cvars (m);
	    __xfree (m);
	  }
	  __delete_method_params (m_ptr);
	  if (m_ptr->local_objects.vars) __delete_method_local_objs (m_ptr);
	  if (m_ptr->local_cvars) __delete_method_local_cvars (m_ptr);
	  __xfree (m_ptr);
	}
      }
      if (o -> instancevars) {
	DELETE_OBJECT_LIST(o -> instancevars)
      }
      if (o -> classvars) {
	DELETE_OBJECT_LIST(o -> classvars)
      }
    } /* for (o = __ctalk_classes ... */
  } /* if (__ctalk_classes) */

  if (__ctalk_classes) {
    DELETE_OBJECT_LIST(__ctalk_classes)
    __ctalk_classes = __ctalk_last_class = NULL;
  } /* if (__ctalk_classes) */

  if (__ctalk_dictionary) {
/*     DELETE_OBJECT_LIST(__ctalk_dictionary) */
    
    __ctalk_dictionary = __ctalk_last_object = NULL;
  }
}

void __ctalkRemoveClass (OBJECT *o) {
  if (!o -> prev) {
    if (o -> next) o -> next -> prev = NULL;
    __ctalk_classes = o -> next;
    if (!__ctalk_classes)
      __ctalk_last_class = __ctalk_classes;
  } else {
    if (!o -> next) {
      if (o -> prev) o -> prev -> next = NULL;
      __ctalk_last_class = o -> prev;
    } else {
      o -> prev -> next = o -> next;
      o -> next -> prev = o -> prev;
    }
  }
}

OBJECT *__ctalk_find_classvar (const char *__name, const char *__classname) {
  OBJECT *o_ptr, *__classvar;
  for (o_ptr = __ctalk_classes; o_ptr; o_ptr = o_ptr -> next) {
    if (__classname && !strcmp (o_ptr -> __o_name, __classname)) {
      for (__classvar = o_ptr -> classvars; __classvar;
	   __classvar = __classvar -> next) {
	if (!strcmp (__classvar -> __o_name, __name))
	  return __classvar;
      }
    } else {
      if (!__classname) {
	for (__classvar = o_ptr -> classvars; __classvar; 
	     __classvar = __classvar -> next) {
	  if (!strcmp (__classvar -> __o_name, __name))
	    return __classvar;
	}
      }
    }
  }
  return NULL;
}
