/* Signature mangling functions. */
/*
  This file is part of Japhar, the GNU Virtual Machine for Java Bytecodes.
  Japhar is a project of The Hungry Programmers, GNU, and OryxSoft.

  Copyright (C) 1997, 1998, 1999 The Hungry Programmers

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library 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
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "sig.h"
#include "log.h"
#include "native-threads.h"
#include "exceptions.h" /* for throw_Exception() */
#include "compat.h"
#include "util.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

#define MYLOG "SigParse"

static Signature *sig_parse_internal(JNIEnv *env, char **sig_string);

static Signature *
sig_parse_primitive(JNIEnv *env, char **sig_string)
{
  Signature *prim_sig = malloc(sizeof(Signature));
  
  prim_sig->any.tag = SIG_PRIM;

  switch (**sig_string)
    {
    case 'B': prim_sig->prim.type = SIG_JBYTE; break;
    case 'C': prim_sig->prim.type = SIG_JCHAR; break;
    case 'I': prim_sig->prim.type = SIG_JINT; break;
    case 'S': prim_sig->prim.type = SIG_JSHORT; break;
    case 'Z': prim_sig->prim.type = SIG_JBOOLEAN; break;
    case 'F': prim_sig->prim.type = SIG_JFLOAT; break;
    case 'D': prim_sig->prim.type = SIG_JDOUBLE; break;
    case 'J': prim_sig->prim.type = SIG_JLONG; break;
    case 'V': prim_sig->prim.type = SIG_JVOID; break;
    default: assert(0); break;
    }

  (*sig_string) ++;

  return prim_sig;
}

static Signature *
sig_parse_class(JNIEnv *env, char **sig_string)
{
  Signature *class_sig = malloc(sizeof(Signature));
  char *semicolon;

  class_sig->any.tag = SIG_CLASS;

  assert(**sig_string == 'L');
  (*sig_string) ++;

  semicolon = strchr(*sig_string, ';');
  
  class_sig->clazz.java_class = 
    malloc(sizeof(char) * (semicolon - (*sig_string)) + 1);

  strncpy(class_sig->clazz.java_class,
	  *sig_string, semicolon - (*sig_string));

  class_sig->clazz.java_class[ semicolon - (*sig_string) ] = 0;

  *sig_string = semicolon + 1;

  return class_sig;
}

static Signature *
sig_parse_method(JNIEnv *env, char **sig_string)
{
  Signature *method_sig = malloc(sizeof(Signature));

  method_sig->any.tag = SIG_METHOD;

  assert(**sig_string == '(');
  (*sig_string) ++;

  method_sig->method.num_params = 0;
    
  while (**sig_string != ')')
    {
      Signature *param_sig = sig_parse_internal(env, sig_string);

      method_sig->method.params [ method_sig->method.num_params ++ ] = param_sig;
    }

  (*sig_string) ++;

  method_sig->method.return_type = sig_parse_internal(env, sig_string);

  return method_sig;
}

static Signature *
sig_parse_array(JNIEnv *env, char **sig_string)
{
  Signature *array_sig = malloc(sizeof(Signature));

  array_sig->any.tag = SIG_ARRAY;

  assert(**sig_string == '[');
  (*sig_string) ++;
  
  array_sig->array.subtype = sig_parse_internal(env, sig_string);

  return array_sig;
}

static Signature *
sig_parse_internal(JNIEnv *env, char **sig_string)
{
  switch(**sig_string)
    {
    case '(':
      return sig_parse_method(env, sig_string);
      break;
    case '[':
      return sig_parse_array(env, sig_string);
      break;
    case 'L':
      return sig_parse_class(env, sig_string);
      break;
    default:
      return sig_parse_primitive(env, sig_string);      
      break;
    }
}

Signature *
SIG_parseFromJavaSig(JNIEnv *env, char *sig_string)
{
  char *foo = sig_string;

  return sig_parse_internal(env, &foo);
}

char *
SIG_formatReturnTypeToC(JNIEnv *env, Signature *sig)
{
  assert(sig->any.tag == SIG_METHOD);
  return SIG_formatToC(env, sig->method.return_type);
}

static char *
sig_format_method_to_c(JNIEnv *env, Signature *sig)
{
  static char working_string[1024];
  int i;
  
  strcpy(working_string, "(");

  for (i = 0; i < sig->method.num_params; i ++)
    {
      strcat(working_string, SIG_formatToC(env, sig->method.params[i]));
      if (i < sig->method.num_params - 1)
	strcat (working_string, ", ");
    }

  strcat(working_string, ")");

  return working_string;
}

static char *
sig_format_array_to_c(JNIEnv *env, Signature *sig)
{
  switch (sig->array.subtype->any.tag)
    {
    case SIG_PRIM:
      switch (sig->array.subtype->prim.type)
	{
	case SIG_JINT:
	  return "jintArray";
	  break;
	case SIG_JLONG:
	  return "jlongArray";
	  break;
	case SIG_JDOUBLE:
	  return "jdoubleArray";
	  break;
	case SIG_JSHORT:
	  return "jshortArray";
	  break;
	case SIG_JBYTE:
	  return "jbyteArray";
	  break;
	case SIG_JCHAR:
	  return "jcharArray";
	  break;
	case SIG_JBOOLEAN:
	case SIG_JFLOAT:
	case SIG_JOBJECT:
	case SIG_JVOID:
	  {
	    throw_Exception(env, "java/lang/RuntimeException",
			    "sig.c/sig_format_array_to_c()[1]");
	    return NULL;
	  }
	default:
	  assert(0);
	  break;
	}
    case SIG_ARRAY:
    case SIG_CLASS:
      return "jobjectArray";
    case SIG_METHOD:
      {
	throw_Exception(env, "java/lang/RuntimeException",
			"sig.c/sig_format_array_to_c()[2]");
	return NULL;
      }
    default:
      assert(0);
      break;
    }

  return NULL;
}

char*
SIG_formatPrimitiveTypeToC(JNIEnv *env, SigPrimType sig_prim_type)
{
  switch(sig_prim_type)
    {
    case SIG_JBYTE:
      return "jbyte";
      break;
    case SIG_JCHAR:
      return "jchar"; /* ? */
      break;
    case SIG_JSHORT:
      return "jshort";
      break;
    case SIG_JBOOLEAN:
      return "jboolean";
      break;
    case SIG_JFLOAT:
      return "jfloat";
      break;
    case SIG_JINT:
      return "jint";
      break;
    case SIG_JLONG:
      return "jlong";
      break;
    case SIG_JDOUBLE:
      return "jdouble";
      break;
    case SIG_JVOID:
      return "void";
      break;
    case SIG_JOBJECT:
      return "jobject";
      break;
    default:
      fprintf (stderr, "unhandled case -- yell at toshok\n");
      exit(1);
    }
}

static char *
sig_format_class_to_c(JNIEnv *env, Signature *sig)
{
  if (!strcmp(sig->clazz.java_class, "java/lang/String"))
    return "jstring";
  else
    return "jobject";
}

char *
SIG_formatToC(JNIEnv *env, Signature *sig)
{
  switch(sig->any.tag)
    {
    case SIG_METHOD:
      return sig_format_method_to_c(env, sig);
      break;
    case SIG_ARRAY:
      return sig_format_array_to_c(env, sig);
      break;
    case SIG_CLASS:
      return sig_format_class_to_c(env, sig);
      break;
    case SIG_PRIM:
      return SIG_formatPrimitiveTypeToC(env, sig->prim.type);
      break;
    }

  {
    throw_Exception(env, "java/lang/RuntimeException", "sig.c/SIG_formatToC()");
    return NULL;
  }
}

char *
SIG_formatToJavaSig(JNIEnv *env, Signature *sig)
{
  char buf[2000];

  switch (sig->any.tag)
    {
    case SIG_CLASS:
      {
	snprintf(buf, 2000, "L%s;", sig->clazz.java_class);
	break;
      }
    case SIG_PRIM:
      {
	switch (sig->prim.type)
	  {
	  case SIG_JBOOLEAN:
	    strcpy(buf, "Z");
	    break;
	  case SIG_JBYTE:
	    strcpy(buf, "B");
	    break;
	  case SIG_JCHAR:
	    strcpy(buf, "C");
	    break;
	  case SIG_JSHORT:
	    strcpy(buf, "S");
	    break;
	  case SIG_JINT:
	    strcpy(buf, "I");
	    break;
	  case SIG_JFLOAT:
	    strcpy(buf, "F");
	    break;
	  case SIG_JDOUBLE:
	    strcpy(buf, "D");
	    break;
	  case SIG_JLONG:
	    strcpy(buf, "J");
	    break;
	  case SIG_JVOID:
	    strcpy(buf, "V");
	    break;
	  case SIG_JOBJECT:
	    abort();
	    break;
	  default:
	    assert(0);
	    break;
	  }
	break;
      }
    case SIG_ARRAY:
      {
	strcpy(buf, "[");
	strcat(buf, SIG_formatToJavaSig(env, sig->array.subtype));
	break;
      }
    case SIG_METHOD:
      {
	int i;
	strcpy(buf, "(");
	for (i = 0; i < sig->method.num_params; i ++)
	  strcat(buf, SIG_formatToJavaSig(env, sig->method.params[i]));
	strcat(buf, ")");
	strcat(buf, SIG_formatToJavaSig(env, sig->method.return_type));
	break;
      }
    }

  return strdup(buf);
}

char *
SIG_formatToJavaSource(JNIEnv *env, Signature *sig)
{
  char buf[2000];

  switch (sig->any.tag)
    {
    case SIG_CLASS:
      {
	snprintf(buf, sizeof(buf), "%s", sig->clazz.java_class);
	slashes_to_dots(buf);
	break;
      }
    case SIG_PRIM:
      {
	switch (sig->prim.type)
	  {
	  case SIG_JBOOLEAN:
	    strcpy(buf, "boolean");
	    break;
	  case SIG_JBYTE:
	    strcpy(buf, "byte");
	    break;
	  case SIG_JCHAR:
	    strcpy(buf, "char");
	    break;
	  case SIG_JSHORT:
	    strcpy(buf, "short");
	    break;
	  case SIG_JINT:
	    strcpy(buf, "int");
	    break;
	  case SIG_JFLOAT:
	    strcpy(buf, "float");
	    break;
	  case SIG_JDOUBLE:
	    strcpy(buf, "double");
	    break;
	  case SIG_JLONG:
	    strcpy(buf, "long");
	    break;
	  case SIG_JVOID:
	    strcpy(buf, "void");
	    break;
	  case SIG_JOBJECT:
	    abort();
	    break;
	  default:
	    assert(0);
	    break;
	  }
	break;
      }
    case SIG_ARRAY:
      {
	strcpy(buf, SIG_formatToJavaSource(env, sig->array.subtype));
	strcat(buf, "[]");
	break;
      }
    case SIG_METHOD:
      {
	int i;
	strcpy(buf, "(");
	for (i = 0; i < sig->method.num_params; i ++)
	  strcat(buf, SIG_formatToJavaSource(env, sig->method.params[i]));
	strcat(buf, ")");
	strcat(buf, SIG_formatToJavaSource(env, sig->method.return_type));
	break;
      }
    }

  return strdup(buf);
}

int
SIG_formatStringToNativeName_buf(JNIEnv *env, char *sig_str, char *buf, int buf_len)
{
  int j,i;

  j = 0;
  buf[0] = 0;
  for (i = 0; i < strlen(sig_str); i ++)
    {
      if (sig_str[i] == '/')
	{
	  buf[j++] = '_';
	  if (j == buf_len)
	    return -1;
	  buf[j] = 0;
	}
      else if (sig_str[i] == '_')
	{
	  if (buf_len - 3 == j)
	    return -1;
	  strcat(buf, "_1");
	  j += 2;
	}
      else if (sig_str[i] == ';')
	{
	  if (buf_len - 3 == j)
	    return -1;
	  strcat(buf, "_2");
	  j += 2;
	}
      else if (sig_str[i] == '[')
	{
	  if (buf_len - 3 == j)
	    return -1;
	  strcat(buf, "_3");
	  j += 2;
	}
      else if (isalnum(sig_str[i]))
	{
	  buf[j++] = sig_str[i];
	  if (j == buf_len)
	    return -1;
	  buf[j] = 0;
	}
      else if (sig_str[i] == '(')
	{
	  continue;
	}
      else if (sig_str[i] == ')')
	{
	  break; /* XXX */
	}
      else
	{
	  char buf3[7];
	  if (buf_len - j == 6)
	    return -1;
	  sprintf(buf3, "_%05X", sig_str[i]);
	  strcat(buf, buf3);
	  j += 6;
	}
    }
  
  buf[j++] = 0;
  return 0;
}

char *
SIG_formatStringToNativeName(JNIEnv *env, char *sig_str)
{
  char buf2[1000];

  SIG_formatStringToNativeName_buf(env, sig_str, buf2, sizeof(buf2));
  return strdup(buf2);
}

char *
SIG_formatToNativeName(JNIEnv *env, Signature *sig)
{
  char buf[2000];
  int i;

  switch (sig->any.tag)
    {
    case SIG_METHOD:
      {
	buf[0] = 0;

	for (i = 0; i < sig->method.num_params; i ++)
	  {
	    char *param_sig = SIG_formatToNativeName(env,
						     sig->method.params[i]);

	    strcat(buf, param_sig);
	    free(param_sig);
	  }
	break;
      }
    case SIG_PRIM:
      return SIG_formatToJavaSig(env, sig);
      break;
    case SIG_CLASS:
      {
	char *hacked_class;

	hacked_class = SIG_formatStringToNativeName(env, sig->clazz.java_class);

	snprintf(buf, sizeof(buf), "L%s_2", hacked_class);

	free(hacked_class);
      }
      break;
    case SIG_ARRAY:
      {
	char *subtype = SIG_formatToNativeName(env, sig->array.subtype);
	snprintf(buf, sizeof(buf), "_3%s", subtype);
	free(subtype);
      }
      break;
    }

  return strdup(buf);
}


int
SIG_sizeInBytes(JNIEnv *env, Signature *sig)
{
  switch (sig->any.tag)
    {
    case SIG_PRIM:
      switch (sig->prim.type)
	{
	case SIG_JBYTE:
	case SIG_JCHAR:
	case SIG_JSHORT:
	case SIG_JINT:
	case SIG_JBOOLEAN:
	case SIG_JFLOAT:
	  return 4;
	  break;
	case SIG_JDOUBLE:
	case SIG_JLONG:
	  return 8;
	  break;
	case SIG_JOBJECT:
	  return 4;
	  break;
	case SIG_JVOID:
	  return 0;
	  break;
	default:
	  assert(0);
	}
    case SIG_CLASS:
      return 4;
    case SIG_METHOD:
      JAVARLOG0(MYLOG, 1, "trying to take the size of a method... this is probably wrong.\n");
      return 4;
    case SIG_ARRAY:
      return 4;
    }

  return 0; /* keep cc happy */
}


int
SIG_sizeInWords(JNIEnv *env, Signature *sig)
{
  switch (sig->any.tag)
    {
    case SIG_PRIM:
      switch (sig->prim.type)
	{
	case SIG_JBYTE:
	case SIG_JCHAR:
	case SIG_JSHORT:
	case SIG_JINT:
	case SIG_JBOOLEAN:
	case SIG_JFLOAT:
	  return 1;
	  break;
	case SIG_JDOUBLE:
	case SIG_JLONG:
	  return 2;
	  break;
	case SIG_JOBJECT:
	  return 1;
	  break;
	case SIG_JVOID:
	  return 0;
	  break;
	default:
	  assert(0);
	}
    case SIG_CLASS:
      return 1;
    case SIG_METHOD:
      JAVARLOG0(MYLOG, 1, "trying to take the size of a method... this is probably wrong.\n");
      return 1;
    case SIG_ARRAY:
      return 1;
    }

  return 0; /* keep cc happy */
}

int
SIG_numParams(JNIEnv *env, Signature *sig)
{
  assert(sig->any.tag == SIG_METHOD);

  return sig->method.num_params;
}

static void
sig_free_primitive(JNIEnv *env, Signature *sig)
{
  free(sig);
}

static void
sig_free_class(JNIEnv *env, Signature *sig)
{
  free(sig->clazz.java_class);
  free(sig);
}

static void
sig_free_method(JNIEnv *env, Signature *sig)
{
  int i;

  SIG_free(env, sig->method.return_type);

  for (i = 0; i < sig->method.num_params; i ++)
    SIG_free(env, sig->method.params[i]);

  free(sig);
}

static void
sig_free_array(JNIEnv *env, Signature *sig)
{
  SIG_free(env, sig->array.subtype);
  free(sig);
}

void
SIG_free(JNIEnv *env, Signature *sig)
{
  switch (sig->any.tag)
    {
    case SIG_PRIM:
      sig_free_primitive(env, sig);
      break;
    case SIG_CLASS:
      sig_free_class(env, sig);
      break;
    case SIG_METHOD:
      sig_free_method(env, sig);
      break;
    case SIG_ARRAY:
      sig_free_array(env, sig);
      break;
    }
}

int 
SIG_isVoid(JNIEnv *env, Signature *sig)
{
  if (sig->any.tag != SIG_PRIM)
    return 0;

  if (sig->prim.type != SIG_JVOID)
    return 0;

  return 1;
}

int
SIG_equal(JNIEnv *env, Signature *sig1, Signature *sig2)
{
  if (sig1->any.tag != sig2->any.tag)
    return 0;
  
  switch (sig1->any.tag)
    {
    case SIG_PRIM:
      return sig1->prim.type == sig2->prim.type;
      break;
    case SIG_CLASS:
      return !strcmp(sig1->clazz.java_class, sig2->clazz.java_class);
      break;
    case SIG_ARRAY:
      return SIG_equal(env, sig1->array.subtype, sig2->array.subtype);
      break;
    case SIG_METHOD:
      {
	int i;
		
	if (sig1->method.num_params != sig2->method.num_params)
	  return 0;
	if (!SIG_equal(env, sig1->method.return_type, sig2->method.return_type))
	  return 0;
		
	for (i = 0; i < sig1->method.num_params; i ++)
	  if (!SIG_equal(env, sig1->method.params[i], sig2->method.params[i]))
	    return 0;
		
	return 1;
      }
    default:
      assert(0);
      return 0;
    }
}

char
SIG_toUnionSelector(JNIEnv *env, Signature *sig)
{
  switch (sig->any.tag)
    {
    case SIG_PRIM:
      switch(sig->prim.type)
	{
	case SIG_JBOOLEAN:
	case SIG_JINT:
	  return 'i';
	  break;
	case SIG_JSHORT:
	  return 's';
	  break;
	case SIG_JCHAR:
	  return 'c';
	  break;
	case SIG_JBYTE:
	  return 'b';
	  break;
	case SIG_JFLOAT:
	  return 'f';
	  break;
	case SIG_JLONG:
	  return 'j';
	  break;
	case SIG_JDOUBLE:
	  return 'd';
	  break;
	case SIG_JOBJECT:
	  return 'l';
	  break;
	default:
	  assert(0);
	}
    case SIG_CLASS:
    case SIG_ARRAY:
      return 'l';
      break;
    case SIG_METHOD:
      fprintf (stderr, "error... methods don't have a union selector\n");
      exit(1);
      break;
    }

  return 0; /* keep cc happy */
}
