#ifndef SANDBOX_HPP
#define SANDBOX_HPP

#include <open_engine.hpp>

#include <glm/ext/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <imgui.h>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <string>
#include <unordered_map>
#include <vector>

class SandboxLayer : public OpenEngine::Layer
{
    public:
        OpenEngine::Ref<OpenEngine::VertexArray> CreateSquare()
        {
            OpenEngine::Ref<OpenEngine::VertexArray> square_vertex_array(OpenEngine::VertexArray::Create());

            float square_vertices[5 * 4] = {
                -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
                0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
                0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
                -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
            };

            OpenEngine::Ref<OpenEngine::VertexBuffer> vertex_buffer;
            vertex_buffer.reset(OpenEngine::VertexBuffer::Create(square_vertices, sizeof(square_vertices)));

            OpenEngine::BufferLayout layout = {
                { OpenEngine::ShaderDataType::Float3, "a_Position" },
                { OpenEngine::ShaderDataType::Float2, "a_TextCoord" }
            };

            vertex_buffer->SetLayout(layout);
            square_vertex_array->AddVertexBuffer(vertex_buffer);

            uint32_t indices[6] = { 0, 1, 2, 2, 3, 0 };
            OpenEngine::Ref<OpenEngine::IndexBuffer> index_buffer;
            index_buffer.reset(OpenEngine::IndexBuffer::Create(indices, sizeof(indices) / sizeof(uint32_t)));

            square_vertex_array->SetIndexBuffer(index_buffer);

            return square_vertex_array;
        }

        SandboxLayer()
            : Layer("Sandbox")
        {
            //vertex_array.reset(OpenEngine::VertexArray::Create());
            square = CreateSquare();

            //float vertices[3 * 7] = {
            //    -0.5f, -0.5f, 0.0f, 0.8f, 0.2f, 0.8f, 1.0f,
            //    0.5f, -0.5f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,
            //    0.0f,  0.5f, 0.0f, 0.8f, 0.8f, 0.2f, 1.0f
            //};

            //OpenEngine::Ref<OpenEngine::VertexBuffer> vertex_buffer;
            //vertex_buffer.reset(OpenEngine::VertexBuffer::Create(vertices, sizeof(vertices)));

            //OpenEngine::BufferLayout layout = {
            //    {OpenEngine::ShaderDataType::Float3, "a_Position"},
            //    {OpenEngine::ShaderDataType::Float4, "a_Color"}
            //};

            //vertex_buffer->SetLayout(layout);
            //vertex_array->AddVertexBuffer(vertex_buffer);

            //uint32_t indices[3] = { 0, 1, 2 };
            //OpenEngine::Ref<OpenEngine::IndexBuffer> index_buffer;
            //index_buffer.reset(OpenEngine::IndexBuffer::Create(indices, sizeof(indices) / sizeof(uint32_t)));

            //shader = OpenEngine::Shader::Create("./assets/shaders/texture.glsl");
            //vertex_array->SetIndexBuffer(index_buffer);

            //texture_shader = OpenEngine::Shader::Create("./assets/shaders/texture.glsl");
            auto texture_shader = shader_library.Load("./assets/shaders/texture.glsl");
            texture = OpenEngine::Texture2D::Create("./assets/textures/container.jpg");
            face = OpenEngine::Texture2D::Create("./assets/textures/awesomeface.png");

            std::dynamic_pointer_cast<OpenEngine::OpenGLShader>(texture_shader)->Bind();
            std::dynamic_pointer_cast<OpenEngine::OpenGLShader>(texture_shader)->SetInt("u_Texture", 0);

            for (auto joystick : OpenEngine::Input::GetJoystickList())
                OE_TRACE("Joystick {}: {}", joystick.first, joystick.second);

            bindings = {
                {"fwd/bckwd", 1},
                {"right/left", 0},
                {"yaw", 2}
            };
        }

        void moveCamera()
        {
            if (current_joystick != -1) {
                float raw_axis = OpenEngine::Input::GetJoystickAxis(current_joystick, bindings["yaw"]);
                float rotation = remap(raw_axis, -1, 1, -180, 180);
                camera.GetCamera().SetRotation(rotation);
            }
        }

        void moveSquare(glm::vec3& position)
        {
            float velocity = 1 * OpenEngine::Time::Get().DeltaTime();
            if (OpenEngine::Input::IsKeyPressed(OE_KEY_I))
                position.y += velocity;
            if (OpenEngine::Input::IsKeyPressed(OE_KEY_Y))
                position.x -= velocity;
            if (OpenEngine::Input::IsKeyPressed(OE_KEY_U))
                position.y -= velocity;
            if (OpenEngine::Input::IsKeyPressed(OE_KEY_O))
                position.x += velocity;
        }

        void moveSquareF(glm::vec3& position)
        {
            if (current_joystick != -1) {
                float test = OpenEngine::Input::GetJoystickAxis(current_joystick, bindings["fwd/bckwd"]);
                position.y = test;
                float test2 = OpenEngine::Input::GetJoystickAxis(current_joystick, bindings["right/left"]);
                position.x = test2;
            }
        }

        float remap(float value, float minInput, float maxInput, float minOutput, float maxOutput) {
            // 1. Normalize the input to a 0.0 - 1.0 range
            float t = (value - minInput) / (maxInput - minInput);

            // 2. Use glm::mix to interpolate between the output range
            return glm::mix(minOutput, maxOutput, t);
        }
        
        void OnUpdate() override
        {
            static glm::mat4 scale = glm::scale(glm::mat4(1.0f), glm::vec3(1.0f));

            OpenEngine::RenderCommand::SetClearColor({1.0f, 0.0f, 1.0f, 1.0f});
            OpenEngine::RenderCommand::Clear();

            OpenEngine::Renderer::BeginScene(camera.GetCamera());

            //shader->Bind();
            //vertex_array->Bind();
            
            moveCamera();

            //glm::mat4 transform = glm::translate(glm::mat4(1.0f), triangle_position);
            //OpenEngine::Renderer::Submit(shader, vertex_array, transform);

            auto texture_shader = shader_library.Get("texture");

            texture_shader->Bind();
            texture->Bind();
            glm::mat4 square_transform = glm::translate(glm::mat4(1.0f), square_pos) * scale;
            OpenEngine::Renderer::Submit(texture_shader, square, square_transform);
            moveSquare(square_pos);
            moveSquareF(square_pos);

            face->Bind();
            OpenEngine::Renderer::Submit(texture_shader, square, square_transform);

            OpenEngine::Renderer::EndScene();

            //if (OpenEngine::Input::IsKeyPressed(OE_KEY_ESCAPE))
            //    OpenEngine::Application::Get().StopRunning();
            camera.OnUpdate();
        }

        void OnEvent(OpenEngine::Event& event) override
        {
            OpenEngine::EventDispatcher dispatcher(event);
            dispatcher.Dispatch<OpenEngine::KeyPressedEvent>(BIND_EVENT_FN(SandboxLayer::QuitRunning));

            camera.OnEvent(event);
        }

        bool QuitRunning(OpenEngine::KeyPressedEvent& event)
        {
            if (event.GetKeyCode() == OE_KEY_ESCAPE) {
                OpenEngine::Application::Get().StopRunning();
                return true;
            }

            return false;
        }

        void OnImGuiRender() override
        {
            static std::vector<std::string> axis_labels;
            static std::vector<const char*> axis_pointers;

            if (axis_labels.empty()) {
                axis_labels.reserve(MAX_AXIS);
                axis_pointers.reserve(MAX_AXIS);

                for (int i = 0; i < MAX_AXIS; ++i) {
                    axis_labels.push_back("Axis " + std::to_string(i + 1));
                    axis_pointers.push_back(axis_labels.back().c_str());
                }
            }

            auto joysticks = OpenEngine::Input::GetJoystickList();
            ImGui::Begin("Settings");
            ImGui::ColorEdit3("Square Color", glm::value_ptr(square_color));
            ImGui::TextColored(ImVec4(1,1,0,1), "Important Stuff");
            ImGui::BeginChild("Child");
            ImGui::Text("-1");
            ImGui::RadioButton("None", &current_joystick, -1);
            for (const auto& joystick : joysticks) {
                ImGui::PushID(joystick.first);
                ImGui::Text("%d", joystick.first);
                ImGui::RadioButton(joystick.second.c_str(), &current_joystick, joystick.first);
                ImGui::PopID();
            }
            ImGui::Text("Bindings");
            for (auto& binding : bindings)
                ImGui::Combo(binding.first.c_str(), (int*)&binding.second, axis_pointers.data(), (int)axis_pointers.size());
            ImGui::EndChild();
            ImGui::End();
        }

        OpenEngine::ShaderLibrary shader_library;
        //OpenEngine::Ref<OpenEngine::Shader> shader;
        //OpenEngine::OrthographicCamera camera = {-1.6f, 1.6f, -0.9f, 0.9f};
        OpenEngine::OrthographicCameraController camera = {1280.0f / 1440.0f, 1.0f};
        //OpenEngine::Ref<OpenEngine::VertexArray> vertex_array;
        OpenEngine::Ref<OpenEngine::VertexArray> square;

        glm::vec3 triangle_position{0.0f};
        glm::vec3 square_pos{0.0f};
        glm::vec3 square_color{0.0f};

        OpenEngine::Ref<OpenEngine::Texture2D> texture, face;
        //OpenEngine::Ref<OpenEngine::Shader> texture_shader;
        int current_joystick = -1;
        std::unordered_map<std::string, unsigned int> bindings;
};

class Sandbox : public OpenEngine::Application
{
    public:
        Sandbox();
        ~Sandbox();
};

#endif // SANDBOX_HPP
