/* $Id: mthdrf.c,v 1.6 2009/01/04 20:28:41 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 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"

extern CLASSLIB *lib_includes[MAXARGS + 1];  /* Declared in class.c.  */
extern int lib_includes_ptr;
extern CLASSLIB *input_declarations[MAXARGS+1];
extern int input_declarations_ptr;

extern MESSAGE *var_messages[N_MESSAGES + 1];  /* Declared in cvars.c. */
extern int var_messageptr;

extern HASHTAB declared_method_names;    /* Declared in method.c. */
extern HASHTAB declared_method_selectors;
extern HASHTAB declared_global_variables; /* Declared in cvars.c. */
I_PASS interpreter_pass;    /* Declared in main.c.                      */

extern int library_input;   /* Declared in class.c.  TRUE if evaluating */
                            /* class libraries.                         */

extern LIST *input_methods; /* Declared in infiles.c.                   */

static int old_input_size = 0;
static char *input = NULL;

LIST *output_method_prototypes = NULL;

int method_prototypes (char *p_lib_fn, CLASSLIB *c) {

  int idx, tmpsize, end_ptr;
  MESSAGE *m;
  char *d_buf;
  LIST *l;

  if (!file_exists (p_lib_fn))
    _error ("%s: %s.", p_lib_fn, strerror (errno));
  
  tmpsize = file_size (p_lib_fn);
  if (tmpsize > old_input_size) {
    old_input_size = tmpsize+1;
    if ((input = (char *)realloc (input, tmpsize+1)) == NULL)
      _error ("parse_vars: %s.", strerror (errno));
  }
  memset ((void *)input, 0, old_input_size);
  read_file (input, p_lib_fn);

  end_ptr = tokenize (var_message_push, input);

  for (idx = N_MESSAGES; idx >= end_ptr; idx--) {

    m = var_messages[idx];

    if (M_ISSPACE(m)) continue;

    switch (M_TOK(m))
      {
      case LABEL:
	if (!strcmp (M_NAME(m), "instanceMethod") ||
	    !strcmp (M_NAME(m), "classMethod")) {
	  int _class_idx, _selector_idx, _alias_idx;
	  int _open_arg_idx, _close_arg_idx;
	  int _open_block_idx, _close_block_idx;
	  if (((_class_idx = prevlangmsg (var_messages, idx)) == ERROR) ||
	      ((_selector_idx = nextlangmsg (var_messages, idx)) == ERROR) ||
	      ((_alias_idx = nextlangmsg (var_messages, _selector_idx)) == ERROR))
	    _error ("Parse error.\n");

	  if (M_TOK(var_messages[_alias_idx]) == OPENPAREN) {
	    _open_arg_idx = _alias_idx;
	  } else {
	    if ((_open_arg_idx = nextlangmsg (var_messages, _alias_idx)) == ERROR)
	      _error ("Parse error.\n");
	  }
	  if ((_close_arg_idx = match_paren (var_messages, 
	     _open_arg_idx, get_stack_top (var_messages))) == ERROR)
	    _error ("Parse error.\n");

	  if ((_open_block_idx = nextlangmsg (var_messages, _alias_idx))
	      == ERROR)
	    _error ("Parse error.\n");
	  _close_block_idx = 
	    find_function_close (var_messages, _close_arg_idx);
	  d_buf = collect_tokens (var_messages, _class_idx, 
				  _close_block_idx);
	  l = new_list ();
	  l -> data = d_buf;
	  list_push (&(c -> _method_proto), &l);
	}
	break;
      }
  }
  DELETE_MESSAGES(var_messages,var_messageptr,N_MESSAGES)

  return SUCCESS;
}

#define INPUT_DECLARATION input_declarations[input_declarations_ptr+1]

int is_method_proto (char *classname, char *selector) {
  LIST *l;
  char buf[MAXMSG];
  if (lib_includes_ptr < MAXARGS) {
    if (!strcmp (C_LIB_INCLUDE->name, classname)) {
      for (l = C_LIB_INCLUDE->_method_proto; l; l = l -> next) {
	sprintf (buf, "%s instanceMethod %s ", classname, selector);
	if (!strncmp ((char *)l->data, buf, strlen (buf)))
	  return TRUE;
      }
    }
  }
  if (input_declarations_ptr < MAXARGS) {
    for (l = INPUT_DECLARATION->_method_proto; l; l = l -> next) {
      sprintf (buf, "%s instanceMethod %s ", classname, selector);
      if (!strncmp ((char *)l->data, buf, strlen (buf)))
	return TRUE;
      sprintf (buf, "%s classMethod %s ", classname, selector);
      if (!strncmp ((char *)l->data, buf, strlen (buf)))
	return TRUE;
    }
  }
  return FALSE;
}

int method_proto_is_output (char *classname, char *selector) {
  LIST *l;
  char buf[MAXMSG];
  for (l = output_method_prototypes; l; l = l -> next) {
    sprintf (buf, "%s_instance_%s", classname, selector);
    if (!strncmp ((char *)l->data, buf, strlen (buf)))
      return TRUE;
  }
  for (l = output_method_prototypes; l; l = l -> next) {
    sprintf (buf, "%s_class_%s", classname, selector);
    if (!strncmp ((char *)l->data, buf, strlen (buf)))
      return TRUE;
  }
  return FALSE;
}

static char *method_proto (char *classname, char *selector) {
  LIST *l;
  char buf[MAXMSG];
  if (lib_includes_ptr < MAXARGS) {
    if (!strcmp (C_LIB_INCLUDE->name, classname)) {
      for (l = C_LIB_INCLUDE->_method_proto; l; l = l -> next) {
	sprintf (buf, "%s instanceMethod %s ", classname, selector);
	if (!strncmp ((char *)l->data, buf, strlen (buf)))
	  return (char *)l -> data;
      }
    }
  }
  if (input_declarations_ptr < MAXARGS) {
    for (l = INPUT_DECLARATION->_method_proto; l; l = l -> next) {
      sprintf (buf, "%s instanceMethod %s ", classname, selector);
      if (!strncmp ((char *)l->data, buf, strlen (buf)))
	return (char *)l -> data;
      sprintf (buf, "%s classMethod %s ", classname, selector);
      if (!strncmp ((char *)l->data, buf, strlen (buf)))
	return (char *)l -> data;
    }
  }
  return NULL;
}

void input_prototypes (char *fn) {
  method_prototypes (fn, input_declarations[input_declarations_ptr+1]);
}

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

OBJECT *method_from_prototype (char *methodname) {

  int stack_start, stack_end;
  char *proto_buf;
  char *tmpbuf,           /* Buffer for collected method body tokens.      */
    *srcbuf;              /* Buffer for method with translated declaration.*/
  int rcvr_ptr,           /* Index of the class message, which is the
			     same class as the method message's receiver 
			     object.                                      */
    method_msg_ptr,
    param_start_ptr,     /* Index of parameter start.                     */
    param_end_ptr,       /* Index of parameter end.                       */
    method_start_ptr,    /* Index of function body open block.            */
    method_end_ptr,      /* End of the method body.                       */
    alias_ptr,           /* Index of the method alias, if any.            */
    funcname_ptr,        /* Index of the function name.                   */
    n_params,            /* Number of parameters in declaration.          */
    i,
    buflength;
  int have_c_param = 0;
  int param_class_exception;
   int x_src_size,
     x_chars_read;
  MESSAGE *m_method,  /* Message of the, "method," keyword. */
    *m_rcvr;          /* Message of the class object receiver. */

  METHOD  *n_method,
    *tm,
    *rcvr_method;

  char *params[MAXARGS],
    c_param[MAXMSG],
    funcname[MAXLABEL],
    alias[MAXLABEL],
    tmpname[FILENAME_MAX],  /* For method buffering. */
    *x_srcbuf;

  CVAR *__cvar_dup;
  CFUNC *__shadowed_c_fn;
  
  I_PASS prev_pass;



/*     i, */


  stack_start = N_MESSAGES;
  proto_buf = method_proto (rcvr_class_obj -> __o_name, methodname);
  stack_end = tokenize (var_message_push, proto_buf);

  if (strcmp (M_NAME(var_messages[stack_start]), 
	      rcvr_class_obj -> __o_name)) {
    _error ("Parser error.\n");
  } else {
    rcvr_ptr = stack_start;
    m_rcvr = var_messages[rcvr_ptr];
    m_rcvr -> obj = rcvr_class_obj;
  }

  if ((method_msg_ptr = nextlangmsg (var_messages, rcvr_ptr)) == ERROR) {
    _error ("Parser error.\n");
  } else {
    m_method = var_messages[method_msg_ptr];
    m_method -> receiver_obj = m_rcvr -> obj;
    m_method -> tokentype = METHODMSGLABEL;
  }

  /* 
   *  Find the number of args required by the method declaration.
   *  TO DO - Determine if we can use method_declaration_info ()
   *  here also - we only need n_params here.
   */

   if ((method_end_ptr = 
	find_function_close (var_messages, method_msg_ptr))
       == ERROR) {
     int e_prev_idx, e_next_idx;
     MESSAGE *m_error_rcvr, *m_error_name;
     if ((e_prev_idx = prevlangmsg (var_messages,
				    method_msg_ptr)) != ERROR) {
       m_error_rcvr = var_messages[e_prev_idx];
     } else {
       m_error_rcvr = NULL;
     }
     if ((e_next_idx = nextlangmsg (var_messages,
				    method_msg_ptr)) != ERROR) {
       m_error_name = var_messages[e_next_idx];
     } else {
       m_error_name = NULL;
     }
     error (m_method, "%s method %s : Can't find method close.",
 	   (m_error_rcvr ? M_NAME(m_error_rcvr) : ""),
 	   (m_error_name ? M_NAME(m_error_name) : ""));
   }

   if ((param_start_ptr = 
        scanforward (var_messages, method_msg_ptr,
 		    method_end_ptr, OPENPAREN)) == ERROR)
     error (m_method, "Missing parameters in method declaration.");

   if ((param_end_ptr = scanforward (var_messages, param_start_ptr,
 				    method_end_ptr, CLOSEPAREN)) == ERROR)
     error (m_method, "Missing parameters in method declaration.");

   if ((method_start_ptr = scanforward (var_messages, param_start_ptr,
					method_end_ptr, OPENBLOCK)) == ERROR)
     error (m_method, "Missing start of method body.");


   if ((n_method = (METHOD *)calloc (1, sizeof (METHOD))) == NULL)
     _error ("method_from_prototype: %s.", strerror (errno));
   strcpy (n_method -> sig, "METHOD");

   new_methods[new_method_ptr--] = n_method;

   fn_param_declarations (var_messages, param_start_ptr, param_end_ptr,
			  params, &n_params);

  /*
   *   Reversing the order of the parameters here helps to 
   *   ensure that the run-time code pushes the arguments 
   *   in the correct order for nested method calls.
   */
   n_method -> n_params = n_params;
   for (i = n_params - 1, param_class_exception = ERROR; i >= 0; i--) {
     /* TO DO - Possibly add error checking in method_param (). 
	See the comment below. */
     /* TO DO - Make sure that method_param has the correct
	error line, column, and source file name. */
     n_method -> params[i] = method_param (params[i]);

     if (! param_class_exists (n_method -> params[i] -> class)) {
      /*
       *  NOTE - This should be handled as an exception in
       *  a future release.
       */
       param_class_exception = i;
     }
     if ((i == 0) && 
	 (n_method -> params[i] -> attrs & PARAM_C_PARAM)) {
       char *s;
       s = strchr (params[i], ' ');
       have_c_param = TRUE;
       strcpy (c_param, s);
     }
     if (n_method -> params[i] -> attrs & PARAM_VARARGS_PARAM)
       n_method -> varargs = TRUE;
     free (params[i]);
   }

   method_declaration_msg (var_messages, method_msg_ptr, param_start_ptr,
			   funcname, &funcname_ptr, alias, &alias_ptr);

#ifdef DEBUG_UNDEFINED_PARAMETER_CLASSES
   if (param_class_exception != ERROR)
     warning (var_messagesmethod_msg_ptr],
       "Undefined parameter class %s. (Method %s, Class %s).",
       n_method -> params[param_class_exception] -> class,
       ((*alias) ? alias : funcname),
       rcvr_class_obj -> __o_name);
#endif

     strcpy (n_method -> name, ((*alias) ? alias : funcname));
     n_method -> n_args = 0;
     n_method -> error_line = m_method -> error_line;
     n_method -> error_column = m_method -> error_column;
  /* This is a kludge - all methods are marked as imported,
     until we can get the method buffering straightened out. */
     n_method -> imported = True;
     n_method -> queued = False;
  
     sprintf (n_method -> selector, "%s%s%s",
	      rcvr_class_obj -> __o_name, INSTANCE_SELECTOR, funcname);

     strcpy (n_method -> returnclass, rcvr_class_obj -> __o_name);

     for (rcvr_method = rcvr_class_obj -> instance_methods; rcvr_method;
	  rcvr_method = rcvr_method -> next) {
       if (!strcmp (rcvr_method -> name, n_method -> name)) {
	 warning (m_method, "Redefinition of method, \"%s.\" (Class %s).",
		  n_method -> name, rcvr_class_obj -> __o_name);
	 new_methods[new_method_ptr++] = NULL;
	 free (n_method);
	 return NULL;
       }
     }

     generate_instance_method_definition_call (rcvr_class_obj -> __o_name,
					       ((*alias) ? alias : funcname),
				       n_method -> selector, n_params);

   for (i = 0; i < n_params; i++) {
     if (!(n_method -> params[i] -> attrs & PARAM_C_PARAM))
       generate_instance_method_param_definition_call (rcvr_class_obj -> __o_name,
 					   ((*alias) ? alias : funcname),
 					   n_method -> params[i] -> name,
 					   n_method -> params[i] -> class,
 					   n_method -> params[i] -> is_ptr,
				       n_method -> params[i] -> is_ptrptr
						       );
   }

   if ((__cvar_dup = _hash_remove (declared_global_variables,
 				  n_method -> name)) != NULL) {
     warning (message_stack_at(method_msg_ptr),
	      "Method %s shadows a global variable.", n_method -> name);
     unlink_global_cvar (__cvar_dup);
     _delete_cvar (__cvar_dup);
   }

   if ((__shadowed_c_fn = get_function (n_method -> name)) != NULL) {
       warning (message_stack_at(method_msg_ptr),
 	       "Method %s shadows a function.", n_method -> name);
   }

  /* Collect tokens from the end of the ctalk declaration to the end of
     the method.
  */

   tmpbuf =
     collect_tokens (var_messages, method_start_ptr, method_end_ptr);

   if (have_c_param) {
     buflength = strlen (METHOD_RETURN) + strlen (" ") + 
       strlen (c_param) + strlen (" ( ) ") + 
       strlen (n_method -> selector) + strlen (" ") + strlen (tmpbuf);
   } else {
     buflength = strlen (METHOD_RETURN) + strlen (" ") + 
       strlen (METHOD_C_PARAM) + strlen (" ") +
       strlen (n_method -> selector) + strlen (" ") + strlen (tmpbuf);
   }

   if ((srcbuf = (char *)calloc (buflength + 1, sizeof (char))) == NULL)
     _error ("new_instance_method: %s.", strerror (errno));

   if (n_method -> params[0] &&
       (n_method -> params[0] -> attrs & PARAM_C_PARAM)) {
     sprintf (srcbuf, "%s %s (%s) %s", METHOD_RETURN, n_method -> selector,
  	     c_param, tmpbuf);
    } else {
     sprintf (srcbuf, "%s %s %s %s", METHOD_RETURN, n_method -> selector, 
 	     METHOD_C_PARAM, tmpbuf);
    }

   prev_pass = interpreter_pass;
   interpreter_pass = method_pass;

  /*
    TO DO -
    1. Make sure that methods can be resolved as externs - and also
       class declarations, which might be initialized in a method's
       init block.
  */

   create_tmp ();
   begin_method_buffer (n_method);
   parse (srcbuf, (long long) strlen (srcbuf));
   end_method_buffer ();
   unbuffer_method_statements ();

   close_tmp ();
   generate_instance_method_return_class_call (rcvr_class_obj -> __o_name,
 				     n_method -> name,
 				     n_method -> returnclass);
   strcpy (tmpname, get_tmpname ());
   x_src_size = file_size (tmpname);
   if ((x_srcbuf = (char *) calloc (x_src_size + 1, sizeof (char))) == NULL)
     error (m_method, "new_instance_method %s (class %s): %s.",
 	   n_method -> name, rcvr_class_obj -> __o_name,
 	   strerror (errno));
#ifdef __DJGPP__
     errno = 0;
     x_chars_read = read_file (x_srcbuf, tmpname);
     if (errno)
     error (m_method, "new_instance_method %s (class %s): %s.",
 	   n_method -> name, rcvr_class_obj -> __o_name,
	    strerror (errno));
#else
   if ((x_chars_read = read_file (x_srcbuf, tmpname)) != x_src_size)
     error (m_method, "new_instance_method %s (class %s): %s.",
 	   n_method -> name, rcvr_class_obj -> __o_name,
 	   strerror (errno));
#endif
   n_method -> src = strdup (x_srcbuf);
   free (x_srcbuf);

   queue_method_for_output (rcvr_class_obj -> __o_name, n_method);
   if (!library_input) {

    /* Replace the unevaled method's tokens with whitespace so
       the parent parser doesn't evaluate the method body again. */
    /* TO DO - 
       Inform the parent parser simply to skip the frames of the 
       source method, if possible, without adding another state to 
       the parser. */
     for (i = rcvr_ptr; i >= method_end_ptr; i--) {
       MESSAGE *m_tmp;
        m_tmp = var_messages[i];
        if ((m_tmp -> tokentype != WHITESPACE) &&
 	   (m_tmp -> tokentype != NEWLINE)) {
 	 m_tmp -> tokentype = WHITESPACE;
 	 strcpy (m_tmp -> name, " ");
        }
     }
   }

   unlink_tmp ();

   if (rcvr_class_obj -> instance_methods == NULL) {
     rcvr_class_obj -> instance_methods = n_method;
   } else {
     for (tm = rcvr_class_obj -> instance_methods; tm -> next; tm = tm -> next)
       ;
     tm -> next = n_method;
     n_method -> prev = tm;
   }
   _hash_put (declared_method_names, n_method -> name, n_method -> name);
   _hash_put (declared_method_selectors, n_method -> selector, n_method -> selector);

   if (output_method_prototypes == NULL) {
     output_method_prototypes = new_list ();
     output_method_prototypes -> data = strdup (n_method -> selector);
   } else {
     LIST *l;
     l = new_list ();
     l -> data = strdup (n_method -> selector);
     list_push (&output_method_prototypes, &l);
   }

   interpreter_pass = prev_pass;

   new_methods[++new_method_ptr] = NULL;
  
  DELETE_MESSAGES(var_messages,var_messageptr,N_MESSAGES)

   free (srcbuf);
   free (tmpbuf);

  return NULL;
}

