#include "runtime.hpp"

#include <cassert>
#include <cstring>
#include <stdexcept>

namespace Interpreter
{
    Runtime::Runtime()
        : mContext(nullptr)
        , mCode(nullptr)
        , mCodeSize(0)
        , mPC(0)
    {
    }

    int Runtime::getPC() const
    {
        return mPC;
    }

    int Runtime::getIntegerLiteral(int index) const
    {
        if (index < 0 || index >= static_cast<int>(mCode[1]))
            throw std::out_of_range("out of range");

        const Type_Code* literalBlock = mCode + 4 + mCode[0];

        return *reinterpret_cast<const int*>(&literalBlock[index]);
    }

    float Runtime::getFloatLiteral(int index) const
    {
        if (index < 0 || index >= static_cast<int>(mCode[2]))
            throw std::out_of_range("out of range");

        const Type_Code* literalBlock = mCode + 4 + mCode[0] + mCode[1];

        return *reinterpret_cast<const float*>(&literalBlock[index]);
    }

    std::string_view Runtime::getStringLiteral(int index) const
    {
        if (index < 0 || static_cast<int>(mCode[3]) <= 0)
            throw std::out_of_range("out of range");

        const char* literalBlock = reinterpret_cast<const char*>(mCode + 4 + mCode[0] + mCode[1] + mCode[2]);

        size_t offset = 0;

        for (; index; --index)
        {
            offset += std::strlen(literalBlock + offset) + 1;
            if (offset / 4 >= mCode[3])
                throw std::out_of_range("out of range");
        }

        return literalBlock + offset;
    }

    void Runtime::configure(const Type_Code* code, int codeSize, Context& context)
    {
        clear();

        mContext = &context;
        mCode = code;
        mCodeSize = codeSize;
        mPC = 0;
    }

    void Runtime::clear()
    {
        mContext = nullptr;
        mCode = nullptr;
        mCodeSize = 0;
        mStack.clear();
    }

    void Runtime::setPC(int PC)
    {
        mPC = PC;
    }

    void Runtime::push(const Data& data)
    {
        mStack.push_back(data);
    }

    void Runtime::push(Type_Integer value)
    {
        Data data;
        data.mInteger = value;
        push(data);
    }

    void Runtime::push(Type_Float value)
    {
        Data data;
        data.mFloat = value;
        push(data);
    }

    void Runtime::pop()
    {
        if (mStack.empty())
            throw std::runtime_error("stack underflow");

        mStack.pop_back();
    }

    Data& Runtime::operator[](int Index)
    {
        if (Index < 0 || Index >= static_cast<int>(mStack.size()))
            throw std::runtime_error("stack index out of range");

        return mStack[mStack.size() - Index - 1];
    }

    Context& Runtime::getContext()
    {
        assert(mContext);
        return *mContext;
    }
}