#include <pch.hpp>

#include <application.hpp>

#include <core.hpp>
#include <events/application_event.hpp>
#include <renderer/renderer2d.hpp>
#include <input/input_system.hpp>
#include <imgui/imgui_layer.hpp>
#include <renderer/renderer.hpp>
#include <core/time.hpp>

#include <imgui.h>

namespace OpenEngine {
    Application::Application(const std::string& name,
            ApplicationCommandLineArgs& args)
        : name(name), arguments(args)
    {
        OE_PROFILE_FUNCTION();

        OE_CORE_ASSERT(!instance, "Application already exists!");
        instance = this;

        window = Window::Create(WindowProps(name));
        window->SetEventCallback(BIND_EVENT_FN(Application::OnEvent));

        {
            OE_PROFILE_SCOPE("Initializing Renderers");
            Renderer::Init();
            Renderer2D::Init();
        }

        imgui_layer = std::make_shared<ImGuiLayer>();
        QueueOverlayPush(imgui_layer);

        Time::Init();
    }

    Application::~Application()
    {
        OpenEngine::Renderer2D::Shutdown();
    }

    void Application::Run()
    {
        OE_PROFILE_FUNCTION()

        while(running) {
            OE_PROFILE_SCOPE("Frame");

            Time::Update();
            layer_stack.UpdateLayers();

            {
                OE_PROFILE_SCOPE("Updating all Layers");
                for (auto layer : layer_stack)
                    layer->OnUpdate();
            }

            {
                OE_PROFILE_SCOPE("Rendering ImGui");
                imgui_layer->Begin();

                for (auto layer : layer_stack)
                    layer->OnImGuiRender();

                imgui_layer->End();
            }

            window->OnUpdate();
        }
    }

    void Application::OnEvent(Event& event)
    {
        OE_PROFILE_FUNCTION();

        EventDispatcher dispatcher(event);
        dispatcher.Dispatch<WindowCloseEvent>(BIND_EVENT_FN(Application::OnWindowClose));
        dispatcher.Dispatch<WindowResizeEvent>(BIND_EVENT_FN(Application::OnWindowResize));

        {
            OE_PROFILE_SCOPE("Layers OnEvent");
            for (auto it = layer_stack.end(); it != layer_stack.begin();) {
                if (event.handled)
                    break;
                (*--it)->OnEvent(event);
            }
        }
    }

    bool Application::OnWindowClose(WindowCloseEvent& event)
    {
        Close();

        return true;
    }

    void Application::Close()
    {
        running = false;
    }

    bool Application::OnWindowResize(WindowResizeEvent& event)
    {
        OE_PROFILE_FUNCTION();
        Renderer::OnWindowResize(event.GetWidth(), event.GetHeight());

        return false;
    }

    void Application::QueueLayerPush(Ref<Layer> layer)
    {
        layer_stack.QueueLayerPush(layer);
    }

    void Application::QueueLayerPop(Ref<Layer> layer)
    {
        layer_stack.QueueLayerPop(layer);
    }

    void Application::QueueOverlayPush(Ref<Layer> overlay)
    {
        layer_stack.QueueOverlayPush(overlay);
    }

    void Application::QueueOverlayPop(Ref<Layer> overlay)
    {
        layer_stack.QueueOverlayPop(overlay);
    }
}
