/* -*- Mode: C; c-file-style: "gnu" -*-
   header.c -- special code for outputting headers for japharh.
   Created: Chris Toshok <toshok@hungry.com>
 */
/*
  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) 1998 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
*/

#include "config.h"
#include "ClazzFile.h"
#include "resolve.h"
#include "sig.h"
#include "log.h"
#include "japharh.h"
#include "util.h"

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

#define MYLOG "Javah"

static char predefs[100][100];
static int num_predefs = 0;

extern char *javah_dest_dir;
extern JNIEnv *the_env;

static char *
type_to_predef(char *c_type)
{
  static char predef[1024];

  /* we only deal with things that have a * at
     the very end of the string -- because pointers
     to primitive types aren't returned, we can be
     reasonable comfortable in the assumption that
     we're dealing with an object reference of some
     sort. */
  if (c_type[strlen(c_type) - 1] == '*'
      && !strncmp(c_type, "struct", strlen("struct")))
    {
      strcpy(predef, c_type);
      
      /* now get rid of the '*' */
      predef[strlen(predef) - 1] = 0;
      
      return predef;
    }
  else
    return NULL;
}

static void
add_predef(char *predef)
{
  int foo;
  int found = 0;
  
  for (foo = 0; foo < num_predefs; foo ++)
    if (!strcmp(predefs[foo], predef))
      found = 1;
  
  if (!found)
    strcpy(predefs [ num_predefs ++ ], predef);
}

static void
collect_obj_predefs(ClazzFile *cf)
{
  int field_num;
  ClazzFile *superclass = NULL;
  JNIEnv *env = NULL;

  JAVARLOG0(MYLOG, 1, "in collect_obj_predefs()\n");

  num_predefs = 0;

  for (field_num = 0; field_num < cf->num_fields; field_num ++)
    {
      char *predef;
      Signature *sig = SIG_parseFromJavaSig(env, cf->fields[field_num]->sig_str);
      if (cf->fields[field_num]->access_flags & ACC_STATIC)
	continue; /* we don't have to worry about static fields. */

      predef = type_to_predef(SIG_formatToC(env, sig));

      SIG_free(env, sig);

      if (predef != NULL)
	add_predef(predef);
    }

  superclass = cf->superclass;

  if (superclass
      && superclass->access_flags && ACC_ABSTRACT)
    {
      for (field_num = 0; field_num < superclass->num_fields; field_num ++)
	{
	  char *predef;
	  Signature *sig = SIG_parseFromJavaSig(env, superclass->fields[field_num]->sig_str);
	  if (superclass->fields[field_num]->access_flags & ACC_STATIC)
	    continue; /* we don't have to worry about static fields. */
	  
	  predef = type_to_predef(SIG_formatToC(env, sig));
	  
	  SIG_free(env, sig);

	  if (predef != NULL)
	    add_predef(predef);
	}
    }
}

static void
output_predefs(FILE *fp)
{
  int i;

  JAVARLOG0(MYLOG, 1, "in output_predefs()\n");

  for (i = 0; i < num_predefs; i ++)
    {
      fprintf(fp, "%s;\n", predefs[i]);
    }
}

static char *
format_constant_value(ClazzFile *cf, int index)
{
  static char working_str[1024];

  JAVARLOG0(MYLOG, 1, "in format_constant_value()\n");

  switch(cf->constants[index].generic.tag)
    {
    case CONSTANT_Integer:
      sprintf(working_str, "%ldL",
	      *(long*)&(cf->constants[index].integer_info.bytes));
      break;
    case CONSTANT_Double:
      sprintf(working_str, "%f",
	      *(double*)&(cf->constants[index].double_info.high_bytes));
      break;
    case CONSTANT_Float:
      sprintf(working_str, "%f",
	      *(float*)&(cf->constants[index].float_info.bytes));
      break;
    case CONSTANT_Long:
      sprintf(working_str, "(int64_t)0x%08lX%08lX",
	      *(long*)&(cf->constants[index].long_info.high_bytes),
	      *(long*)&(cf->constants[index].long_info.low_bytes));
      break;
    default:
      fprintf(stderr, "Go yell at toshok.  japharh doesn't work\n");
      exit(1);
      break;
    }

  return working_str;
}

static char *
format_constant(ClazzFile *cf, FieldStruct *field)
{
  JAVARLOG0(MYLOG, 1, "in format_constant()\n");

  if (field->has_constant_value)
    return format_constant_value(cf, field->constant_value_index);
  else
    return NULL;
}

static void
output_fields(ClazzFile *cf, FILE *fp, char *bar_name)
{
  int i;
  int num_outputted_fields = 0; /* so we can output a pad C field if 
				   there aren't any non-static java 
				   fields. */
  ClazzFile *superclass = NULL;
  ClazzFile *cur_class;
  JNIEnv *env = NULL;

  JAVARLOG0(MYLOG, 1, "in output_fields()\n");

  for (i = 0; i < cf->num_fields; i ++)
    {
      JAVARLOG1(MYLOG, 1, "Working on field '%s'", 
		cf->fields[i]->name);
	  
      if (cf->fields[i]->access_flags & ACC_FINAL)
	{
	  char *constant = format_constant(cf, cf->fields[i]);
	  if (constant)
	    fprintf (fp, "#define %s_%s %s\n", 
		     bar_name,
		     cf->fields[i]->name,
		     constant);
	  else
	    fprintf (fp, "/* Inaccessible static: %s */\n", 
		     cf->fields[i]->name);
	}
      else if (cf->fields[i]->access_flags & ACC_STATIC)
	{
	  fprintf (fp, "/* Inaccessible static: %s */\n", 
		   cf->fields[i]->name);
	}
      else
	{
	  Signature *sig = SIG_parseFromJavaSig(env, cf->fields[i]->sig_str);
	  fprintf (fp, "   %s %s;\n", 
		   SIG_formatToC(env, sig),
		   cf->fields[i]->name);
		  
	  SIG_free(env, sig);
	  num_outputted_fields ++;
	}
    }
  
  
  /* if the superclass is abstract, we also need to collect the
     predefs for it's fields. */
  cur_class = cf;

  while (cur_class->superclass != 0)
    {
      superclass = cur_class->superclass;
	  
      if (superclass 
	  && superclass->access_flags && ACC_ABSTRACT)
	{
	  for (i = 0; i < superclass->num_fields; i ++)
	    {
	      JAVARLOG1(MYLOG, 1, "Working on field '%s'", 
			superclass->fields[i]->name);
			  
	      if (superclass->fields[i]->access_flags & ACC_FINAL)
		{
		  char *constant = format_constant(superclass, 
						   superclass->fields[i]);
		  if (constant)
		    fprintf (fp, "#define %s_%s %s\n", 
			     bar_name,
			     superclass->fields[i]->name,
			     constant);
		  else
		    fprintf (fp, "/* Inaccessible static: %s */\n", 
			     superclass->fields[i]->name);
		}
	      else if (superclass->fields[i]->access_flags & ACC_STATIC)
		{
		  fprintf (fp, "/* Inaccessible static: %s */\n", 
			   superclass->fields[i]->name);
		}
	      else
		{
		  Signature *sig = SIG_parseFromJavaSig(env, superclass->fields[i]->sig_str);
		  fprintf (fp, "   %s %s;\n", 
			   SIG_formatToC(env, sig),
			   superclass->fields[i]->name);
				  
		  SIG_free(env, sig);
		  num_outputted_fields ++;
		}
	    }
	}
	  
      cur_class = superclass;
    }

  if (num_outputted_fields == 0)
    fprintf (fp, "    char PAD;	/* ANSI C requires structures to have a least one member */");
}

static void
collect_method_predefs(Signature *sig,
		       FILE *fp)
{
  int i;
  JNIEnv *env = NULL;

  JAVARLOG0(MYLOG, 1, "in collect_method_predefs()\n");
  
  num_predefs = 0;
  
  for (i = 0; i < sig->method.num_params; i ++)
    {
      char *predef = type_to_predef(SIG_formatToC(env, sig->method.params[i]));
	  
      if (predef)
	add_predef(predef);
    }
}

void
output_method_prototype(FILE *fp,
			const char *prefix, /* Fill (blanks) at start of line */
			const char *class_name,
			const char *method_name,
			char *sig_str,
			int isStatic)
{
  JNIEnv *env = NULL;
  Signature *sig = SIG_parseFromJavaSig(env, sig_str);
  char *hacked_class;
  char *hacked_method;

  hacked_class = SIG_formatStringToNativeName(the_env, class_name);
  hacked_method = SIG_formatStringToNativeName(the_env, method_name);

  fprintf (fp, "%sextern JNIEXPORT %s JNICALL\n%s Java_%s_%s( JNIEnv *env, %s", 
	   prefix,
	   SIG_formatToC(env, sig->method.return_type),
	   prefix,
	   hacked_class, hacked_method,
	   isStatic ? "jclass cls" : "jobject obj");

  if (SIG_numParams(env, sig) > 0)
    {
      int j;

      fprintf (fp, ", ");
      for (j = 0; j < SIG_numParams(env, sig); j ++)
	{
	  fprintf (fp, "%s%s", SIG_formatToC(env, sig->method.params[j]),
		   j < SIG_numParams(env, sig) - 1 ? ", " : "");
	}
    }

  fprintf (fp, " );\n");
  SIG_free(env, sig);
  free(hacked_class);
  free(hacked_method);
}

void
output_method_prototypes(ClazzFile *cf,
			 FILE *fp)
{
  JNIEnv *env = NULL; 
  int i;

  JAVARLOG0(MYLOG, 1, "in output_method_prototypes()\n");

  for (i = 0; i < cf->num_methods; i ++)
    {
      if (cf->methods[i]->clazz == cf /* since we collapse the method table down... */
	  && cf->methods[i]->access_flags & ACC_NATIVE)
	{
	  /* first we output the predefs it depends on. */
	  Signature *sig = SIG_parseFromJavaSig(env,
						cf->methods[i]->sig_str);
	  collect_method_predefs(sig, fp);
	  output_predefs(fp);
	  output_method_prototype(fp, "", cf->class_name, cf->methods[i]->name,
				  cf->methods[i]->sig_str,
				  cf->methods[i]->access_flags & ACC_STATIC);
	  SIG_free(env, sig);
	}
    }
}

static void
spew_clazz_to_header(ClazzFile *cf, FILE *fp)
{
  int i;

  output_method_prototypes(cf, fp);

  /* now we do the inner classes */
  for (i = 0; i < cf->num_innerclasses; i++)
    {
      if (cf != getInnerclass(the_env, cf, i))
	spew_clazz_to_header(getInnerclass(the_env, cf, i), fp);
    }
}

void
spew_header(ClazzFile *cf, char *bar_name)
{
  char full_name[100];
  FILE *fp;

  dots_to_underscores(bar_name);

  JAVARLOG0(MYLOG, 1, "in output_header()\n");

  strcpy(full_name, javah_dest_dir);
  if (javah_dest_dir[strlen(javah_dest_dir) - 1] != '/')
    strcat(full_name, "/");
  strcat(full_name, bar_name);
  strcat(full_name, ".h");

  collect_obj_predefs(cf);

  fp = fopen(full_name, "w");

  if (NULL == fp)
    {
      fprintf(stderr, "Unable to open %s for writing\n", full_name);
      return;
    }

  fprintf (fp, "/* DO NOT EDIT THIS FILE - it is machine generated */\n");
  fprintf (fp, "#include \"jni.h\"\n");
  fprintf (fp, "/* Header for class %s */\n\n", bar_name);

  fprintf (fp, "#ifndef _Included_%s\n", bar_name);
  fprintf (fp, "#define _Included_%s\n\n", bar_name);

  fprintf (fp, "#ifdef __cplusplus\n");
  fprintf (fp, "extern \"C\" {\n");
  fprintf (fp, "#endif\n\n");

  output_predefs(fp);

  spew_clazz_to_header(cf, fp);

  fprintf (fp, "\n#ifdef __cplusplus\n");
  fprintf (fp, "};\n");
  fprintf (fp, "#endif\n\n");

  fprintf (fp, "#endif /* _Included_%s */\n", bar_name);
  fclose(fp);
}
