/* $Id: rt_stdarg.c,v 1.2 2008/10/28 17:41:12 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 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 <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "rtinfo.h"
#include "typeof.h"

extern RADIX radix_of (char *);

/*
 *  This uses the stdarg.h format, which normally requires that the
 *  first argument be a char *fmt.
 *
 *  TO DO - Perform scalar-symbol translations, and scalar and 
 *          pointer args in the same call.  
 *          __ctalkCPrintFmtToCtalkFmt performs this translation
 *          if called first.
 *  TO DO - Argument type mismatches should raise exceptions.
 *  TO DO - For functions with stream arguments (fscanf, sscanf, etc)
 *          allow catalog of stream types and number.
 */

char fmt_tokens[512][8192];
int fmt_tok_idx;

char ptr_args[512][8192];
int ptr_arg_idx;

OBJECT *__ctalkLibcFnWithMethodVarArgs (int (*fn)(), METHOD *method,
					char *return_class_name) {
  char fmtbuf[MAXMSG], retbuf[MAXMSG];
  OBJECT *fmt_value_obj, *return_obj;
  int retval = 0, lextype;  /* Avoid a warning. */
  RADIX r;
  STDARG_CALL_INFO stdarg_call_info;
  OBJECT *arg0_obj = NULL;        /* Avoid a warning. */
  int k;
  OBJECT *read_value_obj;
  
#if ((defined(__linux__) && defined(__i386__)) || \
      defined (__CYGWIN__) || defined (__DJGPP__) || \
     (defined(__sparc__) && defined(__svr4__)))
  stdarg_call_info.libcfn = fn;

  if ((stdarg_call_info.libcfn == (int (*)())fscanf) || 
      (stdarg_call_info.libcfn == (int (*)())sscanf) || 
      (stdarg_call_info.libcfn == (int (*)())fprintf) || 
      (stdarg_call_info.libcfn == (int (*)())sprintf)) {
    stdarg_call_info.fmt_arg_n = 1;
    if ((arg0_obj = 
	 __ctalkGetInstanceVariable (method->args[0], "value", FALSE))
	== NULL)
      arg0_obj = method -> args[0];
    lextype = _lextype (arg0_obj -> __o_value);
    r = radix_of (arg0_obj -> __o_value);
    if ((lextype == INTEGER_T) &&
	((r == hexadecimal) || (r == decimal))) {
      sscanf (arg0_obj -> __o_value, FMT_0XHEX(&(stdarg_call_info.arg0)));
    } else {
      stdarg_call_info.arg0 = arg0_obj -> __o_value;
    }
  } else {
    stdarg_call_info.fmt_arg_n = 0;
  }

  if ((stdarg_call_info.libcfn == (int (*)())fprintf) || 
      (stdarg_call_info.libcfn == (int (*)())sprintf)) {
    stdarg_call_info.stream_write_fn = TRUE;
  } else {
    stdarg_call_info.stream_write_fn = FALSE;
  }

  if ((fmt_value_obj = 
       __ctalkGetInstanceVariable (method->args[stdarg_call_info.fmt_arg_n], 
				   "value", FALSE))
      == NULL)
    fmt_value_obj = method -> args[stdarg_call_info.fmt_arg_n];

  if (strcmp (fmt_value_obj -> __o_classname, STRING_CLASSNAME)) {
    _warning ("__ctalkLibcFnWithMethodArgs: Argument type mismatch.\n");
    return NULL;
  }

  if (*fmt_value_obj -> __o_value == '"')
    strcpy (fmtbuf, TRIM_LITERAL(fmt_value_obj -> __o_value));
  else
    strcpy (fmtbuf, fmt_value_obj -> __o_value);

  tokenize_fmt (fmtbuf);

/*   if (stdarg_call_info.stream_write_fn) { */
/*     int k, l; */
/*     OBJECT *read_value_obj; */
/*     for (l = 0, k = stdarg_call_info.fmt_arg_n + 1;  */
/* 	 k <= stdarg_call_info.fmt_arg_n + fmt_tok_idx; k++, l++) { */
/*       if ((read_value_obj =  */
/* 	   __ctalkGetInstanceVariable (method->args[k],  */
/* 				       "value", FALSE)) == NULL) */
/* 	read_value_obj = method -> args[k]; */
/*       strcpy (ptr_args[l], read_value_obj -> __o_value); */
/*     } */
/*   } else { */
/*     if ((stdarg_call_info.fmt_arg_n == 0) &&  */
/* 	(stdarg_call_info.libcfn == (int (*)())printf)) { */
/*       int k, l; */
/*       OBJECT *read_value_obj; */
/*       for (l = 0, k = stdarg_call_info.fmt_arg_n + 1;  */
/* 	   k <= stdarg_call_info.fmt_arg_n + fmt_tok_idx; k++, l++) { */
  for (k = stdarg_call_info.fmt_arg_n + 1, ptr_arg_idx = 0; 
       method->args[k]; k++) {
    if ((read_value_obj = 
	 __ctalkGetInstanceVariable (method->args[k], 
				     "value", FALSE)) == NULL)
      read_value_obj = method -> args[k];
    strcpy (ptr_args[ptr_arg_idx++], read_value_obj -> __o_value);
  }
/*     } */
/*   } */

  switch (stdarg_call_info.fmt_arg_n)
    {
    case 0:
      retval = __call_fn_w_args_fmtarg0 (fmtbuf, method, &stdarg_call_info);
      break;
    case 1:
      retval = __call_fn_w_args_fmtarg1 (fmtbuf, method, &stdarg_call_info);
      break;
    }

  /*
   *  All functions in the C library that use stdargs return int.
   */

  if (!strcmp (return_class_name, INTEGER_CLASSNAME)) {
    __ctalkDecimalIntegerToASCII (retval, retbuf);
    if (retval > 0)
      args_to_method_args (method, &stdarg_call_info);
    return_obj = 
      __ctalkCreateObjectInit ("result", INTEGER_CLASSNAME, 
			       INTEGER_SUPERCLASSNAME, LOCAL_VAR,
			       retbuf);
  } else {
 _warning ("__ctalkLibCFnWithMethodVarArgs: Unimplemented return class %s.\n",
	   return_class_name);
 return_obj = NULL;
  }

  return return_obj;

#else /* #if defined(__linux__) && defined(__i386__) ...*/

  _warning ("Functions that use stdargs are not implemented for this system.\n");
  return NULL;
#endif
}

int __call_fn_w_args_fmtarg0 (char *fmt, METHOD *method, 
			      STDARG_CALL_INFO *stdarg_call_info) {

  int i, j, retval = 0;   /* Avoid a warning. */

  if (stdarg_call_info -> libcfn == (int (*)())printf) {
    for (i = 0, j = 0; (i <= fmt_tok_idx) && fmt_tokens[i]; i++) {
      if (strchr (fmt_tokens[i], '%')) {
	retval += printf (fmt_tokens[i], ptr_args[j++]);
      } else {
	retval += printf (fmt_tokens[i]);
      }
    }
    return retval;
  }

  if (stdarg_call_info -> libcfn == (int (*)())scanf) {
    for (i = 0, j = 0; (i <= fmt_tok_idx) && fmt_tokens[i]; i++) {
      if (strchr (fmt_tokens[i], '%')) {
	retval += scanf (stdarg_call_info->arg0, fmt_tokens[i], ptr_args[j++]);
      } else {
	retval += scanf (stdarg_call_info->arg0, fmt_tokens[i]);
      }
    }
    return retval;
  }
  return ERROR;
}

int __call_fn_w_args_fmtarg1 (char *fmt, METHOD *method, 
			      STDARG_CALL_INFO *stdarg_call_info) {

  int i, j, retval;
  char tmparg0buf[MAXMSG];
  char *arg0_buf;

  retval = 0;

  if (stdarg_call_info -> libcfn == (int (*)())sprintf) {
    arg0_buf = stdarg_call_info -> arg0;
    *arg0_buf = '\0';
    for (i = 0, j = 0; (i <= fmt_tok_idx) && fmt_tokens[i]; i++) {
      if (strchr (fmt_tokens[i], '%')) {
	retval += sprintf (tmparg0buf, fmt_tokens[i], ptr_args[j++]);
      } else {
	retval += sprintf (tmparg0buf, fmt_tokens[i]);
      }
      strcat (arg0_buf, tmparg0buf);
    }
    return retval;
  }

  if (stdarg_call_info -> libcfn == (int (*)())fprintf) {
    arg0_buf = stdarg_call_info -> arg0;
    *arg0_buf = '\0';
    for (i = 0, j = 0; (i <= fmt_tok_idx) && fmt_tokens[i]; i++) {
      if (strchr (fmt_tokens[i], '%')) {
	retval += fprintf (stdarg_call_info->arg0, fmt_tokens[i], ptr_args[j++]);
      } else {
	retval += fprintf (stdarg_call_info->arg0, fmt_tokens[i]);
      }
    }
    return retval;
  }


  if (stdarg_call_info -> libcfn == (int (*)())fscanf) {
    arg0_buf = stdarg_call_info -> arg0;
    *arg0_buf = '\0';
    for (i = 0, j = 0; (i <= fmt_tok_idx) && fmt_tokens[i]; i++) {
      if (strchr (fmt_tokens[i], '%')) {
	retval += fscanf (stdarg_call_info->arg0, fmt_tokens[i], ptr_args[j++]);
      } else {
	retval += fscanf (stdarg_call_info->arg0, fmt_tokens[i]);
      }
    }
    return retval;
  }

  if (stdarg_call_info -> libcfn == (int (*)())sscanf) {
    int idx = 0;
    arg0_buf = stdarg_call_info -> arg0;
    for (i = 0, j = 0; (i <= fmt_tok_idx) && fmt_tokens[i]; i++) {
      if (strchr (fmt_tokens[i], '%')) {
	retval += sscanf (arg0_buf, fmt_tokens[i], ptr_args[j++]);
      } else {
	retval += sscanf (arg0_buf, fmt_tokens[i]);
      }
      /*
       *  Skip any whitespace after the input tokens. 
       */
      if (isspace((int)arg0_buf[idx])) {
	while (isspace((int)arg0_buf[idx++]))
	  ;
	--idx;  /* Back off for the next iteration. */
      }
    }
    return retval;
  }

  return ERROR;

}

void args_to_method_args (METHOD *method, STDARG_CALL_INFO *stdarg_call_info) {

  int i, n_th_arg;
  OBJECT *arg_val_obj;

  if (!stdarg_call_info -> stream_write_fn) {
    for (i = fmt_tok_idx; i > 0; i--) {
      n_th_arg = i + stdarg_call_info -> fmt_arg_n;
      /*
       *  Skip trailing tokens.
       */
      if (n_th_arg > (method->n_args-1))
	continue;
      /*
       *  fmt_arg_n should be 0 or 1, depending on whether there is a 
       *  stream argument preceding the format in the argument list.
       */
      if ((arg_val_obj = 
	   __ctalkGetInstanceVariable 
	   (method->args[n_th_arg], "value", FALSE)) != NULL) {
	__ctalkSetObjectValue (arg_val_obj, ptr_args[i - 1]);
      } else {
	_warning ("args_to_method_args: Argument mismatch.\n");
	return;
      }
    }
  }

}

int __rt_check_stdargs (void) {
  int i = 0;
  while (i < fmt_tok_idx) {
    if (!strpbrk (fmt_tokens[i], "cs")) {
      _warning ("__call_fn_w_args: Only c and s format characters are allowed in variable argument formats.\n");
      return ERROR;
    }
    ++i;
  }
  return SUCCESS;
}

int tokenize_fmt (char *fmt) {

  int src_idx, dst_idx, fmt_length;

  memset (fmt_tokens, '\0', 512 * 8192);

  for (src_idx = 0, dst_idx = 0, fmt_tok_idx = 0; fmt[src_idx];)
    {
      switch (fmt[src_idx])
	{
	case '%':
	  if ((fmt_length = is_printf_fmt (&fmt[src_idx])) != 0) {
	    substrcpy (fmt_tokens[fmt_tok_idx++], fmt, src_idx, fmt_length);
	    src_idx += fmt_length;
	  } else {
	    fmt_tokens[fmt_tok_idx][dst_idx++] = fmt[src_idx++]; 
	    fmt_tokens[fmt_tok_idx][dst_idx] = 0;
	  }
	  break;
	default:
	  fmt_tokens[fmt_tok_idx][dst_idx++] = fmt[src_idx++]; 
	  fmt_tokens[fmt_tok_idx][dst_idx] = 0;
	  if (fmt[src_idx] == '%') {  /* lookahead */
	    ++fmt_tok_idx;
	    dst_idx = 0;
	  }
	  break;
	}
    }

  return SUCCESS;
}

int tokenize_fmt_old (char *fmt) {
  char tmp[MAXMSG], *tok;

  memset (fmt_tokens, '\0', 512 * 8192);
  fmt_tok_idx = 0;

  strcpy (tmp, fmt);

  tok = strtok (tmp, "%");
  if (tok) {
    strcpy (fmt_tokens[fmt_tok_idx++], tok);
    while ((tok = strtok (NULL, "%")) != NULL) {
      strcpy (fmt_tokens[fmt_tok_idx++], tok);
    }
  } else {
    return ERROR;
  }
  return SUCCESS;
}

