#pragma endian big
#pragma MIME application/x-java-applet

#include <std/io.pat>
#include <std/sys.pat>

// The Java documentations use the number of bytes instead of the number of bits for its type names
using u1 = u8;
using u2 = u16;
using u4 = u32;

using f4 = float;
using f8 = double;

enum cp_tag : u8 {
    CONSTANT_Utf8               = 1,
    CONSTANT_Integer            = 3,
    CONSTANT_Float              = 4,
    CONSTANT_Long               = 5,
    CONSTANT_Double             = 6,
    CONSTANT_Class              = 7,
    CONSTANT_String             = 8,
    CONSTANT_Fieldref           = 9,
    CONSTANT_Methodref          = 10,
    CONSTANT_InterfaceMethodref = 11,
    CONSTANT_NameAndType        = 12,
    CONSTANT_MethodHandle       = 15,
    CONSTANT_MethodType         = 16,
    CONSTANT_InvokeDynamic      = 18
};

enum major_version : u2 {
    JDK_1_1              = 45,
    JDK_1_2              = 46,
    JDK_1_3              = 47,
    JDK_1_4              = 48,

    Java_SE_5_0          = 49,
    Java_SE_6_0          = 50,
    Java_SE_7            = 51,
    Java_SE_8            = 52,
    Java_SE_9            = 53,
    Java_SE_10           = 54,
    Java_SE_11           = 55,
    Java_SE_12           = 56,
    Java_SE_13           = 57,
    Java_SE_14           = 58,
    Java_SE_15           = 59,
    Java_SE_16           = 60,
    Java_SE_17           = 61,
};

// Tag == CONSTANT_Utf8
struct CONSTANT_Utf8_info {
    u2 length;
    char bytes[length];    // u1 in documentation. Changed to char to improve readability
};

// Tag == CONSTANT_Integer
struct CONSTANT_Integer_info {
    u4 bytes;
};

// Tag == CONSTANT_Float
struct CONSTANT_Float_info {
    f4 bytes; // u4 in documentation. Changed to f4 to improve readablilty
};

// Tag == CONSTANT_Long
struct CONSTANT_Long_info {
    u4 high_bytes;
    u4 low_bytes;
};

// Tag == CONSTANT_Double
struct CONSTANT_Double_info {
    u4 high_bytes;
    u4 low_bytes;
};

// Tag == CONSTANT_Class
struct CONSTANT_Class_info {
    u2 name_index;
};

// Tag == CONSTANT_String
struct CONSTANT_String_info {
    u2 string_index;
};

// Tag == CONSTANT_Fieldref
struct CONSTANT_Fieldref_info {
    u2 class_index;
    u2 name_and_type_index;
};

// Tag == CONSTANT_Methodref
struct CONSTANT_Methodref_info {
    u2 class_index;
    u2 name_and_type_index;
};

// Tag == CONSTANT_InterfaceMethodref
struct CONSTANT_InterfaceMethodref_info {
    u2 class_index;
    u2 name_and_type_index;
};

// Tag == CONSTANT_NameAndType
struct CONSTANT_NameAndType_info {
    u2 name_index;
    u2 descriptor_index;
};

// Tag == CONSTANT_MethodHandle
struct CONSTANT_MethodHandle_info {
    u1 reference_kind;
    u2 reference_index;
};

// Tag == CONSTANT_MethodType
struct CONSTANT_MethodType_info {
    u2 descriptor_index;
};

// Tag == CONSTANT_InvokeDynamic
struct CONSTANT_InvokeDynamic_info {
    u2 bootstrap_method_attr_index;
    u2 name_and_type_index;
};

struct cp_info {
    cp_tag tag;

    if (tag == cp_tag::CONSTANT_Utf8)
        CONSTANT_Utf8_info info [[inline]];
    else if (tag == cp_tag::CONSTANT_Integer)
        CONSTANT_Integer_info info [[inline]];
    else if (tag == cp_tag::CONSTANT_Float)
        CONSTANT_Float_info info [[inline]];
    else if (tag == cp_tag::CONSTANT_Long)
        CONSTANT_Long_info info [[inline]];
    else if (tag == cp_tag::CONSTANT_Double)
        CONSTANT_Double_info info [[inline]];
    else if (tag == cp_tag::CONSTANT_Class)
        CONSTANT_Class_info info [[inline]];
    else if (tag == cp_tag::CONSTANT_String)
        CONSTANT_String_info info [[inline]];
    else if (tag == cp_tag::CONSTANT_Fieldref)
        CONSTANT_Fieldref_info info [[inline]];
    else if (tag == cp_tag::CONSTANT_Methodref)
        CONSTANT_Methodref_info info [[inline]];
    else if (tag == cp_tag::CONSTANT_InterfaceMethodref)
        CONSTANT_InterfaceMethodref_info info [[inline]];
    else if (tag == cp_tag::CONSTANT_NameAndType)
        CONSTANT_NameAndType_info info [[inline]];
    else if (tag == cp_tag::CONSTANT_MethodHandle)
        CONSTANT_MethodHandle_info info [[inline]];
    else if (tag == cp_tag::CONSTANT_MethodType)
        CONSTANT_MethodType_info info [[inline]];
    else if (tag == cp_tag::CONSTANT_InvokeDynamic)
        CONSTANT_InvokeDynamic_info info [[inline]];
};

bitfield access_flags_method  {
    ACC_PUBLIC      : 1;    // 0x0001
    ACC_PRIVATE     : 1;    // 0x0002
    ACC_PROTECTED   : 1;    // 0x0004
    ACC_STATIC      : 1;    // 0x0008
    ACC_FINAL       : 1;    // 0x0010
    ACC_SYNCHRONIZED: 1;    // 0x0020
    ACC_BRIDGE      : 1;    // 0x0040
    ACC_VARARGS     : 1;    // 0x0080
    ACC_NATIVE      : 1;    // 0x0100
    padding         : 1;    // 0x0200
    ACC_ABSTRACT    : 1;    // 0x0400
    ACC_STRICT      : 1;    // 0x0800
    ACC_SYNTHETIC   : 1;    // 0x1000
    padding         : 1;    // 0x2000
    padding         : 1;    // 0x4000
    padding         : 1;    // 0x8000
};

bitfield access_flags_field  {
    ACC_PUBLIC      : 1;    // 0x0001
    ACC_PRIVATE     : 1;    // 0x0002
    ACC_PROTECTED   : 1;    // 0x0004
    ACC_STATIC      : 1;    // 0x0008
    ACC_FINAL       : 1;    // 0x0010
    padding         : 1;    // 0x0020
    ACC_VOLATILE    : 1;    // 0x0040
    ACC_TRANSIENT   : 1;    // 0x0080
    padding         : 1;    // 0x0100
    padding         : 1;    // 0x0200
    padding         : 1;    // 0x0400
    padding         : 1;    // 0x0800
    ACC_SYNTHETIC   : 1;    // 0x1000
    padding         : 1;    // 0x2000
    ACC_ENUM        : 1;    // 0x4000
    padding         : 1;    // 0x8000
};

bitfield access_flags_class  {
    ACC_PUBLIC       : 1;    // 0x0001
    padding          : 1;    // 0x0002
    padding          : 1;    // 0x0004
    padding          : 1;    // 0x0008
    ACC_FINAL        : 1;    // 0x0010
    ACC_SUPER        : 1;    // 0x0020
    padding          : 1;    // 0x0040
    padding          : 1;    // 0x0080
    padding          : 1;    // 0x0100
    ACC_INTERFACE    : 1;    // 0x0200
    ACC_ABSTRACT     : 1;    // 0x0400
    padding          : 1;    // 0x0800
    ACC_SYNTHETIC    : 1;    // 0x1000
    ACC_ANNOTATION   : 1;    // 0x2000
    ACC_ENUM         : 1;    // 0x4000
    padding          : 1;    // 0x8000
};

struct attribute_info {
    u2 attribute_name_info;

    u4 attribute_length;

    u1 info[attribute_length];
};

struct field_info {
    access_flags_field     access_flags;
    u2                     name_index;
    u2                     descriptor_index;

    u2                     attribute_count;
    attribute_info         attributes[attribute_count];
};

struct method_info {
    access_flags_method    access_flags;
    u2                     name_index;
    u2                     descriptor_index;
    u2                     attributes_count;
    attribute_info         attributes[attributes_count];
};

struct ClassFile {
    u4 magic;

    u2                     minor_version;
    major_version          major_version;

    u2                     constant_pool_count;
    cp_info                constant_pool[constant_pool_count - 1];

    access_flags_class     access_flags;
    u2                     this_class;
    u2                     super_class;

    u2                     interfaces_count;
    u2                     interfaces[interfaces_count];

    u2                     fields_count;
    field_info             fields[fields_count];

    u2                     methods_count;
    method_info            methods[methods_count];

    u2                     attribute_count;
    attribute_info         attributes[attribute_count];
};

ClassFile class_file @ 0x00;

fn main() {

    std::assert(class_file.magic == 0xCAFEBABE, "File is not a valid Java Class file! Invalid header magic");


    std::print("Fields:");
    for (le u16 i = 0, i < class_file.fields_count, i = i + 1) {
        str method_string = "  ";

        if (class_file.fields[i].access_flags.ACC_PUBLIC)
            method_string = method_string + "public ";
        if (class_file.fields[i].access_flags.ACC_PRIVATE)
            method_string = method_string + "private ";
        if (class_file.fields[i].access_flags.ACC_PROTECTED)
            method_string = method_string + "protected ";
        if (class_file.fields[i].access_flags.ACC_STATIC)
            method_string = method_string + "static ";
        if (class_file.fields[i].access_flags.ACC_FINAL)
            method_string = method_string + "final ";
        if (class_file.fields[i].access_flags.ACC_VOLATILE)
            method_string = method_string + "volatile ";
        if (class_file.fields[i].access_flags.ACC_NATIVE)
            method_string = method_string + "native ";
        if (class_file.fields[i].access_flags.ACC_TRANSIENT)
            method_string = method_string + "transient ";
        if (class_file.fields[i].access_flags.ACC_ENUM)
            method_string = method_string + "enum ";

        method_string = method_string + class_file.constant_pool[class_file.fields[i].name_index - 1].info.bytes;
        method_string = method_string + " [ " + class_file.constant_pool[class_file.fields[i].descriptor_index - 1].info.bytes + " ]";

        std::print("{}", method_string);
    }

    std::print("Methods:");
    for (le u16 i = 0, i < class_file.methods_count, i = i + 1) {
        str method_string = "  ";

        if (class_file.methods[i].access_flags.ACC_PUBLIC)
            method_string = method_string + "public ";
        if (class_file.methods[i].access_flags.ACC_PRIVATE)
            method_string = method_string + "private ";
        if (class_file.methods[i].access_flags.ACC_PROTECTED)
            method_string = method_string + "protected ";
        if (class_file.methods[i].access_flags.ACC_STATIC)
            method_string = method_string + "static ";
        if (class_file.methods[i].access_flags.ACC_FINAL)
            method_string = method_string + "final ";
        if (class_file.methods[i].access_flags.ACC_SYNCHRONIZED)
            method_string = method_string + "synchronized ";
        if (class_file.methods[i].access_flags.ACC_NATIVE)
            method_string = method_string + "native ";
        if (class_file.methods[i].access_flags.ACC_ABSTRACT)
            method_string = method_string + "abstract ";
        if (class_file.methods[i].access_flags.ACC_STRICT)
            method_string = method_string + "strictfp ";

        method_string = method_string + class_file.constant_pool[class_file.methods[i].name_index - 1].info.bytes;
        method_string = method_string + " [ " + class_file.constant_pool[class_file.methods[i].descriptor_index - 1].info.bytes + " ]";

        std::print("{}", method_string);
    }

    std::print("Class Attributes:");
    for (le u16 i = 0, i < class_file.attribute_count, i = i + 1) {
        std::print("  {}", class_file.constant_pool[class_file.attributes[i].attribute_name_info - 1].info.bytes);
    }
};