#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>

#include "classfile.h"
#include "bytecode.h"
#include "util.h"

#define BUFFER_SIZE 1024
char buffer[BUFFER_SIZE];
char buffer2[BUFFER_SIZE];

const char * get_arg_name(const char * str)
{
   char * ptr;
   strcpy(buffer, str);
   ptr = strtok(buffer, "(); ");
   if(ptr != NULL) {
      ptr = strtok(NULL, "(); ");
      if(ptr != NULL) {
          ptr = strtok(NULL, "(); ");
          if(ptr != NULL) {
             ptr = strtok(NULL, "(); ");
          }
      }
   }
   if(ptr == NULL) 
      ptr = "";
   return ptr; 
}

char * get_access(const char * str)
{
   char * ptr;

   strcpy(buffer, str);
   memset(buffer2, 0, BUFFER_SIZE);

   ptr = strtok(buffer, " ");
   while(ptr != NULL) {
      if(strcmp(ptr, "public") == 0
         || strcmp(ptr, "private") == 0
         || strcmp(ptr, "protected") == 0) {
         if(strlen(buffer2) != 0) {
            strncat(buffer2, " ", BUFFER_SIZE-1);
         }
         strncat(buffer2, ptr, BUFFER_SIZE-1);
      }
      ptr = strtok(NULL, " ");
   }

   return buffer2;
}

char * get_modifiers(const char * str)
{
   char * ptr;

   strcpy(buffer, str);
   memset(buffer2, 0, BUFFER_SIZE);

   ptr = strtok(buffer, " ");
   while(ptr != NULL) {
      if(strcmp(ptr, "public") != 0
         && strcmp(ptr, "private") != 0
         && strcmp(ptr, "protected") != 0) {
         if(strlen(buffer2) != 0) {
            strncat(buffer2, " ", BUFFER_SIZE);
         }
         strncat(buffer2, ptr, BUFFER_SIZE);
      }
      ptr = strtok(NULL, " ");
   }

   return buffer2;

}

int count_param(const char * sig) 
{
   int ret = 0;
   int n = strlen(sig);
   size_t i;
   for(i = 0; i < n; i++) {
      if(sig[i] == ';') {
         ret++;
      }
   }

   return ret;
}

extern char * get_type_signature(const char * name);

extern void disassemble(const char* filename, const char * outfile);

void disassemble(const char* filename, const char * outfile)
{
	ClassFile* classFile;
	int i, z;
	Constant *p, *ref, *className, *name, *descriptor;
	Method* method;
	Field* field;
	Attribute* codeAttribute;
	char buffer[1024], constantInfo[10], *dot, localClassName[128];
        char * sig;
        FILE * out;

        out = fopen(outfile, "w");

	classFile = read_class_file(filename);
	if (classFile == NULL)
	{
		fprintf(stderr, "Unable to read class file: '%s'\n", filename);
		return;
	}

	ref = find_constant(classFile, classFile->this_class);
	className = find_constant(classFile, ref->ref);

	//fprintf(stdout, "/*\n    Filename: %s\n    Class %s\n*/\n", filename, class_name_from_internal(className->buffer));

	//fprintf(stdout, "/*\n    Constant Pool\n\n");
/*
	for (p = classFile->constants, i = 0; i < classFile->constant_count; i += 1, p += 1)
	{
		switch (p->tag)
		{
			case TAG_CLASSREF:
			case TAG_STRINGREF:
				sprintf(constantInfo, "#%hd", p->ref);
				break;

			case TAG_FIELDREF:
			case TAG_METHODREF:
			case TAG_IFACEREF:
				sprintf(constantInfo, "#%hd, #%hd", p->classref, p->typedescref);
				break;

			case TAG_TYPEDESC:
				sprintf(constantInfo, "#%hd, #%hd", p->nameref, p->typeref);
				break;

			default:
				strcpy(constantInfo, "");
		}

		fprintf(out, "    %3d: %-9s %-10s %s\n", p->index, ConstantTypes[p->tag].name, 
			constantInfo, constant_to_string_r(classFile, p, buffer));
	}
*/
	//fprintf(stdout, "*/\n");
	//fprintf(stdout, "\n");

        fprintf(out, ".class %s\n", class_name_from_internal(className->buffer));
        fprintf(out, ".access %s\n", access_flags_to_string(classFile->access_flags));
        fprintf(out, ".modifiers\n");

        if (classFile->super_class)
        {
                ref = find_constant(classFile, classFile->super_class);
                name = find_constant(classFile, ref->ref);
        	fprintf(out, ".extends %s\n", class_name_from_internal(name->buffer));
        }
        else {
        	fprintf(out, ".extends java.lang.Object\n");
	}

        //fprintf(stdout, ".extends %s\n", class_name_from_internal(name->buffer));
        fprintf(out, ".implements ");
        if (classFile->interface_count > 0)
        {
                //fprintf(stdout, " implements");
                for (i = 0; i < classFile->interface_count; i += 1)
                {
                        ref = find_constant(classFile, classFile->interfaces[i]);
                        name = find_constant(classFile, ref->ref);
                        fprintf(out, " %s", class_name_from_internal(name->buffer));
                }
        }
        fprintf(out, "\n");


	//fprintf(stdout, "%s class ", access_flags_to_string(classFile->access_flags));
	//fprintf(stdout, "%s ", class_name_from_internal(className->buffer));

	dot = strrchr(class_name_from_internal(className->buffer), '.');
	strcpy(localClassName, dot ? dot + 1 : class_name_from_internal(className->buffer));

	if (classFile->super_class)
	{
		ref = find_constant(classFile, classFile->super_class);
		name = find_constant(classFile, ref->ref);
		//fprintf(stdout, "extends %s", class_name_from_internal(name->buffer));
	}

	if (classFile->interface_count > 0)
	{
		fprintf(stdout, " implements");
		for (i = 0; i < classFile->interface_count; i += 1)
		{
			ref = find_constant(classFile, classFile->interfaces[i]);
			name = find_constant(classFile, ref->ref);
			//fprintf(stdout, " %s", class_name_from_internal(name->buffer));
		}
	}

	fprintf(out, "\n");

	for (i = 0, field = classFile->fields; i < classFile->field_count; i += 1, field += 1)
	{
		name = find_constant(classFile, field->name_index);
		descriptor = find_constant(classFile, field->descriptor_index);
		descriptor_to_string(descriptor->buffer, name->buffer, buffer);
		//fprintf(stdout, "\t%s %s;\n", access_flags_to_string(field->access_flags), buffer);
                char * ptr = strchr(buffer, ' ');
		*ptr = '\0';
                *ptr++;
                fprintf(out, ".field %s\n", ptr);
                fprintf(out, ".access %s\n", access_flags_to_string(field->access_flags));
                fprintf(out, ".modifiers\n");
                fprintf(out, ".type %s\n\n", buffer);
	}

	for (i = 0, method = classFile->methods; i < classFile->method_count; i += 1, method += 1)
	{
                sig = 0;

		fprintf(out, "\n");

		name = find_constant(classFile, method->name_index);
		if (strcmp(name->buffer, "<clinit>") == 0)
		{
			// Static Class Initializer
			//fprintf(stdout, "    static\n");
                        fprintf(out, ".method <clinit>\n");
		}
		else
		{
			descriptor = find_constant(classFile, method->descriptor_index);

			if (strcmp(name->buffer, "<init>") == 0) // Constructor
				descriptor_to_string_ex(descriptor->buffer, localClassName, buffer, FLAG_OMIT_RETURN_TYPE);
			else
				descriptor_to_string(descriptor->buffer, name->buffer, buffer);

			//fprintf(stdout, "%s %s\n", access_flags_to_string(method->access_flags), buffer);
                        sig = strdup(buffer);
                        fprintf(out, ".method %s\n", name->buffer);
		}


		codeAttribute = find_attribute(classFile, ATT_NAME_CODE, method->attribute_count, method->attributes);
		if (codeAttribute == NULL) {
                        fprintf(stdout, ".maxStack 0\n");
                        const char * temp = access_flags_to_string(method->access_flags);
                        fprintf(out, ".access %s\n", get_access(temp));
                        fprintf(out, ".modifiers %s\n", get_modifiers(temp));
                        if(get_type_signature(sig) == NULL) 
                           fprintf(stdout, ".signature ()V\n");
                        else
                           fprintf(stdout, ".signature %s\n", get_type_signature(sig));
                        fprintf(out, ".args\n");
                        fprintf(out, ".data\n");
                        fprintf(out, ".code\n");
                }
		else {
                        const char * temp2 = get_type_signature(sig);
                        int n = count_param(temp2);
printf("n == %d\n", n);
printf("sig == %s\n", temp2);
getchar();
			fprintf(out, ".maxStack %d\n", ((codeAttribute->code.max_stack + n) * 2) - 1);
                        const char * temp = access_flags_to_string(method->access_flags);
                        fprintf(out, ".access %s\n", get_access(temp));
                        fprintf(out, ".modifiers %s\n", get_modifiers(temp));
			if(get_type_signature(sig) == NULL) 
			   fprintf(out, ".signature ()V\n");
			else
			   fprintf(out, ".signature %s\n", get_type_signature(sig));
			fprintf(out, ".args %s\n", get_arg_name(sig));
			fprintf(out, ".data\n");
			for(z = 1; z <= ((codeAttribute->code.max_stack + n) * 2) - 1; z++) {
			   fprintf(out, "\t%s temp%d\n", "int", z);
                        }
			fprintf(out, ".code");

			dump_code_attribute(out, classFile, codeAttribute);
                }
                free(sig);
                fprintf(out, ".end\n");
		//fprintf(stdout, "    }\n");
	}

	//fprintf(stdout, "}\n");

	free_class(classFile);

	fclose(out);
}

#ifdef HAVE_MAIN 
int main(int argc, char** argv)
{
	int i;

	for (i = 1; i < argc; i += 1)
		disassemble(argv[i], "out.asm");

	return 0;
}
#endif
