/* $Id: objtoc.c,v 1.3 2008/11/02 18:47:41 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.
*/

/* 
 *  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 <stdlib.h>
#include <ctype.h>
#include <limits.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "lex.h"

#define IS_SIGN(c) ((c == '-' || c == '+'))

/*
 *  Static in case a function needs to do a conversion independent
 *  of its value.
 */
static void *valptr;

char __ctalk_to_c_char (OBJECT *o) {
  char c;
  OBJECT *o_value;
  char buf[MAXLABEL];

  if (!o) {
#ifdef WARN_UNDEFINED_OBJECT_RETURNS
    _warning ("__ctalk_to_c_char: Undefined value.\n");
#endif
    return '\0';
  }

  if ((o_value = __ctalkGetInstanceVariable (o, "value", FALSE)) != NULL) {
    strcpy (buf, o_value -> __o_value);
    while ((*buf == '\'') && (buf[1] != '\0'))
      TRIM_CHAR (buf);
    if (strlen (buf) > 1) { /* Convert from Integer if possible. */
      c = (char)atoi (buf);
    } else {
      sscanf (buf, "%c", &c);
    }
  } else {
    strcpy (buf, o -> __o_value);
    while ((*buf == '\'') && (buf[1] != '\0'))
    while (*buf == '\'')
      TRIM_CHAR (buf);
    sscanf (buf, "%c", &c);
  }
  return c;
}

double __ctalk_to_c_double (OBJECT *o) {
  double d;
  OBJECT *o_value;

  if (!o) {
#ifdef WARN_UNDEFINED_OBJECT_RETURNS
    _warning ("__ctalk_to_c_double: Undefined value.\n");
#endif
    return 0.0;
  }

  if ((o_value = __ctalkGetInstanceVariable (o, "value", FALSE)) != NULL) 
    sscanf (o_value -> __o_value, "%lf", &d);
  else
    sscanf (o -> __o_value, "%lf", &d);
  return d;
}

int __ctalk_to_c_int (OBJECT *o) {
  int i, bin_exp;
  char c;
  OBJECT *o_value;
  char buf[MAXLABEL];

  if (!o) {
#ifdef WARN_UNDEFINED_OBJECT_RETURNS
    _warning ("__ctalk_to_c_int: Undefined value.\n");
#endif
    return 0;
  }

  if ((o_value = __ctalkGetInstanceVariable (o, "value", FALSE)) != NULL) {
    strcpy (buf, o_value -> __o_value);
  } else {
    strcpy (buf, o -> __o_value);
    o_value = o;
  }

  while (*buf == '\'')
    TRIM_CHAR (buf);

  if (!strcmp (o_value -> __o_classname, INTEGER_CLASSNAME) ||
      __ctalkIsSubClassOf (o_value -> __o_classname, INTEGER_CLASSNAME)) {
#if defined(__GNUC__) && defined(__sparc__) && defined(__svr4__)
    if (isdigit ((int)buf[0]) || IS_SIGN(buf[0])) {
#else
    if (isdigit (buf[0]) || IS_SIGN(buf[0])) {
#endif
      switch (radix_of (buf))
	{
	case hexadecimal:
	  if (!sscanf (buf, "%x", &i))
	    sscanf (buf, "%X", &i);
	  break;
	case octal:
	  sscanf (buf, "%o", &i);
	  break;
	case binary:
	  for (i = strlen(buf), bin_exp = 0; i >= 0; i--) {
	    if ((buf[i] == 'b') || (buf[i] == 'B'))
	      continue;
	    i += (buf[i] == 1) ? 1 * bin_exp: 0;
	    bin_exp = 
	      ((bin_exp == 0) ? 1 : 
	       ((bin_exp == 1) ? bin_exp = 2 : bin_exp * bin_exp));
	  }
	  break;
	case decimal:
	default:
	  sscanf (buf, "%d", &i);
	  break;
	}
    } else {
      if (!strcmp (buf, NULLSTR)) {
	i = 0;
      } else {
	if (*buf == '\0') {
	  i = 0;
	} else {
	  _warning ("__ctalk_to_c_int: Incompatible return type.\n");
	}
      }
    }
  } else {
    if (!strcmp (o_value -> __o_classname, CHARACTER_CLASSNAME)) {
      if (!strcmp (buf, NULLSTR)) {
	i = 0;
      } else {
	sscanf (buf, "%c", &c);
	i = (int)c;
      }
    } else {
      /*
       *  Any other class.
       */
      if ((*buf == '\0') || !strlen (buf) || !strcmp (buf, NULLSTR)) {
	i = 0;
      } else {
	if (strstr (buf, "ll") || strstr (buf, "L")) {
	  long long int l;
	  sscanf (buf, "%lld", &l);
	  i = (int)l;
	} else {
	  if (strstr (buf, "l")) {
	    long int l;
	    sscanf (buf, "%ld", &l);
	    i = (int)l;
	  } else {
	    sscanf (buf, "%d", &i);
	  }
	}
      }
    }
  } 

  return i;
}

int __ctalkToCInteger (OBJECT *o, int keep) {
  int i, bin_exp;
  char c;
  OBJECT *o_value;
  char buf[MAXLABEL];

  if (!o) {
#ifdef WARN_UNDEFINED_OBJECT_RETURNS
    _warning ("__ctalk_to_c_int: Undefined value.\n");
#endif
    return 0;
  }

  if ((o_value = __ctalkGetInstanceVariable (o, "value", FALSE)) != NULL) {
    strcpy (buf, o_value -> __o_value);
  } else {
    strcpy (buf, o -> __o_value);
    o_value = o;
  }

  while (*buf == '\'')
    TRIM_CHAR (buf);

  if (!strcmp (o_value -> __o_classname, INTEGER_CLASSNAME) ||
      __ctalkIsSubClassOf (o_value -> __o_classname, INTEGER_CLASSNAME)) {
#if defined(__GNUC__) && defined(__sparc__) && defined(__svr4__)
    if (isdigit ((int)buf[0]) || IS_SIGN(buf[0])) {
#else
    if (isdigit (buf[0]) || IS_SIGN(buf[0])) {
#endif
      switch (radix_of (buf))
	{
	case hexadecimal:
	  if (!sscanf (buf, "%x", &i))
	    sscanf (buf, "%X", &i);
	  break;
	case octal:
	  sscanf (buf, "%o", &i);
	  break;
	case binary:
	  for (i = strlen(buf), bin_exp = 0; i >= 0; i--) {
	    if ((buf[i] == 'b') || (buf[i] == 'B'))
	      continue;
	    i += (buf[i] == 1) ? 1 * bin_exp: 0;
	    bin_exp = 
	      ((bin_exp == 0) ? 1 : 
	       ((bin_exp == 1) ? bin_exp = 2 : bin_exp * bin_exp));
	  }
	  break;
	case decimal:
	default:
	  sscanf (buf, "%d", &i);
	  break;
	}
    } else {
      if (!strcmp (buf, NULLSTR)) {
	i = 0;
      } else {
	if (*buf == '\0') {
	  i = 0;
	} else {
	  _warning ("__ctalk_to_c_int: Incompatible return type.\n");
	}
      }
    }
  } else {
    if (!strcmp (o_value -> __o_classname, CHARACTER_CLASSNAME)) {
      if (!strcmp (buf, NULLSTR)) {
	i = 0;
      } else {
	sscanf (buf, "%c", &c);
	i = (int)c;
      }
    } else {
      /*
       *  Any other class.
       */
      if ((*buf == '\0') || !strlen (buf) || !strcmp (buf, NULLSTR)) {
	i = 0;
      } else {
	if (strstr (buf, "ll") || strstr (buf, "L")) {
	  long long int l;
	  sscanf (buf, "%lld", &l);
	  i = (int)l;
	} else {
	  if (strstr (buf, "l")) {
	    long int l;
	    sscanf (buf, "%ld", &l);
	    i = (int)l;
	  } else {
	    sscanf (buf, "%d", &i);
	  }
	}
      }
    }
  } 

    if (!keep) {
      OBJECT *__t;
/*       if (!__ctalk_get_object(o->__o_name, o->__o_classname) && */
      if (((__t = __ctalk_get_object(o->__o_name, o->__o_classname)) != o) &&
	  !__ctalkFindClassVariable (o->__o_name, FALSE) &&
	  !is_receiver (o) &&
	  !is_arg (o)) {
	__ctalkDeleteObjectInternal (o -> __o_p_obj ? o -> __o_p_obj : o);
      }
    }
  return i;
}

long long int __ctalk_to_c_longlong (OBJECT *o) {
  long long int l;
  OBJECT *o_value;
  char buf[MAXLABEL];
  if (!o) {
#ifdef WARN_UNDEFINED_OBJECT_RETURNS
    _warning ("__ctalk_to_c_longlong: Undefined value.\n");
#endif
    return 0LL;
  }
  if ((o_value = __ctalkGetInstanceVariable (o, "value", FALSE)) != NULL) {
    strcpy (buf, o_value -> __o_value);
  } else {
    strcpy (buf, o -> __o_value);
  }

#if defined(__GNUC__) && defined(__sparc__) && defined(__svr4__)
  if (isdigit ((int)buf[0]) || IS_SIGN(buf[0])) {
#else
  if (isdigit (buf[0]) || IS_SIGN(buf[0])) {
#endif
    sscanf (buf, "%lld", &l);
  } else {
    if (!strcmp (buf, NULLSTR)) {
      l = 0ll;
    } else {
      if (*buf == '\0') {
	l = 0ll;
      } else {
	_warning ("__ctalk_to_c_longlong: Incompatible return type %s.\n", buf);
      }
    }
  }

  return l;
}

char *__ctalk_to_c_char_ptr (OBJECT *o) {
  OBJECT *o_value;
  if (!o || !IS_OBJECT (o))
    return NULL;
  if ((o_value = __ctalkGetInstanceVariable (o, "value", FALSE)) != NULL) {
    if (!strncmp (o_value -> __o_value, "0x", 2)) {
      sscanf (o_value -> __o_value, "%x", (unsigned int *)&valptr);
      return (char *)valptr;
    } else {
      return o_value -> __o_value;
    }
  } else { 
    if (!strncmp (o -> __o_value, "0x", 2)) {
      sscanf (o -> __o_value, "%x", (unsigned int *)&valptr);
      return (char *)valptr;
    } else {
      return o -> __o_value;
    }
  }
}

void *__ctalk_to_c_ptr (OBJECT *o) {
  OBJECT *o_value;
  RADIX radix;
  unsigned int retval;
  unsigned long long int retval_ll;
  if (!o || !IS_OBJECT (o))
    return NULL;
  if ((o_value = __ctalkGetInstanceVariable (o, "value", FALSE)) == NULL)
    o_value = o;
  if (!strcmp (o -> __o_classname, INTEGER_CLASSNAME) ||
      __ctalkIsSubClassOf (o -> __o_classname, INTEGER_CLASSNAME)) {
    if ((radix = radix_of (o_value -> __o_value)) == hexadecimal) {
      sscanf (o_value -> __o_value, "%x", &retval);
    } else {
      sscanf (o_value -> __o_value, "%d", &retval);
    }
    return (void *)retval;
  } else {
    if (!strcmp (o -> __o_classname, LONGINTEGER_CLASSNAME) ||
	__ctalkIsSubClassOf (o -> __o_classname, LONGINTEGER_CLASSNAME)) {
      if ((radix = radix_of (o_value -> __o_value)) == hexadecimal) {
	sscanf (o_value -> __o_value, "%llx", &retval_ll);
      } else {
	sscanf (o_value -> __o_value, "%lld", &retval_ll);
      }
      if (retval_ll > INT_MAX) 
	_warning ("Warning: value of %s is truncated.\n", o -> __o_name);
      return (void *) ((unsigned int)(retval_ll & INT_MAX));
    } else {
      return o_value -> __o_value;
    }
  }
}

void *__ctalkToCArrayElement (OBJECT *o) {
  OBJECT *value_var;
  if ((value_var = __ctalkGetInstanceVariable (o, "value", FALSE)) == NULL)
    value_var = o;

  if ((!strcmp (o -> __o_classname, CHARACTER_CLASSNAME) ||
      __ctalkIsSubClassOf (o -> __o_classname, CHARACTER_CLASSNAME)) &&
      (strcmp (o -> __o_classname, STRING_CLASSNAME) &&
       !__ctalkIsSubClassOf (o -> __o_classname, STRING_CLASSNAME))) {
    int c;
    void *char_value;
    c = (int)__ctalk_to_c_char (value_var);
    char_value = (void *)c;
    return char_value;
  }
  /*
   *  Doubles are not compatible here.
   */

  if (!strcmp (o -> __o_classname, FLOAT_CLASSNAME) ||
      __ctalkIsSubClassOf (o -> __o_classname, FLOAT_CLASSNAME)) {
    _warning ("warning: Float array element returned as char pointer.\n");
    return value_var -> __o_value;
  }

  if (!strcmp (o -> __o_classname, INTEGER_CLASSNAME) ||
      __ctalkIsSubClassOf (o -> __o_classname, INTEGER_CLASSNAME)) {
    int i;
    void *int_val;
    i = __ctalk_to_c_int (value_var);
    int_val = (void *)i;
    return int_val;
  }

  if (!strcmp (o -> __o_classname, LONGINTEGER_CLASSNAME) ||
      __ctalkIsSubClassOf (o -> __o_classname, LONGINTEGER_CLASSNAME)) {
    int l;
    void *ll_val;
    l = __ctalk_to_c_longlong (value_var);
    _warning ("warning: LongInteger array element truncated to int.\n");
    ll_val = (void *)(l & INT_MAX);
    return (void *)ll_val;
  }

  if (!strcmp (o -> __o_classname, STRING_CLASSNAME) ||
      __ctalkIsSubClassOf (o -> __o_classname, STRING_CLASSNAME))
    return __ctalk_to_c_char_ptr (value_var);

  return value_var -> __o_value;
}

int __ctalkToCIntArrayElement (OBJECT *o) {
  OBJECT *value_var;
  if ((value_var = __ctalkGetInstanceVariable (o, "value", FALSE)) == NULL)
    value_var = o;

  if (!strcmp (o -> __o_classname, INTEGER_CLASSNAME) ||
      __ctalkIsSubClassOf (o -> __o_classname, INTEGER_CLASSNAME)) {
    return __ctalk_to_c_int (value_var);
  }

  if (!strcmp (o -> __o_classname, LONGINTEGER_CLASSNAME) ||
      __ctalkIsSubClassOf (o -> __o_classname, LONGINTEGER_CLASSNAME)) {
    _warning ("warning: LongInteger array element truncated to int.\n");
    return (int) __ctalk_to_c_longlong (value_var);
  }

  return 0;
}
