#include <stdio.h>
#include <assert.h>

FILE *fp = NULL;
int eof  = 0;
size_t bytes_read = 0;

void die(char *str)
{
  puts(str);
  exit(1);
}

typedef unsigned char  u1;
typedef unsigned short u2;
typedef unsigned int   u4;

#define GET(u_type) \
u_type get_##u_type()\
{\
  u4 res = 0, buf;\
  unsigned char shift = (4 - sizeof(u_type)) << 3;\
  if( fread(&buf, sizeof(u_type), 1, fp) != 1 )\
    eof = 1;\
  else\
    bytes_read += sizeof(u_type);\
  res |= (buf & 0x000000ff) << 24;\
  res |= (buf & 0x0000ff00) <<  8;\
  res |= (buf & 0x00ff0000) >>  8;\
  res |= (buf & 0xff000000) >> 24;\
  res >>= shift;\
  return (u_type)res;\
}

GET(u1)
GET(u2)
GET(u4)

const char *cp_info_name[] = {
  "undefined",
  "CONSTANT_Utf8_info",
  "undefined",
  "CONSTANT_Integer",
  "CONSTANT_Float",
  "CONSTANT_Long",
  "CONSTANT_Double",
  "CONSTANT_Class",
  "CONSTANT_String",
  "CONSTANT_Fieldref",
  "CONSTANT_Methodref",
  "CONSTANT_InterfaceMethodref",
  "CONSTANT_NameAndType"
};
  
void read_cp_info(int index)
{
  /** read cp_info struct **/
  u1 tag = get_u1();

  if( tag <= 12 )
  {
    printf("  %2d %s: ", index, cp_info_name[tag]);
    switch (tag)
    {
      case  1: //CONSTANT_Utf8
        {
          //only reads single byte UTF8 strings (very naive)
          int i;
          u2 len = get_u2();
          for(i=0; i<len; i++)
            putc(get_u1(), stdout);
          break;
        }
        
      case  3: //CONSTANT_Integer
        {
          printf("%d", (int)get_u4());
          break;
        }

      case  4: //CONSTANT_Float
        {
          get_u4();
          printf("float N/A");
          break;
        }
        
      case  5: //CONSTANT_Long
        {
          get_u4();
          get_u4();
          printf("long N/A");
          break;
        }
        
      case  6: //CONSTANT_Double
        {
          get_u4();
          get_u4();
          printf("double N/A");
          break;
        }
        
      case  7: //CONSTANT_Class
        {
          printf("classref(%d)", get_u2());
          break;
        }
        
      case  8: //CONSTANT_String
        {
          printf("String(%d)", get_u2());
          break;
        }
        
      case  9: //CONSTANT_Fieldref
        {
          printf("Class(%d).Field of Type(%d)", get_u2(), get_u2());
          break;
        }
        
      case 10: //CONSTANT_Methodref
        {
          printf("Class(%d).Method() of Type(%d)", get_u2(), get_u2());
          break;
        }
        
      case 11: //CONSTANT_InterfaceMethodref
        {
          printf("Class(%d).InterfaceMethod() of Type(%d)", get_u2(), get_u2());
          break;
        }
        
      case 12: //CONSTANT_NameAndType
        {
          printf("Name(%d) is a Type(%d)", get_u2(), get_u2());
          break;
        }
    }
    printf("\n");
  }
  else
  {
    printf("undefined cp_info.tag\n");
  }
}
  
void read_att_list(int att_count)
{
  int i, att_i;
  u2 name_index;
  u4 att_length;
  
  for(i=0; i<att_count; i++)
  {
    printf(" Attribute(%d)=[", i);
    name_index = get_u2();
    att_length = get_u4();

    for(att_i=0; att_i<att_length; att_i++)
    {
      get_u1();
      putc('.', stdout);
    }
    printf("]\n");
  }
}

void read_field_info(int index)
{
  u2 access = get_u2();
  u2 name_index = get_u2();
  u2 type_index = get_u2();
  u2 att_count  = get_u2();
  
  printf(" %d type: value\n");
  read_att_list(att_count);
}

void read_method_info(int index)
{
  u2 access = get_u2();
  u2 name_index = get_u2();
  u2 type_index = get_u2();
  u2 att_count  = get_u2();

  printf(" Name(%d)%s\n", name_index, "()void");
  read_att_list(att_count);
}

int main(int argc, char **argv)
{
  int i;
  if( argc != 2 )
    die("usage: naive-reader file.class");
  
  fp = fopen(argv[1], "rb");

  if( !fp )
    die("fopen() failed");

  /** magic number (4) **/
  u4 magic_number = get_u4();
  printf("Magic Number: %8X\n", magic_number);
  
  /** minor version (2), major version (2) **/
  u2 minor_version,
     major_version;

  minor_version = get_u2();
  major_version = get_u2();
  printf("Version: %X.%X\n", major_version, minor_version);

  /** constant pool count (2) **/
  u2 constant_pool_count;
  constant_pool_count = get_u2();

  printf("Constant Pool[%d]:\n", constant_pool_count);

  /** constant_pool_count times cp_info **/
  for(i=1; i<constant_pool_count && !eof; i++)
    read_cp_info(i);

  /** access flags (2) **/
  u2 access = get_u2();

  printf("Access: ");
  if( access & 0x0001 )
    printf("public ");
  else
    printf("private ");
  
  if( access & 0x0010 )
    printf("final ");
  
  if( access & 0x0020 )
    printf("invokespecial ");
  
  if( access & 0x0400 )
    printf("abstract ");
  
  if( access & 0x0200 )
    printf("interface ");
  else
    printf("class ");
  printf("\n");

  /** this_class (2) **/
  printf("Class: Class(%d)\n", get_u2());

  /** super (2) **/
  printf("Super: Class(%d)\n", get_u2());

  /** interface_count (2) **/
  u2 interface_count = get_u2();
  printf("Interfaces[%d]:\n", interface_count);

  for(i=0; i<interface_count; i++)
  {
    get_u2(); //what are interfaces??
  }

  /** fields_count (2) **/
  u2 fields_count = get_u2();
  printf("Fields[%d]:\n", fields_count);

  for(i=0; i<fields_count; i++)
  {
    read_field_info(i);
  }

  /** method_count (2) **/
  u2 method_count = get_u2();
  printf("Methods[%d]:\n", method_count);

  for(i=0; i<method_count; i++)
  {
    read_method_info(i);
  }

  /** attribute_count (2) **/
  u2 att_count = get_u2();
  printf("Attributes[%d]:\n", att_count);
  
  read_att_list(att_count);
  
  /** at end? **/
  printf("Bytes Read: %d\n", bytes_read);

  if( eof )
  {
    printf("premature end of file\n");
  }
  else
  {
    u1 at_end = get_u1();
    if( eof )
      printf("valid EOF\n");
    else
      printf("file too long\n");
  }
   
  fclose(fp);
  
  return 0;
}


