#include "InstrReelDyn.hpp"

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

namespace spider {

    InstrReelDyn::InstrReelDyn(u64 length) : _use_count(0), _block_index(0) {
        _total_size = length;
        growToFit(length > 0 ? length - 1 : 0);
        selectBlock(0);
    }

    InstrReelDyn::InstrReelDyn(const u8* data, u64 length) {}

    InstrReelDyn::InstrReelDyn(const InstrReelDyn& copy) : _use_count(copy._use_count), _block_index(copy._block_index), _blocks(copy._blocks) {
        if (_block_index < _blocks.size()) selectBlock(_block_index);
    }

    InstrReelDyn::InstrReelDyn(InstrReelDyn&& move) noexcept : _use_count(move._use_count), _block_index(move._block_index), _blocks(std::move(move._blocks)) {
        if (_block_index < _blocks.size()) selectBlock(_block_index);
    }

    InstrReelDyn::~InstrReelDyn() {
        // .. //
    }

    InstrReelDyn& InstrReelDyn::operator=(const InstrReelDyn& copy) {
        _use_count = copy._use_count;
        _block_index = copy._block_index;
        _blocks = copy._blocks;
        if (_block_index < _blocks.size()) selectBlock(_block_index);
        return *this;
    }

    InstrReelDyn& InstrReelDyn::operator=(InstrReelDyn&& move) noexcept {
        _use_count = move._use_count;
        _block_index = move._block_index;
        _blocks = std::move(move._blocks);
        if (_block_index < _blocks.size()) selectBlock(_block_index);

        move._use_count = 0;
        move._block_index = 0;
        move._mem = nullptr;
        move._offset = 0;
        move._size = 0;
        move._total_size = 0;
        return *this;
    }

    void InstrReelDyn::growToFit(isize index) {
        while (_blocks.size() < (index + 1)) {
            _blocks.emplace_back();
        }
    }

    isize InstrReelDyn::selectIndex(u64 ip) {
        return ip / 256;
    }

    InstrReelDyn::ReelBlock* InstrReelDyn::selectBlock(isize index) {
        // Update base class cache
        auto ptr = &_blocks[index];
        _offset = index * 256;
        _mem = ptr->data;
        _size = 256;
        _block_index = index;

        //_blocks[block_idx].access_count++;
        return ptr;
    }

    u8 InstrReelDyn::atU8(u64 ip) {
        isize j = selectIndex(ip);
        if (j >= _blocks.size()) return 0;
        if (j != _block_index) {
            this->selectBlock(j);
        }
        return _mem[ip - _offset];
    }

    u16 InstrReelDyn::atU16(u64 ip) {
        isize j0 = selectIndex(ip);
        isize j1 = selectIndex(ip + 1);
        if (j1 >= _blocks.size()) return 0;
        if (j0 == j1 && j0 != _block_index) {
            selectBlock(j0);
        }
        if (j0 == j1 && j0 == _block_index) {
            u16 dat;
            spider::loadLE(&dat, _mem);
            return dat;
        }

        // general case, first part
        u16 dat = 0;
        const u8 size = sizeof(u16);

        // select first block and offset
        selectBlock(j0);
        u8 rem = ip % 256;

        for (u8 n = 0; n < size; n++) {
            dat |= _mem[rem++] << (n * 8);
            ip++;
            if (!rem) selectBlock(++j0);
        }

        return dat;
    }

    u32 InstrReelDyn::atU32(u64 ip) {
        isize j0 = selectIndex(ip);
        isize j1 = selectIndex(ip + 3);
        if (j1 >= _blocks.size()) return 0;
        if (j0 == j1 && j0 != _block_index) {
            selectBlock(j0);
        }
        if (j0 == j1 && j0 == _block_index) {
            u32 dat;
            spider::loadLE(&dat, _mem);
            return dat;
        }

        // general case, first part
        u32 dat = 0;
        const u8 size = sizeof(u32);

        // select first block and offset
        selectBlock(j0);
        u8 rem = ip % 256;

        for (u8 n = 0; n < size; n++) {
            dat |= _mem[rem++] << (n * 8);
            ip++;
            if (!rem) selectBlock(++j0);
        }

        return dat;
    }

    u64 InstrReelDyn::atU64(u64 ip) {
        isize j0 = selectIndex(ip);
        isize j1 = selectIndex(ip + 3);
        if (j1 >= _blocks.size()) return 0;
        if (j0 == j1 && j0 != _block_index) {
            selectBlock(j0);
        }
        if (j0 == j1 && j0 == _block_index) {
            u64 dat;
            spider::loadLE(&dat, _mem);
            return dat;
        }

        // general case, first part
        u64 dat = 0;
        const u8 size = sizeof(u64);

        // select first block and offset
        selectBlock(j0);
        u8 rem = ip % 256;

        for (u8 n = 0; n < size; n++) {
            dat |= _mem[rem++] << (n * 8);
            ip++;
            if (!rem) selectBlock(++j0);
        }

        return dat;
    }

    void InstrReelDyn::at(u64 ip, u8 dat) {}

    void InstrReelDyn::at(u64 ip, u16 dat) {}

    void InstrReelDyn::at(u64 ip, u32 dat) {}

    void InstrReelDyn::at(u64 ip, u64 dat) {}

    /**
     * Appends instruction at location.
     */
    void InstrReelDyn::append(u64 ip, u16 bc) {}

    /**
     * Appends instruction at the end.
     */
    void InstrReelDyn::append(u16 bc) {}

    /**
     * Removes instruction at location.
     */
    void InstrReelDyn::remove(u64 ip) {}

}
