/* $Id: class.c,v 1.4 2008/11/06 20:54:36 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 program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  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.  See the
  GNU 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 <stdarg.h>
#include <sys/stat.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "lex.h"
#include "classlib.h"
#include "symbol.h"

OBJECT *classes = NULL;       /* Class dictionary and list head pointer.  */
static OBJECT *last_class;

int library_input = FALSE;    /* TRUE if evaluating class libraries.      */

extern int error_line,        /* Declared in lex.c.                       */
  error_column;

extern char                   /* Declared in main.c.                      */
input_source_file[FILENAME_MAX];
extern I_PASS 
interpreter_pass;
extern int nolinemarker_opt; 
extern int verbose_opt;
extern int warn_classlib_opt;

extern CTRLBLK *ctrlblks[MAXARGS + 1];
extern int ctrlblk_ptr;
extern int ctrlblk_pred,            /* Global states.      */
   ctrlblk_blk;
extern int for_init, 
  for_term,
  for_inc;
extern char ctpp_ofn[FILENAME_MAX];  /* File name of any preprocessor output.*/
  

extern METHOD *new_methods[MAXARGS+1]; /* Declared in lib/rtnwmthd.c.     */
extern int new_method_ptr;

static CLASSLIB *lib_cache,      /* List of previous library searches.       */
  *cache_ptr;

/* 
 *   The function resolve() looks up object and method references.
 */

OBJECT *resolve (int message_ptr) {

  OBJECT *result_object = NULL;
  OBJECT *class_object; 
  OBJECT *instancevar_object;
  MESSAGE *m;      
  int msg_frame_top = 0,       /* Avoid a warning.                     */
    ctrlblk_start;
  int prev_label_ptr;          /* Stack pointers to messages with      */
  int prev_tok_ptr;
  int idx_actual_method;
  int method_attrs;
  MESSAGE *m_actual_method;
  MESSAGE *m_prev_label=NULL;  /* Messages that refer to previously    */
  int next_label_ptr;          /* Forward message references to check  */
  MESSAGE *m_next_label;       /*  method arguments, if necessary.     */
  METHOD *method;
  OBJECT_CONTEXT param_context;

  m = message_stack_at (message_ptr);

  if (!m || !IS_MESSAGE(m))
    _error ("Invalid message in resolve.");

  if (m -> evaled)
    return NULL;

  if (is_c_keyword (M_NAME(m)) ||
      get_local_var (M_NAME(m)) ||
      global_var_is_declared (M_NAME(m)) ||
      get_function (M_NAME(m)) ||
      typedef_is_declared (M_NAME (m)))
    return NULL;
      
  /* Look for a class object. */

  if ((result_object = class_object_search (m -> name)) != NULL) {
    m -> obj = result_object;
    default_method (message_stack (), message_ptr);
    return result_object;
  }

  if (is_expr_obj (m)) return M_VALUE_OBJ (m);

  /*
   *   Check for a method parameter reference and create an
   *   object if necessary.
   */
  if (interpreter_pass == method_pass) {
    int i, n;
    METHOD *method;
    method = new_methods[new_method_ptr + 1];

    if (method && IS_METHOD (method)) {
      for (i = 0; i < method -> n_params; i++) {
	if (!strcmp (m -> name, method -> params[i] -> name)) {

	  param_context = object_context (message_stack (), message_ptr);

  	  if (param_context == c_context)  {
	    /* 
	     *  Replace the parameter name with the argument accessor
	     *  function.  In an argument context, the arguments are on 
	     *  the run-time stack in reverse order. 
	     *
	     *  TO DO -Note that, at the moment, we cannot simply add
	     *  an object for the argument expression - the, "Expr,"
	     *  class may not yet have been added.  So try to make,
	     *  "Expr," one of the default classes.
	     */

  	    for (n = 0; n < method -> n_params; n++) {
  	      if (!strcmp (m -> name, method -> params[n] -> name) &&
		  !(method -> params[n] -> attrs & PARAM_C_PARAM)) {
		if (need_rt_eval (message_stack (), message_ptr)) {
		  ctrlblk_pred_rt_expr (message_stack (), message_ptr);
		} else {
		  method_arg_accessor_fn (message_stack (), message_ptr,
					  method -> n_params - n - 1);
		}
	      }
  	    }
  	  } else {

	    /*   If the reference occurs in an argument, replace the
	     *   parameter with the rt arg call, and make sure that
	     *   there is an object associated with it so we can
	     *   perform further rt substitutions if necessary.
	     */
	    /*   Take 2 - There's no way, at the moment, of knowing
	     *   if a parameter expression has a valid method.  Until
	     *   the interpreter adds a second pass, we'll have
	     *   to make it a run time expression.
	     */

	    if (param_context == argument_context) {
	      for (n = 0; n < method -> n_params; n++) {
		if (!strcmp (m -> name, method -> params[n] -> name))
		  method_arg_rt_expr (message_stack (), message_ptr);
	      }
	    }
	    if (!m -> obj || !IS_OBJECT (m -> obj)) {
	      if (((class_object = 
		   class_object_search (method -> params[i] -> class)) 
		  == NULL) &&
		  !is_pending_class(method -> params[i] -> class))
 		error (m, "Resolve: Undefined class %s.",
 		       method -> params[i] -> class);

 	      result_object =
 		create_object_init 
		(((strlen (method -> params[i] -> class) != 0) ?
		  method -> params[i] -> class : "Object"),
		 "Object",
		 method -> params[i] -> name,
		 NULLSTR);
	      save_method_object (result_object);
	      m -> obj = result_object;
	      return result_object;
	    }
  	  }
	}
      }
    }
  }

  /* 
   *  Look for a global or local object.  
   */
  prev_label_ptr = prevlangmsg (message_stack (), message_ptr);
  if (prev_label_ptr != ERROR)
    m_prev_label = message_stack_at (prev_label_ptr);
  /*
   *  Note that finding a previous label depends on the parser
   *  framing if the class variable is in a receiver context.
   *  Case for <classname> <classvarname> expression.
   */
  if (M_TOK(m) == LABEL) {
    if ((prev_label_ptr != ERROR) && 
	((result_object = 
	  get_class_variable (M_NAME(m), M_NAME(m_prev_label), FALSE))
	 != NULL)) {
      class_variable_expression (message_stack (), message_ptr);
      return result_object;
    } else {
      /*
       *  Case for <classvar> without receiver class.
       */
      if ((result_object = find_class_variable (m, M_NAME(m))) != NULL) {
	m -> obj = result_object;
	m -> attrs |= OBJ_IS_CLASS_VAR;
	default_method (message_stack (), message_ptr);
	return result_object;
      } else {
	/*
	 *  Case for <instance object> <classvar>.  We can
	 *  eliminate the instance object.
	 */
	if (((next_label_ptr = nextlangmsg (message_stack (), message_ptr))
	     != ERROR) && 
	    get_object (M_NAME(m), NULL) && 
	    ((result_object = 
	      find_class_variable (message_stack_at (next_label_ptr),
				   M_NAME(message_stack_at(next_label_ptr))))
	     != NULL)) {
	  ++m -> evaled;
	  ++m -> output;
	  return result_object;
	} else {
	  if ((result_object = get_object (m -> name, NULL)) != NULL) {
	    m -> obj = result_object;
	    default_method (message_stack (), message_ptr);
	    return result_object;
	  }
	}
      }
    }
  }

  /* 
   *  Check for, "self," "require," "return," "noMethodInit," 
   *  and "returnObjectClass." If they don't translate 
   *  into run time code, they get elided here also.
   */
   if (!strcmp (m -> name, "require")) {
     require_class (message_stack (), message_ptr);
     return NULL;
   }

  if (!strcmp (m -> name, "self") && 
      need_rt_eval (message_stack (), message_ptr)) {
    if (ctrlblk_pred) {
      ctrlblk_pred_rt_expr (message_stack (), message_ptr);
    } else {
      int _expr_end;
      (void)rt_expr (message_stack (), message_ptr, &_expr_end);
    }
  } else {
    if ((result_object =
  	 self_object (message_stack (), message_ptr)) != NULL) {
      m -> obj = result_object;
      param_context = object_context (message_stack (), message_ptr);
      if (param_context == argument_context) {
	method_arg_rt_expr (message_stack (), message_ptr);
	/*
	 *  self_object () or some other function sets the
	 *  output member. Clear it so the expression can
	 *  be output.
	 */
	m -> output = 0;
	return result_object;
      } else {
	default_method (message_stack (), message_ptr);
	return result_object;
      }
    }
  }

  if (fn_return (message_stack (), message_ptr) == 0)
    return NULL;

  if (interpreter_pass == method_pass) {
    int i, next_label_ptr;
    if (!strcmp (m -> name, "returnObjectClass")) {
      if ((next_label_ptr = scanforward (message_stack (), message_ptr - 1,
			 message_frame_top_n (parser_frame_ptr () - 1), 
					 LABEL)) == ERROR) {
	warning (m, "Syntax error");
      }
      strcpy (new_methods[new_method_ptr + 1] -> returnclass,
	      message_stack_at(next_label_ptr) -> name);
      if ((next_label_ptr = scanforward (message_stack (), message_ptr + 1,
			 message_frame_top_n (parser_frame_ptr () - 1), 
					 SEMICOLON)) == ERROR) {
	warning (m, "Syntax error");
      }
      for (i = message_ptr; i >= next_label_ptr; i--) {
	++(message_stack_at (i)) -> evaled;
	++(message_stack_at (i)) -> output;
      }
      return NULL;
    }
    if (!strcmp (M_NAME(m), "noMethodInit")) {
      new_methods[new_method_ptr + 1] -> no_init = TRUE;
      ++(message_stack_at (message_ptr)) -> evaled;
      ++(message_stack_at (message_ptr)) -> output;
      if ((i = nextlangmsg (message_stack (), message_ptr)) == ERROR)
	warning (m, "Syntax error");
      if (message_stack_at (i) -> tokentype != SEMICOLON)
	warning (m, "Syntax error");
      ++(message_stack_at (i)) -> evaled;
      ++(message_stack_at (i)) -> output;
      return NULL;
    }
  }

  /* Attempt to resolve methods and arguments. */

  /*
   *  Check for modifiers or predicates to the method.  At the 
   *  moment, the only modifier is, "super."
   */
  
  if ((idx_actual_method = compound_method (message_stack (), message_ptr,
					    &method_attrs)) 
      != message_ptr) {
  }
  m_actual_method = message_stack_at (idx_actual_method);

  /*
   *  Here we need to check for control blocks that are not enclosed
   *  by braces and occur in the same frame as the predicate.
   */
  if (ctrlblk_blk)
    ctrlblk_start = C_CTRL_BLK -> blk_start_ptr;
  if (ctrlblk_pred)
    ctrlblk_start = C_CTRL_BLK -> pred_start_ptr;
  /*
   *  Here we have to account for, "for," loop predicates.  Because
   *  they all occur within the same frame, we use the CTRLBLK structure
   *  indexes instead of the frame top.
   */
  if (ctrlblk_pred) {
    if (C_CTRL_BLK -> stmt_type == stmt_for) {
      if (for_init)
	msg_frame_top = C_CTRL_BLK -> for_init_start;
      if (for_term)
	msg_frame_top = C_CTRL_BLK -> for_term_start;
      if (for_inc)
	msg_frame_top = C_CTRL_BLK -> for_inc_start;
    } else {
      /*
       *  NOTE - It may be necessary to add conditions for other
       *  special loop cases here.
       */
      msg_frame_top = message_frame_top_n (parser_frame_ptr ());
    }
  } else {
    if (ctrlblk_blk) {
      /*
       *  If we have a code block without braces, then set 
       *  msg_frame_top to blk_start_ptr.  If the control block
       *  is delimited by braces, we can simply use the frame 
       *  top.
       */
      if (C_CTRL_BLK -> braces == 0) {
	msg_frame_top = C_CTRL_BLK -> blk_start_ptr;
      } else {
	msg_frame_top = message_frame_top_n (parser_frame_ptr ());
      }
    } else {
      msg_frame_top = message_frame_top_n (parser_frame_ptr ());
    }
  }

  prev_label_ptr = scanback (message_stack (), idx_actual_method + 1,
  			     msg_frame_top, LABEL);
  prev_tok_ptr = prevlangmsg (message_stack (), idx_actual_method);

  /*  If the label isn't resolved as an object above, it could be
   *  interpreted as an argument or method of an object in the
   *  predicate.
   */

  if (ctrlblk_blk) {
    if ((idx_actual_method <= C_CTRL_BLK -> blk_start_ptr) &&
	(idx_actual_method >= C_CTRL_BLK -> blk_end_ptr) &&
	(prev_label_ptr <= C_CTRL_BLK -> pred_start_ptr) &&
	(prev_label_ptr >= C_CTRL_BLK -> pred_end_ptr))
      return NULL;
  }

  if (prev_label_ptr != ERROR)
    m_prev_label = message_stack_at (prev_label_ptr);

  if (prev_label_ptr == ERROR) {
    if ((prev_label_ptr = scanback (message_stack (), idx_actual_method + 1,
 				    msg_frame_top, C_KEYWORD)) != -1) {
      return NULL;
    } else {
      OBJECT_CONTEXT c;
      /* 
       *  Issue a warning only for label tokens for which a receiver object
       *  has not been defined, or if we can determine that the 
       *  undefined label token appears in an object context.
       */
      if ((m_actual_method -> tokentype == LABEL) && 
	  !IS_DEFINED_LABEL (m_actual_method -> name) && 
	  !result_object) {
	__ctalkExceptionInternal (m, undefined_label_x, m_actual_method -> name);
	return NULL;
      }

      c = object_context (message_stack (), idx_actual_method);
      if ((c == argument_context) || (c == receiver_context)) {
	if ((M_TOK(m_actual_method) == LABEL) || 
	    (M_TOK(m_actual_method) == METHODMSGLABEL)) {
	  if (!IS_DEFINED_LABEL (m_actual_method -> name))
	    __ctalkExceptionInternal (m, undefined_receiver_x, m_actual_method -> name);
	  return NULL;
	}
      }
    } /* else */
  }

  /* The most recent reference is an object. */

  if (prev_label_ptr != ERROR) {

    if (m_prev_label && m_prev_label -> obj && 
	IS_OBJECT (m_prev_label -> obj)) {

      /* The previous reference is a class object. */
      if (IS_CLASS_OBJECT(m_prev_label -> obj)) {
	if (((method = get_instance_method (m_prev_label, m_prev_label->obj,
					   m_actual_method->name,FALSE)) 
	    != NULL) ||
	    ((method = get_class_method (m_prev_label, m_prev_label->obj,
					    m_actual_method->name,FALSE)) 
	     != NULL) ||
	    is_method_proto (m_prev_label->obj->__o_class->__o_name,
			     m_actual_method -> name)) {
	  m_actual_method -> receiver_msg = m_prev_label;
	  m_actual_method -> receiver_obj = m_prev_label -> obj;
	  m_actual_method -> tokentype = METHODMSGLABEL;
	} else {
	  __ctalkExceptionInternal (m_actual_method, undefined_method_x,
				    m_actual_method -> name);
	  return NULL;
	}
      } else {
	
	/* 
	 *   The previous label refers to an object instance. 
	 *   Determine if this label refers to a method or 
	 *   an instance variable.  Give precedence to instance
	 *   variables.
	 */

	if ((class_object = 
	     get_class_object (m_prev_label -> obj -> __o_classname))
	    == NULL)
 	  error (m_prev_label, "Unknown class \"%s.\"", 
		 m_prev_label ->obj -> __o_classname);
	/*
	 *   We still have to make sure that the method (at idx_actual_method)
	 *   immediately follows the receiver (at prev_label_ptr).
	 */
	if (prev_tok_ptr == prev_label_ptr) {
	  m_actual_method -> receiver_msg = m_prev_label;
	  m_actual_method -> receiver_obj = m_prev_label -> obj;
	  if (((instancevar_object = 
		__ctalkGetInstanceVariable (m_prev_label -> obj, m_actual_method -> name,
					     FALSE)) != NULL) ||
	      ((method = get_instance_method (m_actual_method, 
					      class_object, 
					      m_actual_method -> name, FALSE)) != NULL)) {
	    m_actual_method -> tokentype = METHODMSGLABEL;
	  } else {
	    /*
	     *  Class variable alone, possibly with value class different
	     *  than var class.
	     */
	    if ((m_prev_label->attrs & OBJ_IS_CLASS_VAR) &&
		m_prev_label->obj->instancevars && 
		((method = 
		  get_instance_method (m_actual_method,
       get_class_object (m_prev_label->obj->instancevars->__o_classname), 
				       M_NAME(m_actual_method), 
				       FALSE)) != NULL)) {
	      m_actual_method -> tokentype = METHODMSGLABEL;
	    } else {
	      char errbuf[MAXMSG];
	      if (is_method_proto (CLASSNAME(class_object), 
				   M_NAME(m_actual_method))) {
		SNPRINTF(errbuf,MAXMSG,METHOD_ERR_FMT,
			 M_NAME(m_actual_method),
			 CLASSNAME(class_object));
		__ctalkExceptionInternal (m_actual_method, 
					  method_used_before_define_x, errbuf);
	      } else {
		__ctalkExceptionInternal (m_actual_method, undefined_method_x, 
					  M_NAME(m_actual_method));
	      }
	      return NULL;
	    }
	  }
	}
      }
    }

    if ((next_label_ptr = nextlangmsg (message_stack (), idx_actual_method))
	!= ERROR) {
      m_next_label = message_stack_at (next_label_ptr);

      if (m_actual_method -> tokentype == METHODMSGLABEL) {
	method_call (idx_actual_method); 
      } else {
	if (interpreter_pass == parsing_pass) {
	  if ((m_actual_method -> tokentype == LABEL) && 
	      !m_actual_method -> obj && !m_prev_label -> obj)
	    if (!IS_DEFINED_LABEL (m_actual_method -> name)) {
	      __ctalkExceptionInternal (m, undefined_label_x, m_actual_method -> name);
  	      return NULL;
	    }
	} else {
	  if (interpreter_pass == method_pass) {
	    if ((m_actual_method -> tokentype == LABEL) && 
		!m_actual_method -> obj && !m_prev_label -> obj)
	      if (!IS_DEFINED_LABEL (m_actual_method -> name) &&
		  !is_method_parameter (message_stack (), idx_actual_method)) {
		__ctalkExceptionInternal (m, undefined_label_x, m_actual_method -> name);
		return NULL;
	      }
	  }
	}
      }
      return M_VALUE_OBJ (m_prev_label);
    }

    return m_prev_label -> obj;
  }
  
  /* The most recent reference is a method.  If not evaled, set the
     method args and call the method.
  */

  m_actual_method -> obj = result_object;
  if (m_prev_label && IS_MESSAGE(m_prev_label)) 
    m_actual_method -> receiver_obj = m_prev_label -> obj;

  return result_object;

}

/* Define the default object for class, "Object," and the primitive
   methods, "class," "new," "method," "instanceVariable," and, "classVariable."
   Generate the run-time code for inclusion in the source file if it
   includes main().  Refer to ctalklib.h for the run-time equivalents
   of the primitive methods.
*/

int init_default_classes (void) {
  
  OBJECT *o;                   /* The default Object class object. */
  char buf[MAXMSG];

  o = create_object ("Class", "Object");
  SNPRINTF (buf, MAXMSG, "%#x", (unsigned int)o);
  __ctalkSetObjectValue (o, buf);
  add_class_object (o);
  generate_global_object_definition ("Object", "Class", NULL, buf, GLOBAL_VAR);
  define_primitive_method ("Object", "class", define_class, 1, FALSE);
  define_primitive_method ("Object", "new", new_object, 1, TRUE);
  define_primitive_method ("Object", "instanceMethod", 
			   new_instance_method, 1, FALSE);
  define_primitive_method ("Object", "classMethod", 
			   new_class_method, 1, FALSE);
  define_primitive_method ("Object", "instanceVariable", 
			   define_instance_variable, 1, TRUE);
  define_primitive_method ("Object", "classVariable", 
			   define_class_variable, 1, TRUE);
  generate_global_method_definition_call ("Object", "class", 
				   "__ctalk_define_class", 1);
  generate_global_method_definition_call ("Object", "new", 
				   "__ctalk_new_object", 1);
  generate_global_method_definition_call ("Object", "instanceVariable", 
				   "__ctalkDefineInstanceVariable", 1);
  generate_global_method_definition_call ("Object", "classVariable", 
				   "__ctalkDefineClassVariable", 1);
  return SUCCESS;
}

/* 
 *     Add a class object to the class dictionary. 
 */
int add_class_object (OBJECT *o) {

  if (!classes) {
    classes = o;
    last_class = o;
  } else {
    last_class -> next = o;
    o -> prev = last_class;
    last_class = o;
  }

  return SUCCESS;
}

/*
 *     Retrieve a class object from the class dictionary.
 */
OBJECT *get_class_object (char *name) {

  OBJECT *o;

  if ((interpreter_pass == preprocessing_pass) ||
      (interpreter_pass == var_pass))
    return NULL;

  if (!classes) 
    return NULL;

  for (o = classes; ; o = o -> next) {
    if (!strcmp (o -> __o_name, name))
      return o;
    if (!o || !o -> next)
      break;
  }

  return NULL;
}

OBJECT *class_object_search (char *name) {

  OBJECT *o;

  if (!classes) 
    return NULL;

  for (o = classes; ; o = o -> next) {
    if (!strcmp (o -> __o_name, name))
      return o;
    if (!o || !o -> next)
      break;
  }

  if (!library_search (name))
    for (o = classes; ; o = o -> next) {
      if (!strcmp (o -> __o_name, name))
	return o;
      if (!o || !o -> next)
	break;
    }

  return NULL;
}

CLASSLIB *lib_includes[MAXARGS + 1];
int lib_includes_ptr;

CLASSLIB *input_declarations[MAXARGS + 1];
int input_declarations_ptr;

int get_lib_includes_ptr (void) {
  return lib_includes_ptr;
}

CLASSLIB *lib_include_at (int ptr) {
  return lib_includes[ptr];
}

char *library_pathname (void) {
  if (lib_includes_ptr >= MAXARGS)
    return NULL;
  else
    return lib_includes[lib_includes_ptr + 1] -> path;
}

void push_library_include (char *path, char *name, int src_line) {

  CLASSLIB *c;

  if (lib_includes_ptr == 0)
    _error ("push_library_include: stack overflow.");

  if ((c = (CLASSLIB *)calloc (1, sizeof (CLASSLIB))) == NULL)
    _error ("push_library_include: %s.", strerror (errno));

  strcpy (c -> sig, "CLASSLIB");
  strcpy (c -> path, path);
  strcpy (c -> name, name);
  strcpy (c -> included_from_filename, __source_filename ());
  c -> included_from_line = src_line;

  lib_includes[lib_includes_ptr--] = c;

  if (warn_classlib_opt) {
    debug ("%d. %s.", lib_includes_ptr + 1, c -> path);
  
    if (lib_includes_ptr + 2 < MAXARGS) {
      int i;
      for (i = lib_includes_ptr + 2; i <= MAXARGS; i++)
	debug ("\tFrom %s, Line %d.", lib_includes[i] -> path, 1);
    }
    debug ("\tFrom %s, Line %d.", input_source_file, error_line);
  }
  
}

CLASSLIB *pop_library_include (void) {

  CLASSLIB *c;
  if (lib_includes_ptr > MAXARGS)
    _error ("pop_library_include: stack underflow.");

  if (warn_classlib_opt) {
    debug ("%d. Exiting %s.", 
	   lib_includes_ptr + 1, 
	   lib_includes[lib_includes_ptr + 1] -> path);
  }

  c = lib_includes[++lib_includes_ptr];
  __ctalkRtSaveSourceFileName (c -> included_from_filename);
  error_line = c -> included_from_line;
  lib_includes[lib_includes_ptr] = NULL;
  return c;
}

/* 
 *  Cache the path of a library search to avoid repeating the
 *  search and redefining objects.
 */

int cache_library_search (CLASSLIB *c) {
  if (!cache_ptr) {
    lib_cache = cache_ptr = c;
  } else {
    cache_ptr -> next = c;
    c -> prev = cache_ptr;
    cache_ptr = c;
  }
  return SUCCESS;
}

/* 
 * Keep searching the library paths for all files of the same name, in
 * case, for example, there is a library module called, "new."
 */

/*
 *  Last line marker info encountered by parser_pass ().
 */

extern struct {
  int line;
  char filename[FILENAME_MAX];
  int flag;
} line_marker;


int library_search (char *class) {

  struct stat statbuf;
  char *path, *inbuf, *s;
#ifdef __linux__ 
  __off_t file_size; 
#else
  off_t file_size;
#endif
  int i, r, r_stat;
  size_t input_chars;
  int prev_library_input;
  int find_next = FALSE;
  OBJECT *parent_rcvr_class;
  I_PASS last_pass;
  CLASSLIB *l, *libinc;
  char ctpp_ofn_lib[FILENAME_MAX];

  if (!class || ! strlen (class))
    return ERROR;

 next_file:

  if ((path = find_library_include (class, find_next)) == NULL)
    return ERROR;

  /*
   *  Determine if we've already loaded the library.  This should
   *  be factored out if we use it a lot.
   */
  for (l = lib_cache; l; l = l -> next) {
    if (!strcmp (l -> path, path))
      return SUCCESS;
  }

  /* 
   *  If this is a library we're currently loading, 
   *  also return.
   *
   *  TO DO - This can be refactored better with the 
   *  library cache so we don't have to look up the
   *  path directly.
   */
  for (i = lib_includes_ptr + 1; i <= MAXARGS; i++) {
    if (!strcmp (lib_includes[i] -> path, path))
      return SUCCESS;
  }

#ifdef CLASSLIB_TRACE
  debug ("Class lib %s.", path);
#endif

  if (is_dir (path)) {
#ifdef CLASSLIB_TRACE
    _warning ("Class lib %s: is a directory.\n");
#endif
    find_next = TRUE;
    goto next_file;
  }

  if ((r_stat = stat (path, &statbuf)) != 0) {
    _warning ("%s: %s.", path, strerror (errno));
    return ERROR;
  }

  /*
   *  Set the source file to the library file after saving the 
   *  calling source file.
   */

  prev_library_input = library_input;
  library_input = TRUE;
  last_pass = interpreter_pass;
  interpreter_pass = library_pass;
  parent_rcvr_class = get_rcvr_class_obj ();
  push_library_include (path, class, error_line);

  /* 
   *  Fills in ctpp_ofn.
   */
  if ((r = preprocess (path, FALSE)) == ERROR) 
    goto recover;

  strcpy (ctpp_ofn_lib, ctpp_ofn);

  interpreter_pass = var_pass;
  parse_vars (ctpp_ofn_lib);
  resolve_incomplete_types ();
  method_prototypes (ctpp_ofn_lib, C_LIB_INCLUDE);
  interpreter_pass = library_pass;

  if ((r_stat = stat (ctpp_ofn_lib, &statbuf)) != 0)
    return ERROR;
#ifndef S_SPLINT_S
  file_size = statbuf.st_size;
#endif

  if ((inbuf = (char *)calloc ((size_t)statbuf.st_size + 1, 
			       sizeof (char))) == NULL)
    _error ("library search %s: %s.", class, strerror (errno));

  input_chars = read_file (inbuf, ctpp_ofn_lib);

  s = parse (inbuf, (long long) file_size);

  free (inbuf);

 recover:
  library_input = prev_library_input;
  interpreter_pass = last_pass;
  set_rcvr_class_obj (parent_rcvr_class);

  /*
   * Do not delete the lib_include entry because we use it in the
   * cache to prevent a class library from being loaded more than
   * once.
   *
   * There should be an exception handler here, but no errors
   * have yet occurred at this point.
   */

  libinc = pop_library_include ();

  if (r != ERROR)
    cache_library_search (libinc);
  else
    free (libinc);

  unlink (ctpp_ofn_lib);

  return r;
}

extern MESSAGE *p_messages[P_MESSAGES+1];   /* Declared in preprocess.c. */
extern int p_message_ptr;

int require_class (MESSAGE_STACK messages, int msg_ptr) {

  int operand_ptr,
    term_ptr;

  if ((interpreter_pass == parsing_pass) ||
      (interpreter_pass == library_pass)) {
    
    if (((operand_ptr = nextlangmsg (messages, msg_ptr)) != ERROR) &&
	((term_ptr = nextlangmsg (messages, operand_ptr)) != ERROR)) {

      if ((messages[operand_ptr] -> tokentype != LABEL) ||
	  (messages[term_ptr] -> tokentype != SEMICOLON)) {
	warning (messages[msg_ptr], "Syntax error.");
	return ERROR;
      }

      ++messages[msg_ptr] -> evaled;
      ++messages[msg_ptr] -> output;
      ++messages[operand_ptr] -> evaled;
      ++messages[operand_ptr] -> output;
      ++messages[term_ptr] -> evaled;
      ++messages[term_ptr] -> output;

      library_search (messages[operand_ptr] -> name);
    }

  }

  return SUCCESS;
}

int is_pending_class (char *s) {
  int i;

  for (i = lib_includes_ptr + 1; i <= MAXARGS; i++) {
    if (!strcmp (lib_includes[i] -> name, s))
      return TRUE;
  }
  return FALSE;
}

int is_cached_class (char *s) {
  CLASSLIB *c;
  for (c = lib_cache; c; c = c -> next) {
    if (!strcmp (c -> name, s))
      return TRUE;
  }
  return FALSE;
}

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

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

int instance_variables_from_class_definition (OBJECT *o) {
  OBJECT *class_object, *superclass_object;

  if ((class_object = get_class_object (o -> __o_classname)) == NULL) {
    __ctalkExceptionInternal (message_stack_at (get_messageptr ()),
			      undefined_class_x, o -> __o_classname);
    return ERROR;
  }
    
  add_instance_variables (class_object, o);

  for (superclass_object = class_object -> __o_superclass;
       superclass_object && *superclass_object -> __o_superclassname;
       superclass_object = superclass_object -> __o_superclass)
    add_instance_variables (superclass_object, o);

  return SUCCESS;
}

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

  OBJECT *class_object, *superclass_object;

  if ((class_object = get_class_object (classname)) == NULL) {
    __ctalkExceptionInternal (message_stack_at (get_messageptr ()),
			      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_methods (METHOD *method_list) {

  METHOD *method_ptr, *method_ptr_prev;
  OBJECT *obj_ptr, *obj_ptr_prev;
  CVAR *cvar_ptr, *cvar_ptr_prev;
  int param_idx;

  for (method_ptr = method_list; 
       method_ptr && method_ptr -> next; 
       method_ptr = method_ptr -> next)
    ;

  while (method_ptr != method_list) {
    method_ptr_prev = method_ptr -> prev;
    if (method_ptr -> local_objects.objs) {
      for (obj_ptr = method_ptr -> local_objects.objs;
	   obj_ptr && obj_ptr -> next;
	   obj_ptr = obj_ptr -> next)
	;
      while (obj_ptr != method_ptr -> local_objects.objs) {
	obj_ptr_prev = obj_ptr -> prev;
	delete_object (obj_ptr);
	obj_ptr = obj_ptr_prev;
      }
      delete_object (method_ptr -> local_objects.objs);
    }
    if (method_ptr -> local_cvars) {
      for (cvar_ptr = method_ptr -> local_cvars;
	   cvar_ptr && cvar_ptr -> next;
	   cvar_ptr = cvar_ptr -> next)
	;
      while (cvar_ptr != method_ptr -> local_cvars) {
	cvar_ptr_prev = cvar_ptr -> prev;
	_delete_cvar (cvar_ptr);
	cvar_ptr = cvar_ptr_prev;
      }
      _delete_cvar (method_ptr -> local_cvars);
    } 
    if (method_ptr -> n_params) {
      for (param_idx = 0; param_idx < method_ptr -> n_params; param_idx++) {
	free (method_ptr -> params[param_idx]);
      }
    }
    free (method_ptr -> src);
    free (method_ptr);
    method_ptr = method_ptr_prev;
  }

  if (method_list -> local_objects.objs) {
    for (obj_ptr = method_list -> local_objects.objs;
	 obj_ptr && obj_ptr -> next;
	 obj_ptr = obj_ptr -> next)
      ;
    while (obj_ptr != method_list -> local_objects.objs) {
      obj_ptr_prev = obj_ptr -> prev;
      delete_object (obj_ptr);
      obj_ptr = obj_ptr_prev;
    }
    delete_object (method_list -> local_objects.objs);
  }
  if (method_list -> local_cvars) {
    for (cvar_ptr = method_list -> local_cvars;
	 cvar_ptr && cvar_ptr -> next;
	 cvar_ptr = cvar_ptr -> next)
      ;
    while (cvar_ptr != method_list -> local_cvars) {
      cvar_ptr_prev = cvar_ptr -> prev;
      _delete_cvar (cvar_ptr);
      cvar_ptr = cvar_ptr_prev;
    }
    _delete_cvar (method_list -> local_cvars);
  }
  if (method_list -> n_params) {
    for (param_idx = 0; param_idx < method_list -> n_params; param_idx++) {
      free (method_list -> params[param_idx]);
    }
  }
  free (method_list -> src);
  free (method_list);
}

void delete_class_library (void) {

  OBJECT *class_ptr, *class_ptr_prev;

  class_ptr = last_class;

  while (TRUE) {

    if (class_ptr -> instance_methods) {
      delete_methods (class_ptr -> instance_methods);
      class_ptr -> instance_methods = NULL;
    }

    if (class_ptr -> class_methods) {
      delete_methods (class_ptr -> class_methods);
      class_ptr -> class_methods = NULL;
    }

    if (class_ptr == classes)
      break;
    class_ptr = class_ptr -> prev;
  }

  class_ptr = last_class;
  while (class_ptr != classes) {
    class_ptr_prev = class_ptr -> prev;
    delete_object (class_ptr);
    class_ptr = class_ptr_prev;
  }
  delete_object (classes);
}

void push_input_declaration (char *path, char *name, int src_line) {

  CLASSLIB *c;

  if (lib_includes_ptr == 0)
    _error ("push_input_declaration: stack overflow.");

  if ((c = (CLASSLIB *)calloc (1, sizeof (CLASSLIB))) == NULL)
    _error ("push_input_declaration: %s.", strerror (errno));

  strcpy (c -> sig, "CLASSLIB");
  strcpy (c -> path, path);
  strcpy (c -> name, name);
  strcpy (c -> included_from_filename, __source_filename ());
  c -> included_from_line = src_line;

  input_declarations[input_declarations_ptr--] = c;

  if (warn_classlib_opt) {
    debug ("%d. %s.", input_declarations_ptr + 1, c -> path);
  
    if (input_declarations_ptr + 2 < MAXARGS) {
      int i;
      for (i = input_declarations_ptr + 2; i <= MAXARGS; i++)
	debug ("\tFrom %s, Line %d.", input_declarations[i] -> path, 1);
    }
    debug ("\tFrom %s, Line %d.", input_source_file, error_line);
  }
  
}

CLASSLIB *pop_input_declaration (void) {

  CLASSLIB *c;
  if (input_declarations_ptr > MAXARGS)
    _error ("pop_input_declaration: stack underflow.");

  if (warn_classlib_opt) {
    debug ("%d. Exiting %s.", 
	   input_declarations_ptr + 1, 
	   input_declarations[input_declarations_ptr + 1] -> path);
  }

  c = input_declarations[++input_declarations_ptr];
/*   __ctalkRtSaveSourceFileName (c -> included_from_filename); */
  error_line = c -> included_from_line;
  input_declarations[input_declarations_ptr] = NULL;
  return c;
}

void init_library_include_stack (void) {
  lib_includes_ptr = MAXARGS;
  input_declarations_ptr = MAXARGS;
  lib_cache = cache_ptr = NULL;
}

