/* $Id: methodbuf.c,v 1.3 2009/01/15 15:14:00 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.
*/

/*
 *  Essentially the same as fnbuf.c but called when reparsing in 
 *  output_imported_methods.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "lex.h"
#include "list.h"
#include "bufmthd.h"

extern int library_input;            /* Declared in class.c.              */
extern I_PASS interpreter_pass;      /* Declared in main.c.               */

static BUFFERED_METHOD method_buffer[MAXARGS+1];
static int method_buffer_ptr;

int buffer_method_output = FALSE;

int format_method_output = FALSE;

void init_method_buffer (void) {
  int i;

  method_buffer_ptr = -1;

  for (i = 0; i <= MAXARGS + 1; i++)
    method_buffer[i].src = (LIST *)NULL;
}

void method_init_statement (char *buf) {

  LIST *l;

  if ((interpreter_pass == method_pass) && 
      (method_buffer_ptr >= 0)) {

    l = new_list ();
    l -> data = (void *)strdup (buf);

    if (method_buffer[method_buffer_ptr].init == NULL) {
      method_buffer[method_buffer_ptr].init = l;
    } else {
      list_push (&method_buffer[method_buffer_ptr].init, &l);
    }

  }
}

/*
 *  Buffer method statements only if called during method_pass;
 *  i.e., from new_method ().
 */

void begin_method_buffer (METHOD *method) {
  if (interpreter_pass == method_pass) {
    ++method_buffer_ptr;
    method_buffer[method_buffer_ptr].method = method;
  }
}

void end_method_buffer (void) {
   if (interpreter_pass == method_pass)
     --method_buffer_ptr;
}

void buffer_method_statement (char *s) {

  LIST *l;

  if ((interpreter_pass == method_pass) &&
      (method_buffer_ptr >= 0)) {

    l = new_list ();
    l -> data = (void *)strdup (s);

    if (method_buffer[method_buffer_ptr].src == NULL) {
      method_buffer[method_buffer_ptr].src = l;
    } else {
      list_push (&method_buffer[method_buffer_ptr].src, &l);
    }

  }
}

static MESSAGE *method_buf_messages[P_MESSAGES+1]; /* Method message stack.  */
static int method_buf_message_ptr = P_MESSAGES;    /* Stack pointer.         */

MESSAGE **method_buf_message_stack (void) { return method_buf_messages; }

int get_method_buf_message_ptr (void) { return method_buf_message_ptr; }

int method_buf_message_push (MESSAGE *m) {
  if (method_buf_message_ptr == 0) {
    warning (m, "method_buf_message_push: stack overflow.");
    return ERROR;
  }
  method_buf_messages[method_buf_message_ptr--] = m;
  return method_buf_message_ptr;
}

/*
 *   Call format method on any method buffers after
 *   methodbuffer[method_buffer_ptr], which is the buffer of the
 *   current method.  Any buffers after are methods that the library
 *   input has returned from.
 */

void unbuffer_method_statements (void) {

  int i,
    r;
  LIST *l;

  for (i = method_buffer_ptr + 1; method_buffer[i].src; i++) {
    if (method_buffer[i].src) {
      if ((r = format_method (method_buffer[i])) == SUCCESS) {
	while ((l = list_unshift (&method_buffer[i].src)) != NULL) {
	  free (l -> data);
	  free (l);
	}
	while ((l = list_unshift (&method_buffer[i].init)) != NULL) {
	  free (l -> data);
	  free (l);
	}
	method_buffer[i].src = NULL;
	method_buffer[i].method = NULL;
	method_buffer[i].init = NULL;
      }
    }
  }
}

int format_method (BUFFERED_METHOD b) {

  int i,
    stack_start,
    stack_end,
    block_level;
  char *tmpbuf;
  long long int buflength;
  Boolean need_init;
  enum {
    fn_decl,
    fn_var_decl,
    fn_body,
    fn_null
  } fn_state;
  LIST *t;
  MESSAGE *m, 
    *m_decl;


  format_method_output = TRUE;

  for (t = b.src, buflength = 0LL; t; t = t -> next)
    buflength += strlen ((char *)t -> data);

  if ((tmpbuf = (char *)calloc (buflength + 1LL, sizeof (char))) == NULL)
    _error ("unbuffer_method_statements: %s\n", strerror (errno));
			 
  for (t = b.src, *tmpbuf = 0; t; t = t -> next)
    strcat (tmpbuf, (char *)t -> data);

  stack_start = P_MESSAGES;

  stack_end = tokenize (method_buf_message_push, tmpbuf);

  free (tmpbuf);

  for (i = stack_start, fn_state = fn_null, block_level = 0, need_init = True; 
       i > stack_end; i--) {

    m = method_buf_messages[i];

    if (!m || !IS_MESSAGE (m)) break;

    switch (m -> tokentype)
      {
      case LABEL:
  	if ((is_c_fn_declaration_msg (method_buf_messages, i)) &&
  	    !is_c_function_prototype_declaration (method_buf_messages, i)) {
 	  ++block_level;
  	  for ( ; method_buf_messages[i] -> tokentype != OPENBLOCK; i--) 
  	    __fileout (method_buf_messages[i] -> name);
 	  __fileout (method_buf_messages[i] -> name);
 	} else {
	  if (is_c_var_declaration_msg (method_buf_messages, i, FALSE)) {
	    int k;
	    k = find_declaration_end (method_buf_messages, i);
	    for ( ; i >= k; i--) {
	      m_decl = method_buf_messages[i];
	      if (m_decl && IS_MESSAGE (m_decl))
		__fileout (method_buf_messages[i] -> name);
	    }
	    ++i;
	    fn_state = fn_var_decl;
	  } else {
	    if (fn_state != fn_body) {
	      fn_state = fn_body;
	      if (need_init == True)
		generate_method_init (b.method, b.init);
	      need_init = False;
	    }
	    __fileout (method_buf_messages[i] -> name);
	  }
 	}
	break;
      case OPENBLOCK:
	++block_level;
	if ((block_level > 1) && need_init) {
	  generate_method_init (b.method, b.init);
	  need_init = False;
	}
	__fileout (method_buf_messages[i] -> name);
	break;
      case CLOSEBLOCK:
	--block_level;
	if (!block_level && need_init &&
	    fn_closure (method_buf_messages, i, get_frame_pointer ()))
	  generate_method_init (b.method, b.init);
	__fileout (method_buf_messages[i] -> name);
	break;
      default:
	__fileout (method_buf_messages[i] -> name);
	break;
      }
  }

  DELETE_MESSAGES(method_buf_messages,method_buf_message_ptr,P_MESSAGES)

    format_method_output = FALSE;

  return SUCCESS;
}

/* 
 *  Generate a method init.  __fileout () here instead of re-buffering,
 *  because generate_init () can use a temp file.  TO DO - Determine
 *  if we can use generate_init () instead.
 */

void generate_method_init (METHOD *method, LIST *init) {

  char buf[MAXMSG];
  LIST *l;

  if (method -> no_init) return;

  sprintf (buf, "\n     {\n");
  __fileout (buf);

  sprintf (buf, "     %s (\"%s\");\n", SRC_FILE_SAVE_FN, source_filename ());
  __fileout (buf);

  sprintf (buf, "     __ctalk_initLocalObjects ();\n");
  __fileout (buf);

  /*
   *  This call is probably redundant with the call that is made
   *  when the method is defined.  If necessary, it can be uncommented
   *  here.
   */
/*   sprintf (buf, "     __ctalkMethodReturnClass (\"%s\");\n",  */
/* 	   method -> returnclass); */
/*   __fileout (buf); */

  for (l = init; l; l = l -> next) {
    __fileout ("     ");
    __fileout ((char *) l -> data);
  }

  sprintf (buf, "     }\n\n");
  __fileout (buf);

}
