/* $Id: objtoc.c,v 1.18 2009/02/11 13:59:20 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.
*/

/* 
 *  Generate calls that translate objects to their C equivalents.  In
 *  the case of object that do not correspond directly to C data types,
 *  return the value pointer, or at least try to do something 
 *  intelligent.
 *
 *  In this version, C int and char types are synonymous, Float class
 *  translates to double, as in C, and a long double class isn't 
 *  implemented.
 *
 *  Long int and int are synonymous, as in GNU C for most, if not all, 
 *  platforms.
 *
 *  NOTE - Future versions might allow the class library to define
 *  what instance variable to return in a C context.
 */

#include <stdio.h>
#include <string.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "objtoc.h"

static char *__warn_unknown_C_type_msg (char *type_name) {
  static char buf[MAXMSG];
  sprintf (buf, 
	   _("Unimplemented C type %s in conversion, class defaulting to Integer.\n"), 
	   type_name);
  return buf;
}

char *fmt_printf_fmt_arg (MESSAGE_STACK messages, 
			  int expr_start_idx,
			  int stack_start_idx,
			  int stack_end_idx, char *expr) {
  static char expr_x_buf[MAXMSG];
  if (is_char_ptr_fmt_arg (messages, expr_start_idx,
			   stack_start_idx, stack_end_idx)) {
    strcpy 
      (expr_x_buf, 
       (fmt_rt_return (expr, STRING_CLASSNAME, TRUE)));
  } else {
    if (is_char_fmt_arg (messages, expr_start_idx,
			 stack_start_idx, stack_end_idx)) {
      strcpy 
	(expr_x_buf, 
	 (fmt_rt_return (expr, STRING_CLASSNAME, TRUE)));
    } else {
      if (is_int_fmt_arg (messages, expr_start_idx, 
			  stack_start_idx, stack_end_idx)) {
	strcpy 
	  (expr_x_buf, 
	   (fmt_rt_return (expr, INTEGER_CLASSNAME, TRUE)));
      }
    }
  }
  return expr_x_buf;
}

/*
 *  The function needs to decide whether to use the return class of
 *  the receiver object or of the method if they are different.  Use
 *  the return class of the method, unless the method is defined in a
 *  superclass and does not have a corresponding basic C data type.
 *  In that case, use the return class of the receiver object.
 */

char *obj_2_c_wrapper (MESSAGE *orig, OBJECT *object, 
		       METHOD *method, char *rt_expr, int keep) {

  char return_class_name[MAXLABEL];

  if (!method) {
    /*
     *  Look for an instance variable message and use the
     *  instance variable's class as the return class,
     *  or the object's class if necessary.
     */
    OBJECT *instancevar;
    if ((instancevar = __ctalkGetInstanceVariable (object, M_NAME(orig),
						   TRUE)) == NULL) {
      strcpy (return_class_name, object -> __o_classname);
    } else {
      if (!strcmp (instancevar -> __o_classname, "Character") ||
	  !strcmp (instancevar -> __o_classname, "Float") ||
	  !strcmp (instancevar -> __o_classname, "Integer") ||
	  !strcmp (instancevar -> __o_classname, "LongInteger") ||
	  !strcmp (instancevar -> __o_classname, "String"))
	strcpy (return_class_name, instancevar -> __o_classname);
      else
	strcpy (return_class_name, object -> __o_classname);
    }
	 
  } else {
    /*
     *  Use the method return class as the expression
     *  return class.
     */
    if (!strcmp (method -> returnclass, "Character") ||
	!strcmp (method -> returnclass, "Float") ||
	!strcmp (method -> returnclass, "Integer") ||
	!strcmp (method -> returnclass, "LongInteger") ||
	!strcmp (method -> returnclass, "String")) {
      strcpy (return_class_name, method -> returnclass);
    } else {
      if (orig -> attrs & OBJ_IS_CLASS_VAR) {
	strcpy (return_class_name,
		((object->instancevars) ? 
		object->instancevars->__o_classname :
		 object -> __o_classname));
      } else {
	strcpy (return_class_name, object -> __o_classname);
      }
    }
  }

  return fmt_rt_return (rt_expr, return_class_name, keep);
}

char *obj_2_c_wrapper_trans (MESSAGE_STACK messages, int idx, 
			     MESSAGE *orig, OBJECT *object, 
			     METHOD *method, char *rt_expr, int keep) {

  char obj_return_class_name[MAXLABEL];
  int fn_idx, arg_idx;

  /*
   *  First check if a function arg prototype requires only
   *  an object.  Note that we should be able to use 
   *  arg_return_class_name, above, for prototypes other
   *  than OBJECT *.
   */
  if ((arg_idx = obj_expr_is_arg (messages, idx,
				  stack_start(messages),
				  get_stack_top(messages),
				  &fn_idx)) != ERROR) {
    CFUNC *fn;
    CVAR *param_prototype;
    int n_th_arg;
    if ((fn = get_function (M_NAME(messages[fn_idx]))) != NULL) {
      for (n_th_arg = 0, param_prototype = fn -> params; 
	   /*
	    *  Need check for valid param prototype in loop
	    *  in case of stdargs functions.
	    */
	   (n_th_arg < arg_idx) && 
	     param_prototype && IS_CVAR(param_prototype); 
	   n_th_arg++, param_prototype = param_prototype -> next)
	;
      if (param_prototype && IS_CVAR(param_prototype)) {
	if (!strcmp (param_prototype -> type, "OBJECT") &&
	    param_prototype -> n_derefs == 1) {
	  return rt_expr;
	}
      }
    }
  }

  if (is_char_ptr_fmt_arg (messages, idx, 
			   stack_start (messages),
			   get_stack_top (messages))) {
    strcpy (obj_return_class_name, STRING_CLASSNAME);
    return fmt_rt_return (rt_expr, obj_return_class_name, keep);
  } else {
    if (is_char_fmt_arg (messages, idx, 
			 stack_start (messages),
			 get_stack_top (messages))) {
      strcpy (obj_return_class_name, CHARACTER_CLASSNAME);
      return fmt_rt_return (rt_expr, obj_return_class_name, keep);
    } else {
      if (is_int_fmt_arg (messages, idx,
			  stack_start (messages),
			  get_stack_top (messages))) {
	strcpy (obj_return_class_name, INTEGER_CLASSNAME);
	return fmt_rt_return (rt_expr, obj_return_class_name, keep);
      }
    }
  }

  if (!method) {
    /*
     *  Look for an instance variable message and use the
     *  instance variable's class as the return class,
     *  or the object's class if necessary.
     */
    OBJECT *instancevar;
    if ((instancevar = __ctalkGetInstanceVariable (object, M_NAME(orig),
						   TRUE)) == NULL) {
      strcpy (obj_return_class_name, object -> __o_classname);
    } else {
      if (!strcmp (instancevar -> __o_classname, "Character") ||
	  !strcmp (instancevar -> __o_classname, "Float") ||
	  !strcmp (instancevar -> __o_classname, "Integer") ||
	  !strcmp (instancevar -> __o_classname, "LongInteger") ||
	  !strcmp (instancevar -> __o_classname, "String"))
	strcpy (obj_return_class_name, instancevar -> __o_classname);
      else
	strcpy (obj_return_class_name, object -> __o_classname);
    }
  } else {
    /*
     *  Use the method return class as the expression
     *  return class.
     */
    if (!strcmp (method -> returnclass, "Character") ||
	!strcmp (method -> returnclass, "Float") ||
	!strcmp (method -> returnclass, "Integer") ||
	!strcmp (method -> returnclass, "LongInteger") ||
	!strcmp (method -> returnclass, "String")) {
      strcpy (obj_return_class_name, method -> returnclass);
    } else {
      if (orig -> attrs & OBJ_IS_CLASS_VAR) {
	strcpy (obj_return_class_name,
		((object->instancevars) ? 
		object->instancevars->__o_classname :
		 object -> __o_classname));
      } else {
	strcpy (obj_return_class_name, object -> __o_classname);
      }
    }
  }

  return fmt_rt_return (rt_expr, obj_return_class_name, keep);
}

/*
 *  This function is, for now, only called for method
 *  expressions that have arguments.
 *  If the return class of the method is, "Any," then
 *  we should be able to use the class of the method
 *  argument(s).
 */
char *array_obj_2_c_wrapper (MESSAGE_STACK messages, int idx, OBJECT *object, 
		       METHOD *method, char *rt_expr) {

  char return_class_name[MAXLABEL];
  OBJECT *instancevar;
  static char buf[MAXMSG];
  COLLECTION_CONTEXT context;
  int rcvr_idx, arglist_start_idx, arglist_end_idx, expr_start_idx, stack_top_idx;
  int expr_paren_level, expr_open_paren_idx, expr_close_paren_idx;

  if (!strcmp (method -> returnclass, "Character") ||
      !strcmp (method -> returnclass, "Float") ||
      !strcmp (method -> returnclass, "Integer") ||
      !strcmp (method -> returnclass, "LongInteger") ||
      !strcmp (method -> returnclass, "String")) {
    strcpy (return_class_name, method -> returnclass);
    return fmt_rt_return (rt_expr, return_class_name, TRUE);
  } else {
    if (!strcmp (method -> returnclass, "Any")) {
      if ((instancevar = method -> args[0]) != NULL) {
	strcpy (return_class_name, instancevar -> __o_classname);
      } else {
	strcpy (return_class_name, object -> __o_classname);
      }
    }
  }
  
  stack_top_idx = stack_start (messages);
  for (rcvr_idx = idx + 1; rcvr_idx <= stack_top_idx; rcvr_idx++) {
    if (!strcmp (M_NAME(messages[rcvr_idx]), object->__o_name))
      break;
  }
  arglist_start_idx = nextlangmsg (messages, idx);
  arglist_end_idx = 
    method_arglist_limit (messages, arglist_start_idx, method -> n_args,
			  method -> varargs);
  /*
   *  Check for opening parentheses on the expression.
   */
  expr_paren_level = expr_outer_parens (messages, rcvr_idx, arglist_end_idx, 
                     stack_start (messages), get_stack_top (messages),
		     &expr_open_paren_idx, &expr_close_paren_idx);

  expr_start_idx = rcvr_idx;
  if (expr_paren_level) {
    int __t;
    __t = expr_start_idx;
    while ((__t = prevlangmsg (messages, __t)) != ERROR) {
      if (M_TOK(messages[__t]) == OPENPAREN) {
	expr_start_idx = __t;
	continue;
      }
      break;
    }
  }
  context = collection_context (messages, expr_start_idx);

  switch (context) 
    {
    case int_collection_context:
      sprintf (buf, "%s (%s)", ARRAY_INT_TRANS_FN, rt_expr);
      break;
    case char_ptr_collection_context:
      sprintf (buf, "%s (%s, %d)", STRING_TRANS_FN, rt_expr, TRUE);
      break;
    default:
      sprintf (buf, "%s (%s)", ARRAY_TRANS_FN, rt_expr);
      break;
    }
  return buf;
}

char *fmt_rt_return (char *exprbuf, char *returnclassname, int keep) {
  static char buf[MAXMSG];
  *buf = '\0';
  if (!strcmp (returnclassname, CHARACTER_CLASSNAME) ||
      (is_subclass_of (returnclassname, CHARACTER_CLASSNAME) == TRUE)) {
    if (!strcmp (returnclassname, STRING_CLASSNAME) ||
	(is_subclass_of (returnclassname, STRING_CLASSNAME) == TRUE)) {
      sprintf (buf, "%s (%s, %d)", STRING_TRANS_FN, exprbuf, keep);
      return buf;
    } else {
      sprintf (buf, "%s (%s)", CHAR_TRANS_FN, exprbuf);
      return buf;
    }
  }

  if (!strcmp (returnclassname, FLOAT_CLASSNAME) ||
      (is_subclass_of (returnclassname, FLOAT_CLASSNAME) == TRUE)) {
    sprintf (buf, "%s (%s)", FLOAT_TRANS_FN, exprbuf);
    return buf;
  }

  if (!strcmp (returnclassname, INTEGER_CLASSNAME) ||
      (is_subclass_of (returnclassname, INTEGER_CLASSNAME) == TRUE)) {
    sprintf (buf, "%s (%s, %d)", INT_TRANS_FN, exprbuf, keep);
    return buf;
  }

  if (!strcmp (returnclassname, LONGINTEGER_CLASSNAME) ||
      (is_subclass_of (returnclassname, LONGINTEGER_CLASSNAME) == TRUE)) {
    sprintf (buf, "%s (%s)", LONGINT_TRANS_FN, exprbuf);
    return buf;
  }

  /*
   *  FIXME!  - See note in basic_class_from_cvar (). 
   */
  if (!strcmp (returnclassname, OBJECT_CLASSNAME)) {
    strcpy (buf, exprbuf);
    return buf;
  }

  /*
   *  Otherwise, just return the value pointer.
   */

  sprintf (buf, "%s (%s)", PTR_TRANS_FN, exprbuf);
  return buf;
}

static struct __C_type_conv {
  char c_type_name[MAXLABEL];
  int n_derefs;
  char class_name[MAXLABEL];
} __C_type_conv [] = {
  {"char", 0, CHARACTER_CLASSNAME},
  {"char", 1, STRING_CLASSNAME},
  {"char", 2, ARRAY_CLASSNAME},
  {"int", 0, INTEGER_CLASSNAME},
  {"int", 1, SYMBOL_CLASSNAME},
  {"int", 2, ARRAY_CLASSNAME},
  {"long long int", 0, LONGINTEGER_CLASSNAME},
  {"long long int", 1, SYMBOL_CLASSNAME},
  {"long long int", 2, ARRAY_CLASSNAME},
  {"double", 0, FLOAT_CLASSNAME},
  {"double", 1, SYMBOL_CLASSNAME},
  {"double", 2, ARRAY_CLASSNAME},
  {"float", 0, FLOAT_CLASSNAME},
  {"float", 1, SYMBOL_CLASSNAME},
  {"float", 2, ARRAY_CLASSNAME},
  {"OBJECT", 0, OBJECT_CLASSNAME},
  {"OBJECT", 1, OBJECT_CLASSNAME},
  {"OBJECT", 2, OBJECT_CLASSNAME},
  {"_object", 0, OBJECT_CLASSNAME},
  {"_object", 1, OBJECT_CLASSNAME},
  {"_object", 2, OBJECT_CLASSNAME},
  {"void", 0, OBJECT_CLASSNAME},
  {"void", 1, OBJECT_CLASSNAME},
  {"void", 2, OBJECT_CLASSNAME},
  {"EXCEPTION", 0, INTEGER_CLASSNAME},
  /*
   *  Types defined by C99.
   */
#if defined (__linux__)
  {"_IO_FILE", 0, "FileStream"},
  {"_IO_FILE", 1, "FileStream"},
  {"_IO_FILE", 2, "FileStream"},
#else
# if defined (__sparc__)
  {"__FILE", 0, "FileStream"},
  {"__FILE", 1, "FileStream"},
  {"__FILE", 2, "FileStream"},
# endif /* #if defined (__sparc__) */
#endif /* #if defined (__linux__) */
  {"FILE", 0, "FileStream"},
  {"FILE", 1, "FileStream"},
  {"FILE", 2, "FileStream"},
  {"", -1, ""}
};

char *get_type_conv_name (char *s) {
  int i;
  for (i = 0; __C_type_conv[i].n_derefs != -1; i++) {
    if (!strcmp (s, __C_type_conv[i].c_type_name))
      return (__C_type_conv[i].c_type_name);
  }
  return NULL;
}

char *basic_class_from_cvar (CVAR *c, int n_expr_derefs) {
  static char buf[MAXLABEL];
  char type_buf[MAXMSG];
  CVAR *type_alias;
  int i, derefs;

  if (!c || !IS_CVAR(c)) {
    _warning ("basic_class_from_cvar: Invalid CVAR, class defaulting to Integer.\n");
    strcpy (buf, INTEGER_CLASSNAME);
    return buf;
  }
    

  if (!strcmp (c -> type, "int") &&
      !strcmp (c -> qualifier, "long") &&
      !strcmp (c -> qualifier2, "long")) {
    strcpy (type_buf, "long long int");
  } else {
#ifdef __GNUC__
    if (is_gnu_extension_keyword (c -> type)) {
      strcpy (type_buf, c -> qualifier);
    } else {
      strcpy (type_buf, c -> type);
    }
#else
    strcpy (type_buf, c -> type);
#endif
  }

  *buf = 0;
  derefs = c -> n_derefs - n_expr_derefs;
  for (i = 0; __C_type_conv[i].n_derefs != -1; i++) {
    if (!strcmp (__C_type_conv[i].c_type_name, type_buf) &&
	(derefs == __C_type_conv[i].n_derefs)) 
      strcpy (buf, __C_type_conv[i].class_name);
  }
      
  if (*buf == 0) {
    if ((type_alias = get_typedef (c -> type)) != NULL) {
      if (type_alias == c) {
	_warning (__warn_unknown_C_type_msg (c -> name));
	strcpy (buf, INTEGER_CLASSNAME);
      } else {
	strcpy (buf, basic_class_from_cvar (type_alias, n_expr_derefs));
      }
    } else {
      _warning (__warn_unknown_C_type_msg (c -> type));
      strcpy (buf, INTEGER_CLASSNAME);
    }
  }

  /*
   *  TODO - This is needed if get_typedef looks only 
   *  in the declared_typedefs table.
   */
  if (!strcmp (c -> name, "OBJECT") && 
      (c -> n_derefs == 1)) {
    strcpy (buf, OBJECT_CLASSNAME);
  }

  return buf;
}

int is_translatable_basic_class (char *classname) {
  if (!strcmp (classname, CHARACTER_CLASSNAME) ||
      is_subclass_of (classname, CHARACTER_CLASSNAME)) {
    return TRUE;
  }

  if (!strcmp (classname, FLOAT_CLASSNAME) ||
      is_subclass_of (classname, FLOAT_CLASSNAME)) {
    return TRUE;
  }

  if (!strcmp (classname, INTEGER_CLASSNAME) ||
      is_subclass_of (classname, INTEGER_CLASSNAME)) {
    return TRUE;
  }

  if (!strcmp (classname, LONGINTEGER_CLASSNAME) ||
      is_subclass_of (classname, LONGINTEGER_CLASSNAME)) {
    return TRUE;
  }

  return FALSE;
}


