#include <iostream>

#include "App.hh"

#ifdef _WIN32
    #pragma comment(linker, "/subsystem:windows")
    #define VK_USE_PLATFORM_WIN32_KHR
    #define PLATFORM_SURFACE_EXTENSION_NAME VK_KHR_WIN32_SURFACE_EXTENSION_NAME
#endif


//TODO (Eero): https://learnopengl.com/code_viewer_gh.php?code=src/1.getting_started/2.2.hello_triangle_indexed/hello_triangle_indexed.cpp
namespace App
{    
    const float SQUARE[] = {
        -1.0f,  1.0f,
        -1.0f, -1.0f,
        1.0f,  1.0f,
        1.0f, -1.0f
    };

    static bool
    opengl_shader_compile_source(GLuint shader, const GLchar* source)
    {
        glShaderSource(shader, 1, &source, NULL);
        glCompileShader(shader);

        GLint success;
        glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
        if (success != GL_TRUE) {
            GLint info_log_length;
            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_log_length);

            // GLchar* info_log = malloc(info_log_length);
            // glGetShaderInfoLog(shader, info_log_length, NULL, info_log);

            fprintf(stderr, "failed to compile shader:\n%s\n", "asd");
            // free(info_log);

            return false;
        }

        return true;
    }

    App::App() : m_ReturnCode(0), m_WindowWidth(800), m_WindowHeight(600), m_Quit(false), m_VSync(false)
    {
        float vertices[] = {
            -0.5f, -0.5f, 0.0f,
            0.5f, -0.5f, 0.0f,
            0.0f, 0.5f, 0.0f
        };


        // int  success;
        // char infoLog[512];
        // glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
        // if(!success)
        // {
        //     glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        //     std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
        // }

        
        // glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
        // if(!success) {
        //     glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        //     std::cout << "ERROR::SHADER::FRAGMET::COMPILATION_FAILED\n" << infoLog << std::endl;
        // }


        // Do modern OpenGL stuff
        const GLchar *vs_source =
            "#version 330\n"
            "layout(location = 0) in vec2 point;\n"
            "uniform float angle;\n"
            "void main() {\n"
            "    mat2 rotate = mat2(cos(angle), -sin(angle),\n"
            "                       sin(angle), cos(angle));\n"
            "    gl_Position = vec4(0.75 * rotate * point, 0.0, 1.0);\n"
            "}\n";
        const GLchar *fs_source =
            "#version 330\n"
            "out vec4 color;\n"
            "void main() {\n"
            "    color = vec4(1, 0.15, 0.15, 0);\n"
            "}\n";

        GLuint vs = glCreateShader(GL_VERTEX_SHADER);
        GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
        opengl_shader_compile_source(vs, vs_source);
        opengl_shader_compile_source(fs, fs_source);

        m_prog = glCreateProgram();
        opengl_shader_link_program(m_prog, vs, fs);

        GLint uniform_angle = glGetUniformLocation(m_prog, "angle");

        glDeleteShader(fs);
        glDeleteShader(vs);


        glGenBuffers(1, &m_vbo);
        glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(SQUARE), SQUARE, GL_STATIC_DRAW);
        glBindBuffer(GL_ARRAY_BUFFER, 0);

        glGenVertexArrays(1, &m_vao);
        glBindVertexArray(m_vao);
        glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
        glEnableVertexAttribArray(0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0);

        
        std::cout << "Eternity Engine" << std::endl;
        m_Logger = std::make_shared<Logger>();
        m_Controller = std::make_shared<Controller>();

        if(SDL_Init(SDL_INIT_VIDEO) < 0)
        {
            std::cout << "SDL could not be initialized!" << std::endl
                    << "SDL_Error: " << SDL_GetError() << std::endl;
            m_ReturnCode=-1;
            return;
        }
        
        // Request at least 32-bit color
        SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
        SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
        SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
        SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);

        // Request a double-buffered, OpenGL 3.3 (or higher) core profile
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

        // SDL_Vulkan_LoadLibrary(nullptr);
        m_Window = SDL_CreateWindow("App",
                                    SDL_WINDOWPOS_UNDEFINED,
                                    SDL_WINDOWPOS_UNDEFINED,
                                    m_WindowWidth, m_WindowHeight,
                                    SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
        if(!m_Window)
        {
            std::cout << "Window could not be created!" << std::endl
                    << "SDL_Error: " << SDL_GetError() << std::endl;
            m_ReturnCode=-1;
            return;
        }
        
        m_glContext = SDL_GL_CreateContext(m_Window);
        if( !m_glContext )
        {
            // Display error message
            std::cout << "OpenGL context could not be created!" << std::endl
                    << "SDL_Error: " << SDL_GetError() << std::endl;
            m_ReturnCode=-1;
            return;
        }
        else
        {
            long frame_count = 0;
            unsigned long last_frame = 0;
            unsigned long last_second = 0;
            double angle = 0.0;
            // Event loop exit flag
            m_Quit = false;
            Uint32 command = 0;
            // Event loop
            while(!m_Quit)
            {
                SDL_Event e;

                // Wait indefinitely for the next available event
                if(m_VSync)
                    SDL_PollEvent(&e); // Use this for vsync
                else
                    SDL_WaitEvent(&e);

                // User requests quit
                if(e.type == SDL_QUIT)
                {
                    m_Quit = true;
                }
                
                command = m_Controller->HandleEvent(&e);
                handleCommand(command);

                // // Set background color as cornflower blue
                // glClearColor(0.39f, 0.58f, 0.93f, 1.f);
                // // Clear color buffer
                // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
                // glUseProgram(shaderProgram);
                // glBindVertexArray(m_VAO);
                // // someOpenGLFunctionThatDrawsOurTriangle();
                // // Update window with OpenGL rendering
                // SDL_GL_SwapWindow(m_Window);

                glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
                glClear(GL_COLOR_BUFFER_BIT);

                glUseProgram(m_prog);
                glUniform1f(uniform_angle, angle);
                glBindVertexArray(m_vao);
                glDrawArrays(GL_TRIANGLE_STRIP, 0, sizeof(SQUARE) / sizeof(*SQUARE) / 2);
                glBindVertexArray(0);
                glUseProgram(0);
                unsigned long now = SDL_GetTicks();
                unsigned long diff = now - last_frame;
                last_frame = now;

                angle += diff / 1000.0;
                if (angle > 2 * M_PI) angle -= 2 * M_PI;

                if (now - last_second >= 1000) {
                    printf("FPS: %ld\n", frame_count);
                    frame_count = 0;
                    last_second = now;
                }

                frame_count++;
                SDL_GL_SwapWindow(m_Window);
            }
        }

        
        
    }
    
    App::~App()
    {
        glDeleteVertexArrays(1, &m_vao);
        glDeleteBuffers(1, &m_vbo);
        glDeleteProgram(m_prog);
        SDL_DestroyWindow(m_Window);
        SDL_Quit();
        m_Logger.reset();
        m_Controller.reset();
    }

    int App::getReturnCode() const 
    {
        return m_ReturnCode;
    }

    void App::handleCommand(const Uint32 command)
    {
        switch ((int)command)
        {
        case -1:
            m_Quit = true;
            break;
        
        default:
            break;
        }
    }

    const void App::PrintSystemInfo() const
    {
        const char* hasSSE = (bool)SDL_HasSSE() ? "true" : "false";
        const char* hasSSE2 = (bool)SDL_HasSSE2() ? "true" : "false";
        const char* hasSSE3 = (bool)SDL_HasSSE3() ? "true" : "false";
        const char* hasSSE41 = (bool)SDL_HasSSE41() ? "true" : "false";
        const char* hasSSE42 = (bool)SDL_HasSSE42() ? "true" : "false";
        std::cout << "Platform:        " << SDL_GetPlatform() << std::endl;
        std::cout << "CPU Count:       " << SDL_GetCPUCount() << std::endl;
        std::cout << "System RAM:      " << SDL_GetSystemRAM() << " MB" << std::endl;
        std::cout << "Supports SSE:    " << hasSSE << std::endl;
        std::cout << "Supports SSE2:   " << hasSSE2 << std::endl;
        std::cout << "Supports SSE3:   " << hasSSE3 << std::endl;
        std::cout << "Supports SSE4.1: " << hasSSE41 << std::endl;
        std::cout << "Supports SSE4.2: " << hasSSE42 << std::endl;
    }
} // namespace App
