#include "InstrReelFixed.hpp"

#include <spider/runtime/memory/Types.hpp>

#include <cstring>

namespace spider {

    // Constructors & Destructors //

    InstrReelFixed::InstrReelFixed(u64 length)
    : _mem(nullptr), _size(length) {
        if (_size > 0) {
            _mem = new u8[_size];
            std::memset(_mem, 0, _size);
        }
    }

    InstrReelFixed::InstrReelFixed(const u8* data, u64 length)
    : _mem(nullptr), _size(length) {
        if (_size > 0) {
            _mem = new u8[_size];
            std::copy(data, data + _size, _mem);
        }
    }

    InstrReelFixed::InstrReelFixed(const InstrReelFixed& other) {
        _size = other._size;
        _mem = new u8[_size];
        std::copy(other._mem, other._mem + _size, _mem);
    }

    InstrReelFixed::InstrReelFixed(InstrReelFixed&& other) noexcept {
        _mem = other._mem;
        _size = other._size;

        other._mem = nullptr;
        other._size = 0;
    }

    InstrReelFixed::~InstrReelFixed() {
        delete[] _mem;
    }

    // General Case(s) //

    // Instruction abstraction //

    u8 InstrReelFixed::readU8(u64 ip) {
        // guard against access
        if(ip + 1 > _size) return 0;

        // send byte
        return _mem[ip];
    }

    u16 InstrReelFixed::readU16(u64 ip) {
        // guard against access
        if(ip + 2 > _size) return 0;

        // build a 16-bit big endian number
        u16 dat;
        spider::loadLE(&dat, _mem + ip);
        return dat;
    }

    u32 InstrReelFixed::readU32(u64 ip) {
        // guard against access
        if(ip + 4 > _size) return 0;

        // build a 32-bit big endian number
        u32 dat;
        spider::loadLE(&dat, _mem + ip);
        return dat;
    }

    u64 InstrReelFixed::readU64(u64 ip) {
        // guard against access
        if(ip + 8 > _size) return 0;

        // build a 64-bit big endian number
        u64 dat;
        spider::loadLE(&dat, _mem + ip);
        return dat;
    }

    void InstrReelFixed::readRange(u64 ip, u8* out, u64 length) {
        if(ip + length > _size) {
            std::memset(out, 0, length);
            return;
        }
        std::memcpy(out, _mem + ip, length);
    }

    u64 InstrReelFixed::size() {
        return _size;
    }

    // Assign Operators //

    InstrReelFixed& InstrReelFixed::operator=(const InstrReelFixed& other) {
        if (this == &other) return *this; // lock self

        u8* new_mem = new u8[other._size];
        std::copy(other._mem, other._mem + other._size, new_mem);

        delete[] _mem;
        _mem = new_mem;
        _size = other._size;

        return *this;
    }

    InstrReelFixed& InstrReelFixed::operator=(InstrReelFixed&& other) noexcept {
        if (this == &other) return *this; // lock self

        delete[] _mem;

        _mem = other._mem; // steal
        _size = other._size;

        other._mem = nullptr; // leave as husk
        other._size = 0;

        return *this;
    }

    // Misc //

    void InstrReelFixed::writeU8(u64 ip, u8 dat) {
        if(ip + 1 > _size) return;
        _mem[ip] = dat;
    }

    void InstrReelFixed::writeU16(u64 ip, u16 dat) {
        if(ip + 2 > _size) return;
        spider::storeLE(dat, _mem + ip);
    }

    void InstrReelFixed::writeU32(u64 ip, u32 dat) {
        if(ip + 4 > _size) return;
        spider::storeLE(dat, _mem + ip);
    }

    void InstrReelFixed::writeU64(u64 ip, u64 dat) {
        if(ip + 8 > _size) return;
        spider::storeLE(dat, _mem + ip);
    }

    void InstrReelFixed::resize(u64 new_size) {
        // Special case 1
        if (new_size == _size) return;

        // Special case 2
        if (new_size == 0) {
            delete[] _mem;
            _mem = nullptr;
            _size = 0;
            return;
        }

        // 1. Allocate the new block
        u8* new_mem = new u8[new_size];

        // 2. Zero-initialize
        std::memset(new_mem, 0, new_size);

        // 3. Preserve data
        // If shrinking, copy 'new_size' bytes. If growing, copy 'old_size' bytes.
        u64 bytes_to_copy = (new_size < _size) ? new_size : _size;

        // 3.1 Previous size could be zero, where _mem would be null
        if (_mem != nullptr) {
            std::copy(_mem, _mem + bytes_to_copy, new_mem);
        }

        // 4. Swap and Clean up
        delete[] _mem;
        _mem = new_mem;
        _size = new_size;
    }

}
