#pragma once

#include <spider/runtime/common.hpp>
#include <spider/runtime/native/machine.hpp>

namespace spider {

    /**
     * A register is a tiny piece of memory.
     * I hate adding a _t suffix but for some idiotic
     * reason "register" is a keyword in C++.
     * 
     * Note that we have to check the endianness of the system
     * at compile time to order the structure so that smaller
     * types are actually the bottom part of the memory.
     * 
     * Also, this has to be done with a compiler that allows
     * type-punning which is the "standard" right now.
     */
    union register_t {
        u64 _u64;
        i64 _i64;
        f64 _f64;
        u8  _bytes[8];

        struct {
    #if SPIDER_LITTLE_ENDIAN
            u8 _u8; // This looks like a cruel joke
            u8 : 8; u8 : 8; u8 : 8; u8 : 8; u8 : 8; u8 : 8;  u8 : 8;
    #else
            u8 : 8; u8 : 8; u8 : 8; u8 : 8; u8 : 8; u8 : 8;  u8 : 8;
            u8 _u8;
    #endif
        };

        struct {
    #if SPIDER_LITTLE_ENDIAN
            u16 _u16;
            u16 : 16; u16 : 16; u16 : 16;
    #else
            u16 : 16; u16 : 16; u16 : 16;
            u16 _u16;
    #endif
        };

        struct {
    #if SPIDER_LITTLE_ENDIAN
            u32 _u32; u32 : 32;
    #else
            u32 : 32; u32 _u32;
    #endif
        };

        struct {
    #if SPIDER_LITTLE_ENDIAN
            f32 _f32; u32 : 32;
    #else
            u32 : 32; f32 _f32;
    #endif
        };

        u8& operator[](size_t i) { // 0 is always LSB
    #if SPIDER_LITTLE_ENDIAN
            return _bytes[i];
    #else
            return _bytes[7 - i];
    #endif
        }

        // ngl I could get executed for not having a const version
        const u8& operator[](size_t i) const { // 0 is always LSB
    #if SPIDER_LITTLE_ENDIAN
            return _bytes[i];
    #else
            return _bytes[7 - i];
    #endif
        }
    };
    static_assert(sizeof(register_t) == 8, "The register type must be exactly 8 bytes.");

}