/******************************************************************************
*	File		-	wren_state.cpp
*	Author		-	Joey Pollack
*	Date		-	2022/10/25 (y/m/d)
*	Mod Date	-	2022/10/25 (y/m/d)
*	Description	-	Manages the wren vm, runs scripts. This is the main
*                   interface for wren.
******************************************************************************/

#include "wren_state.h"
#include "wren_script.h"
#include "coreAPI.h"
#include <utils/logger.h>
#include <utils/helpers.h>

namespace lunarium
{
    std::vector<WrenState::ForeignMethodDesc> WrenState::mForeignMethods;
    std::vector<WrenState::ForeignClassDesc> WrenState::mForeignClasses;
    
    WrenState::~WrenState()
    {
        Shutdown();
    }

    void WrenState::Shutdown()
    {
        if (mpVM)
            wrenFreeVM(mpVM);
        mpVM = nullptr;
    }

    u32 WrenState::mLogCat = Logger::RegisterCategory("SCRIPT");
    OpRes WrenState::Initialize()
    {
        WrenConfiguration config;
        wrenInitConfiguration(&config);
        config.writeFn = WrenState::WriteFN;
        config.errorFn = WrenState::ErrorFN;
        config.bindForeignMethodFn = WrenState::BindForeignMethodFN;
        config.bindForeignClassFn = WrenState::BindForeignClassFN;

	    mpVM = wrenNewVM(&config);

        CoreAPI::GetInstance().Initialize(*this);

        return OpRes::OK();
    }

    u32 WrenState::GetLogCat() const
    {
        return mLogCat;
    }

    void WrenState::RegisterForeignMethod(ForeignMethodDesc method_desc)
    {
        mForeignMethods.push_back(method_desc);
    }

    void WrenState::RegisterForeignClass(ForeignClassDesc class_desc)
    {
        mForeignClasses.push_back(class_desc);
    }

    /////////////////////////////////////////////////////////////////////
    //				HANDLES
    /////////////////////////////////////////////////////////////////////

    WrenHandle* WrenState::GetWrenClassHandle(std::string from_module, std::string class_name)
    {
        // Load the class into slot 0.
        wrenEnsureSlots(mpVM, 1);
        wrenGetVariable(mpVM, from_module.c_str(), class_name.c_str(), 0);
        WrenHandle* handle = wrenGetSlotHandle(mpVM, 0);
        return handle;
    }

    WrenHandle* WrenState::GetWrenMethodHandle(std::string method_signature)
    {
        WrenHandle* handle = wrenMakeCallHandle(mpVM, method_signature.c_str());
        return handle;
    }

    void WrenState::CallWrenMethod(WrenHandle* method, WrenHandle* receiver, std::vector<WrenParameter> params, std::string name_tag)
    {

        wrenEnsureSlots(mpVM, params.size() + 1);
        for (int i = 0; i < params.size(); i++)
        {
            switch (params[i].Type)
            {
                case WrenParamType::WPT_DOUBLE: wrenSetSlotDouble(mpVM, i + 1, params[i].As.Double); break;
                case WrenParamType::WPT_BOOL: wrenSetSlotBool(mpVM, i + 1, params[i].As.Bool); break;
                case WrenParamType::WPT_STR: wrenSetSlotString(mpVM, i + 1, params[i].As.String); break;
                default: Logger::Warn(mLogCat, "Unknown wren parameter type: %d", (int)params[i].Type);
            }
        }

        wrenSetSlotHandle(mpVM, 0, receiver);
        WrenInterpretResult result = wrenCall(mpVM, method);

        switch (result)
        {
            case WREN_RESULT_COMPILE_ERROR: Logger::Trace(mLogCat, "Call compile error in module: %s", name_tag.c_str()); break;
            case WREN_RESULT_RUNTIME_ERROR: Logger::Trace(mLogCat, "Call runtime error in module: %s", name_tag.c_str()); break;
            case WREN_RESULT_SUCCESS: Logger::Trace(mLogCat, "Call %s run successfully", name_tag.c_str()); break;
        }
    }

    void WrenState::ReleaseWrenHandle(WrenHandle* handle)
    {
        wrenReleaseHandle(mpVM, handle);
    }

    /////////////////////////////////////////////////////////////////////
    //				CODE PROCESSING
    /////////////////////////////////////////////////////////////////////
    
    void WrenState::RunScript(WrenScript* script)
    {
        if (!mpVM)
        {
            Logger::Error(mLogCat, "Could not run script - the VM is nullptr");
            return;
        }

        WrenInterpretResult result = wrenInterpret(mpVM, script->GetModuleName().c_str(), script->GetScriptCode().c_str());
        switch (result)
        {
            case WREN_RESULT_COMPILE_ERROR: Logger::Trace(mLogCat, "Script compile error in module: %s", script->GetModuleName().c_str()); break;
            case WREN_RESULT_RUNTIME_ERROR: Logger::Trace(mLogCat, "Script runtime error in module: %s", script->GetModuleName().c_str()); break;
            case WREN_RESULT_SUCCESS: Logger::Trace(mLogCat, "Script %s run successfully", script->GetModuleName().c_str()); break;
        }
        
    }

    
    void WrenState::RunSnippet(std::string name, std::string code)
    {
        if (!mpVM)
        {
            Logger::Error(mLogCat, "Could not run script - the VM is nullptr");
            return;
        }

        WrenInterpretResult result = wrenInterpret(mpVM, name.c_str(), code.c_str());
        switch (result)
        {
            case WREN_RESULT_COMPILE_ERROR: Logger::Trace(mLogCat, "Snippet compile error in module: %s", name.c_str()); break;
            case WREN_RESULT_RUNTIME_ERROR: Logger::Trace(mLogCat, "Snippet runtime error in module: %s", name.c_str()); break;
            case WREN_RESULT_SUCCESS: Logger::Trace(mLogCat, "Snippet %s run successfully", name.c_str()); break;
        }
    }

    /////////////////////////////////////////////////////////////////////
    //				VM CALLBACK METHODS
    /////////////////////////////////////////////////////////////////////
    
    void WrenState::WriteFN(WrenVM* vm, const char* text)
    {
        std::string final = String::TrimEnd(text, "\n");

        if (final.size() < 1)
            return;

        Logger::Info(mLogCat, final.c_str());
    }

    void WrenState::ErrorFN(WrenVM* vm, WrenErrorType type, const char* module, int line, const char* message)
    {
        switch (type)
        {
        case WREN_ERROR_COMPILE:
            Logger::Error(mLogCat, "Script compile error: [%s line %d] %s\n", module, line, message);
            break;

        case WREN_ERROR_STACK_TRACE:
            Logger::Error(mLogCat, "[%s line %d] in %s\n", module, line, message);
            break;

        case WREN_ERROR_RUNTIME:
            Logger::Error(mLogCat, "Script Runtime Error: %s\n", message);
            break;
        }
    }

    
    WrenForeignMethodFn WrenState::BindForeignMethodFN(WrenVM* vm, const char* module, const char* class_name, bool isStatic, const char* signature)
    {
        ForeignMethodDesc FMID = { module, class_name, isStatic, signature };

        for (auto iter = mForeignMethods.begin(); iter != mForeignMethods.end(); iter++)
        {
            if (FMID == (*iter))
            {
                Logger::Trace(mLogCat, "Successfully bound foreign method: %s::%s", class_name, signature);
                return iter->FM;
            }
        }
        
        Logger::Error(mLogCat, "Failed to bind foreign method: %s::%s", class_name, signature);
        return nullptr;
    }

    WrenForeignClassMethods WrenState::BindForeignClassFN(WrenVM* vm, const char* module, const char* class_name)
    {
        ForeignClassDesc FCD = { module, class_name };

        for (auto iter = mForeignClasses.begin(); iter != mForeignClasses.end(); iter++)
        {
            if (FCD == (*iter))
            {
                Logger::Trace(mLogCat, "Successfully bound foreign class: %s in module %s", class_name, module);
                return iter->FCM;
            }
        }

        Logger::Error(mLogCat, "Could not find foreign class: %s in module %s", class_name, module);
        return WrenForeignClassMethods { nullptr, nullptr };
    }
}