#include "GUI.h"
#include "gl3w.h"
#include <iostream>
#include "Runtime.h"

#define WIDTH   500
#define HEIGHT  500

namespace jes
{
    bool GUI::load()
    {
        if (SDL_Init(SDL_INIT_VIDEO))
        {
            std::cerr << "Failed to initialize SDL!" << std::endl;
            return false;
        }

        atexit(SDL_Quit);

        m_window = SDL_CreateWindow("jes",
                                    SDL_WINDOWPOS_UNDEFINED,
                                    SDL_WINDOWPOS_UNDEFINED,
                                    WIDTH,
                                    HEIGHT,
                                    SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
        if (!m_window)
        {
            std::cerr << "Failed to create window!" << std::endl;
            SDL_Quit();
            return false;
        }

        (void)SDL_GL_CreateContext(m_window);

        if (gl3wInit() != 0)
        {
            std::cerr << "Failed to gl3wInit()" << std::endl;
            SDL_Quit();
            return false;
        }

        if (!gl3wIsSupported(3, 0))
        {
            std::cerr << "OpenGL 3.0 is not supported!" << std::endl;
            SDL_Quit();
            return false;
        }

        glClearColor (0.0, 0.0, 0.0, 0.0);

        m_font_manager = new FontManager();

        if (!m_font_manager->load())
            return false;

        if (!load_shaders())
            return false;

        if (!load_buffers())
            return false;

        return true;
    }

    bool GUI::load_shaders()
    {
        static const char * shader_sources[PROGRAM_COUNT][2] = {
            {"text.v.glsl", "text.f.glsl"},
            {"basic.v.glsl", "basic.f.glsl"},
            {"rect.v.glsl", "basic.f.glsl"},
        };

        for (int i = 0; i < PROGRAM_COUNT; i++)
        {
            PathRef shader_paths[2];
            GLShaderRef shaders[2];

            m_programs[i] = new GLProgram();

            for (int j = 0; j < 2; j++)
            {
                shader_paths[j] = Runtime::locate(Runtime::SHADER, shader_sources[i][j]);
                if (shader_paths[j] == NULL)
                {
                    std::cerr << "Could not find shader source " << shader_sources[i][j] << std::endl;
                    return false;
                }
                shaders[j] = new GLShader();
                if (!shaders[j]->create(j == 0 ? GL_VERTEX_SHADER : GL_FRAGMENT_SHADER, shader_paths[j]))
                {
                    return false;
                }

                m_programs[i]->attach_shader(shaders[j]);
            }
            if (!m_programs[i]->link())
                return false;
        }

        return true;
    }

    bool GUI::load_buffers()
    {
        static const GLint rect_coords[4][2] = {
            {0, 0},
            {1, 0},
            {0, 1},
            {1, 1},
        };

        m_rect_vbo = new GLBuffer();
        if (!m_rect_vbo->create(GL_ARRAY_BUFFER, GL_STATIC_DRAW, &rect_coords, sizeof(rect_coords)))
        {
            std::cerr << "Failed to create rectangle VBO" << std::endl;
            return false;
        }

        return true;
    }

    int GUI::run()
    {
        resize();
        draw();

        SDL_Event event;
        while (SDL_WaitEvent(&event))
        {
            if (event.type == SDL_QUIT)
                break;
            else if (event.type == SDL_KEYDOWN)
            {
                if (event.key.keysym.sym == SDLK_ESCAPE)
                    break;
            }
            else if (event.type == SDL_WINDOWEVENT)
            {
                switch (event.window.event)
                {
                case SDL_WINDOWEVENT_EXPOSED:
                    draw();
                    break;
                case SDL_WINDOWEVENT_RESIZED:
                    resize();
                    draw();
                    break;
                }
            }
        }

        return 0;
    }

    void GUI::resize()
    {
        GLint viewport_size[2];
        SDL_GetWindowSize(m_window, &viewport_size[0], &viewport_size[1]);
        glViewport(0, 0, viewport_size[0], viewport_size[1]);
        for (int i = 0; i < PROGRAM_COUNT; i++)
        {
            m_programs[i]->use();
            glUniform2iv(m_programs[i]->get_uniform("viewport_size"), 1, &viewport_size[0]);
        }
    }

    void GUI::draw()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        SDL_GL_SwapWindow(m_window);
    }

    void GUI::draw_rect(int x, int y, int width, int height, float r, float g, float b, float a)
    {
        m_programs[PROGRAM_RECT]->use();
        glUniform2i(m_programs[PROGRAM_RECT]->get_uniform("position"), x, y);
        glUniform2i(m_programs[PROGRAM_RECT]->get_uniform("size"), width, height);
        glUniform4f(m_programs[PROGRAM_RECT]->get_uniform("color"), r, g, b, a);
        m_rect_vbo->bind();
        glEnableVertexAttribArray(0);
        glVertexAttribIPointer(0, 2, GL_INT, 0, 0);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    }
}
