#include "DeerStudio/EditorEngine/DockPanel/DockPanelObject.h"
#include "DeerStudio/EditorEngine/ErrorHandle.h"
#include "DeerStudio/EditorEngine.h"

#include "Deer/Log.h"

#include "angelscript.h"
#include <string>

#include "imgui.h"

namespace Deer {
    EditorEngine::DockPanelObject::DockPanelObject(asITypeInfo* _type, asIScriptContext* _scriptContext)
        : type (_type), isValid(false), scriptContext(_scriptContext){
        
        // Constructor
        // "type@ type()" 
        std::string callString;
        const std::string ns(type->GetNamespace());
        if (ns != "") {
            callString += ns;
            callString += "::";
        }
        callString += type->GetName();
        callString += "@ ";
        if (ns != "") {
            callString += ns;
            callString += "::";
        }
        callString += type->GetName();
        callString += "()";

        flags = DockPannelFlag_ShowPannel;

        asIScriptFunction* factory = type->GetFactoryByDecl(callString.c_str());
        
        AS_CHECK(scriptContext->Prepare(factory));
        AS_CHECK(scriptContext->Execute());

        // Return value contains the ref to a asIScriptObject in the location provided
        object = *(asIScriptObject**)scriptContext->GetAddressOfReturnValue();
        if (!object){
            DEER_EDITOR_ENGINE_ERROR("Could not create object", type->GetName());
            return;
        }
        object->AddRef();
        
        renderFunction = type->GetMethodByDecl("void onRender()");
        if (!renderFunction) {
            DEER_EDITOR_ENGINE_ERROR("Could not load void onRender() from {0}", type->GetName());
            return;
        }

        menuBarFunction = type->GetMethodByDecl("void onMenuBar()");
        initFunction = type->GetMethodByDecl("void onInit()");

        isValid = true;

        scriptContext->Unprepare();

    }

    int EditorEngine::DockPanelObject::getRefCount() {
        int refCount = object->AddRef();   // increments refcount by 1
        refCount = object->Release();  

        return refCount;
    }

    const char* EditorEngine::DockPanelObject::getName() {
        return type->GetName();
    }

    EditorEngine::DockPanelObject::~DockPanelObject() {
        if (object) 
            object->Release();
    }

    void EditorEngine::DockPanelObject::invalidate() {
        DEER_EDITOR_ENGINE_ERROR("Last error was caused executing {0}", type->GetName());
        isValid = false;
    }

    void EditorEngine::DockPanelObject::init() {
        
        if (!initFunction)
            return;
        
        executingScriptContext = scriptContext;
        AS_CHECK(scriptContext->Prepare(initFunction));
        AS_CHECK(scriptContext->SetObject(object));
        AS_CHECK(scriptContext->Execute());
        AS_CHECK(scriptContext->Unprepare());
        executingScriptContext = nullptr;
    }

    void EditorEngine::DockPanelObject::render() {
        bool show = flags & DockPannelFlag_ShowPannel;
        if (!show)
            return;
        
        // We cache the result because the user can remove the flag while executing
        bool hasPadding = flags & DockPannelFlag_PannelPadding;
        if (hasPadding)
            ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));

        if (menuBarFunction) {
            ImGui::Begin(type->GetName(), (bool*)0, ImGuiWindowFlags_MenuBar);

		    if (ImGui::BeginMenuBar()) {    
                executingScriptContext = scriptContext;
                AS_CHECK(scriptContext->Prepare(menuBarFunction));
                AS_CHECK(scriptContext->SetObject(object));
                AS_CHECK(scriptContext->Execute());
                AS_CHECK(scriptContext->Unprepare());
                executingScriptContext = nullptr;

                ImGui::EndMenuBar();
            } 
        } else {
            ImGui::Begin(type->GetName());
        }

        // This is to make sure that right click activates the window
        if (ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) &&
            (ImGui::IsMouseClicked(ImGuiMouseButton_Right) || ImGui::IsMouseClicked(ImGuiMouseButton_Middle))) {
            ImGui::SetWindowFocus();
        }

        if (!isValid) {
            ImGui::TextColored(ImVec4(1, 0.3f, 0.3f, 1), "There was a runtime error");
            ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "Please check the log");
            ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1), "Plese restart the interface");
            ImGui::End();
            return;
        }
        
        executingScriptContext = scriptContext;
        AS_CHECK(scriptContext->Prepare(renderFunction));
        AS_CHECK(scriptContext->SetObject(object));
        AS_CHECK(scriptContext->Execute());
        AS_CHECK(scriptContext->Unprepare());
        executingScriptContext = nullptr;

        ImGui::End();

        if (hasPadding)
            ImGui::PopStyleVar();
    }

    EditorEngine::DockPanelObject::DockPanelObject(DockPanelObject&& other) noexcept 
        : isValid(other.isValid),
        renderFunction(other.renderFunction),
        type(other.type),
        object(other.object), 
        menuBarFunction(other.menuBarFunction), 
        initFunction(other.initFunction),
        flags(other.flags),
        scriptContext(other.scriptContext) {
        
        other.isValid = false;
        other.renderFunction = nullptr;
        other.type = nullptr;
        other.object = nullptr;
        other.menuBarFunction = nullptr;
        other.initFunction = nullptr;   
        other.scriptContext = nullptr;
    }

    EditorEngine::DockPanelObject& EditorEngine::DockPanelObject::operator=(EditorEngine::DockPanelObject&& other) noexcept {
        if (this != &other) {
            if (object)
                object->Release();

            isValid = other.isValid;
            renderFunction = other.renderFunction;
            type = other.type;
            object = other.object;
            menuBarFunction = other.menuBarFunction;
            initFunction = other.initFunction;
            flags = other.flags;
            scriptContext = other.scriptContext;

            other.isValid = false;
            other.renderFunction = nullptr;
            other.type = nullptr;
            other.object = nullptr;
            other.menuBarFunction = nullptr;
            other.initFunction = nullptr;
            other.scriptContext = nullptr;
        }

        return *this;
    }

    void EditorEngine::DockPanelObject::setFlag(uint32_t flag, bool value) {
        if ((flag & flags) != value) 
            flags ^= flag;
    }

    bool EditorEngine::DockPanelObject::getFlag(uint32_t flag) {
        return flag & flags;
    }
}