//  ---------------------------------------------------------------------------
//
//  @file       TwGLCoreSDL.c
//  @brief      An example that uses AntTweakBar with OpenGL Core Profile 
//              and SDL 1.3.
//
//              AntTweakBar: http://anttweakbar.sourceforge.net/doc
//              OpenGL:      http://www.opengl.org
//              SDL:         http://www.libsdl.org
//  
//  @author     Philippe Decaudin
//
//  ---------------------------------------------------------------------------


#include <AntTweakBar.h>

#ifdef _WIN32
//  MiniSDL13.h is provided to avoid the need of having SDL installed to 
//  recompile this example. Do not use it in your own programs, better
//  install and use the actual SDL library SDK.
#   define USE_MINI_SDL
#endif

//#define GL3_PROTOTYPES 1 ////
//#include <GL3/gl3.h>     ////

#ifdef USE_MINI_SDL
#   include "../src/MiniSDL13.h"
#else
#   include <SDL/SDL.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#ifdef _WIN32
#   include <windows.h> // required by gl.h
#endif
#include <GL/gl.h>
#include <GL/glu.h>


// In this example, we draw a simple rotating square using the OpenGL core profile
// (which requires much more code than with the compatibility profile).
// A tweak bar is created to allow the user to change the color of the 
// rectangle and see its rotation.

// Part of OpenGL core interface is not directly accessible from the common
// OpenGL header and library (at least on windows) so we have to retrieve the
// core functions using glGetProcAddress. These functions are prefixed by
// underscore to avoid possible confict if a modified gl.h has been installed.

#ifdef _WIN32
#   define glGetProcAddress wglGetProcAddress
#else
#   define GLX_GLXEXT_LEGACY
#   include <GL/glx.h>
#   define glGetProcAddress glXGetProcAddressARB
#endif
#ifndef APIENTRY
#   define APIENTRY
#endif
typedef GLuint (APIENTRY *PFNGLCreateShader)(GLenum type);
typedef void (APIENTRY *PFNGLDeleteShader)(GLuint shader);
typedef void (APIENTRY *PFNGLShaderSource)(GLuint shader, GLsizei count, const char* *str, const GLint *length);
typedef void (APIENTRY *PFNGLCompileShader)(GLuint shader);
typedef void (APIENTRY *PFNGLAttachShader)(GLuint program, GLuint shader);
typedef GLuint (APIENTRY *PFNGLCreateProgram)(void);
typedef void (APIENTRY *PFNGLLinkProgram)(GLuint program);
typedef void (APIENTRY *PFNGLUseProgram)(GLuint program);
typedef void (APIENTRY *PFNGLDeleteProgram)(GLuint program);
typedef void (APIENTRY *PFNGLGenBuffers)(GLsizei n, GLuint *buffers);
typedef void (APIENTRY *PFNGLBindBuffer)(GLenum target, GLuint buffer);
typedef void (APIENTRY *PFNGLVertexAttribPointer)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer);
typedef void (APIENTRY *PFNGLEnableVertexAttribArray)(GLuint index);
typedef void (APIENTRY *PFNGLGenVertexArrays)(GLsizei n, GLuint *arrays);
typedef void (APIENTRY *PFNGLBindVertexArray)(GLuint array);
typedef void (APIENTRY *PFNGLDeleteVertexArrays)(GLsizei n, const GLuint *arrays);
typedef GLint (APIENTRY *PFNGLGetAttribLocation)(GLuint program, const char *name);
typedef GLint (APIENTRY *PFNGLGetUniformLocation)(GLuint program, const char *name);
typedef void (APIENTRY *PFNGLUniform1f)(GLint location, GLfloat v0);
typedef void (APIENTRY *PFNGLUniform3f)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
typedef void (APIENTRY *PFNGLBufferData)(GLenum target, ptrdiff_t size, const GLvoid *data, GLenum usage);
typedef void (APIENTRY *PFNGLDeleteBuffers)(GLsizei n, const GLuint *buffers);
PFNGLCreateShader _glCreateShader;
PFNGLDeleteShader _glDeleteShader;
PFNGLShaderSource _glShaderSource;
PFNGLCompileShader _glCompileShader;
PFNGLAttachShader _glAttachShader;
PFNGLCreateProgram _glCreateProgram;
PFNGLLinkProgram _glLinkProgram;
PFNGLUseProgram _glUseProgram;
PFNGLDeleteProgram _glDeleteProgram;
PFNGLGenBuffers _glGenBuffers;
PFNGLBindBuffer _glBindBuffer;
PFNGLVertexAttribPointer _glVertexAttribPointer;
PFNGLEnableVertexAttribArray _glEnableVertexAttribArray;
PFNGLGenVertexArrays _glGenVertexArrays;
PFNGLBindVertexArray _glBindVertexArray;
PFNGLDeleteVertexArrays _glDeleteVertexArrays;
PFNGLGetAttribLocation _glGetAttribLocation;
PFNGLGetUniformLocation _glGetUniformLocation;
PFNGLUniform1f _glUniform1f;
PFNGLUniform3f _glUniform3f;
PFNGLBufferData _glBufferData;
PFNGLDeleteBuffers _glDeleteBuffers;
#ifndef GL_ARRAY_BUFFER
#   define GL_ARRAY_BUFFER      0x8892
#endif
#ifndef GL_STATIC_DRAW
#   define GL_STATIC_DRAW       0x88E4
#endif
#ifndef GL_VERTEX_SHADER
#   define GL_VERTEX_SHADER     0x8B31
#endif
#ifndef GL_FRAGMENT_SHADER
#   define GL_FRAGMENT_SHADER   0x8B30
#endif

int LoadGLCoreFunctions()
{
    _glCreateShader = (PFNGLCreateShader)glGetProcAddress("glCreateShader");
    _glDeleteShader = (PFNGLDeleteShader)glGetProcAddress("glDeleteShader");
    _glShaderSource = (PFNGLShaderSource)glGetProcAddress("glShaderSource"); 
    _glCompileShader = (PFNGLCompileShader)glGetProcAddress("glCompileShader");
    _glAttachShader = (PFNGLAttachShader)glGetProcAddress("glAttachShader"); 
    _glCreateProgram = (PFNGLCreateProgram)glGetProcAddress("glCreateProgram");
    _glLinkProgram = (PFNGLLinkProgram)glGetProcAddress("glLinkProgram");
    _glUseProgram = (PFNGLUseProgram)glGetProcAddress("glUseProgram");
    _glDeleteProgram = (PFNGLDeleteProgram)glGetProcAddress("glDeleteProgram");
    _glGenBuffers = (PFNGLGenBuffers)glGetProcAddress("glGenBuffers");
    _glBindBuffer = (PFNGLBindBuffer)glGetProcAddress("glBindBuffer");
    _glVertexAttribPointer = (PFNGLVertexAttribPointer)glGetProcAddress("glVertexAttribPointer");
    _glEnableVertexAttribArray = (PFNGLEnableVertexAttribArray)glGetProcAddress("glEnableVertexAttribArray");
    _glGenVertexArrays = (PFNGLGenVertexArrays)glGetProcAddress("glGenVertexArrays");
    _glBindVertexArray = (PFNGLBindVertexArray)glGetProcAddress("glBindVertexArray");
    _glDeleteVertexArrays = (PFNGLDeleteVertexArrays)glGetProcAddress("glDeleteVertexArrays");
    _glGetAttribLocation = (PFNGLGetAttribLocation)glGetProcAddress("glGetAttribLocation");
    _glGetUniformLocation = (PFNGLGetUniformLocation)glGetProcAddress("glGetUniformLocation");
    _glUniform1f = (PFNGLUniform1f)glGetProcAddress("glUniform1f");
    _glUniform3f = (PFNGLUniform3f)glGetProcAddress("glUniform3f");
    _glBufferData = (PFNGLBufferData)glGetProcAddress("glBufferData");
    _glDeleteBuffers = (PFNGLDeleteBuffers)glGetProcAddress("glDeleteBuffers");

    if (_glCreateShader == NULL || _glDeleteShader == NULL || _glShaderSource == NULL || _glCompileShader == NULL
        || _glAttachShader == NULL || _glCreateProgram == NULL || _glLinkProgram == NULL || _glUseProgram  == NULL
        || _glDeleteProgram == NULL || _glGenBuffers == NULL || _glBindBuffer == NULL || _glVertexAttribPointer == NULL
        || _glEnableVertexAttribArray == NULL || _glGenVertexArrays == NULL || _glBindVertexArray == NULL
        || _glDeleteVertexArrays == NULL || _glGetAttribLocation == NULL || _glGetUniformLocation == NULL
        || _glUniform1f == NULL || _glUniform3f == NULL || _glBufferData == NULL || _glDeleteBuffers == NULL)
        return 0;
    else 
        return 1;
}


// Shaders globals
GLuint vshader, fshader, program, varray, buffer;
GLint cosa, sina, colorloc;
float angle = 0, quat[4];
float color[] = {0.8f, 1.0f, 0.2f};
float FLOAT_PI = 3.14159265f;

void InitRender()
{
    // Vertex shader
    char *vsource[] = {
        "#version 150 core\n"
        "uniform float cosa, sina;"
        "in vec3 vertex;"
        "void main() { gl_Position = vec4(cosa*vertex.x-sina*vertex.y, sina*vertex.x+cosa*vertex.y, 0, 1); }"
    };
    // Fragment shader
    char *fsource[] = {
        "#version 150 core\n"
        "uniform vec3 color;"
        "out vec4 fcolor;"
        "void main() { fcolor = vec4(color, 1); }"
    };
    // Geometry vertex array
    GLfloat vertices[] = { 
        -0.5f,  0.5f, 0,
        -0.5f, -0.5f, 0, 
         0.5f,  0.5f, 0,
         0.5f, -0.5f, 0
    };
    GLint vlocation;

    // Compile and bind shaders
    vshader = _glCreateShader(GL_VERTEX_SHADER);
    fshader = _glCreateShader(GL_FRAGMENT_SHADER);
    program = _glCreateProgram();
    _glShaderSource(vshader, 1, vsource, NULL);
    _glShaderSource(fshader, 1, fsource, NULL);
    _glCompileShader(vshader);
    _glCompileShader(fshader);
    _glAttachShader(program, vshader);
    _glAttachShader(program, fshader);
    _glLinkProgram(program);
    _glUseProgram(program);

    cosa = _glGetUniformLocation(program, "cosa");
    sina = _glGetUniformLocation(program, "sina");
    colorloc = _glGetUniformLocation(program, "color");

    // Create and bind vertex buffer
    _glGenVertexArrays(1, &varray);
    _glBindVertexArray(varray);
    _glGenBuffers(1, &buffer);
    _glBindBuffer(GL_ARRAY_BUFFER, buffer);
    _glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    vlocation = _glGetAttribLocation(program, "vertex");
    _glVertexAttribPointer(vlocation, 3, GL_FLOAT, GL_TRUE, 0, NULL);
    _glEnableVertexAttribArray(vlocation);

    // GL states
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
}

void UninitRender()
{
    _glDeleteProgram(program);
    _glDeleteShader(fshader);
    _glDeleteShader(vshader);
    _glDeleteBuffers(1, &buffer);
    _glDeleteVertexArrays(1, &varray);
}

void Render()
{
    _glUniform1f(cosa, (float)cos(angle));
    _glUniform1f(sina, (float)sin(angle));
    _glUniform3f(colorloc, color[0], color[1], color[2]);
    _glBindVertexArray(varray);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}


TwBar *CreateTweakBar()
{
    TwBar *bar;

    // Create a tweak bar
    bar = TwNewBar("TweakBar");
    TwDefine(" GLOBAL help='This example shows how to integrate AntTweakBar with SDL and OpenGL Core Profile.\n' "); // Message added to the help bar.

    // Add variables
    TwAddVarRW(bar, "Rotation", TW_TYPE_QUAT4F, &quat, " opened=true help='Rectangle rotation' ");
    TwAddVarRW(bar, "Color", TW_TYPE_COLOR3F, &color, " opened=true help='Rectangle color' ");
 
    return bar;
}


// SDL redefines main
#ifdef main
#   undef main
#endif

int main()
{
    const SDL_VideoInfo* video = NULL;
    int width  = 480, height = 480;
    int bpp, flags;
    int quit = 0;

    // Initialize SDL, then get the current video mode and use it to create a SDL window.
    if (SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        fprintf(stderr, "Video initialization failed: %s\n", SDL_GetError());
        SDL_Quit();
        exit(1);
    }
    video = SDL_GetVideoInfo();
    if (!video) 
    {
        fprintf(stderr, "Video query failed: %s\n", SDL_GetError());
        SDL_Quit();
        exit(1);
    }
    // Request GL context to be OpenGL 3.2 Core Profile
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
    // Other GL attributes
    SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
    //SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    bpp = video->vfmt->BitsPerPixel;
    flags = SDL_OPENGL | SDL_HWSURFACE;
    if (!SDL_SetVideoMode(width, height, bpp, flags))
    {
        fprintf(stderr, "Video mode set failed: %s\n", SDL_GetError());
        SDL_Quit();
        exit(1);
    }
    SDL_WM_SetCaption("AntTweakBar example using OpenGL Core Profile and SDL", "AntTweakBar+GLCore+SDL");

    // Enable SDL unicode and key-repeat
    SDL_EnableUNICODE(1);
    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);

    // Load some OpenGL core functions
    if (!LoadGLCoreFunctions())
    {
        fprintf(stderr, "OpenGL 3.2 not supported.\n");
        SDL_Quit();
        exit(1);
    }

    // Initialize AntTweakBar
    if (!TwInit(TW_OPENGL_CORE, NULL)) {
        fprintf(stderr, "AntTweakBar initialization failed: %s\n", TwGetLastError());
        SDL_Quit();
        exit(1);
    }
    // Tell the window size to AntTweakBar
    TwWindowSize(width, height);
    // Create a tweak bar
    CreateTweakBar();

    // Set OpenGL viewport
    glViewport(0, 0, width, height);

    // Prepare GL shaders and programs for drawing
    InitRender();

    // Main loop:
    // - Draw scene
    // - Process events
    while (!quit)
    {
        SDL_Event event;
        int handled;
        GLenum error;

        // Clear screen
        glClearColor(0.5f, 0.75f, 0.8f, 1);
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

        // Update angle and draw geometry
        angle = (float)SDL_GetTicks()/25.0f * (FLOAT_PI/180.0f);
        quat[0] = quat[1] = 0;
        quat[2] = (float)sin(angle/2.0f);
        quat[3] = (float)cos(angle/2.0f);
        Render();

        // Draw tweak bars
        TwDraw();

        // Present frame buffer
        SDL_GL_SwapBuffers();

        // Process incoming events
        while (SDL_PollEvent(&event)) 
        {
            // Send event to AntTweakBar
            handled = TwEventSDL(&event, SDL_MAJOR_VERSION, SDL_MINOR_VERSION);

            // If event has not been handled by AntTweakBar, process it
            if (!handled)
            {
                switch (event.type)
                {
                case SDL_QUIT:  // Window is closed
                    quit = 1;
                    break;

                case SDL_VIDEORESIZE:   // Window size has changed
                    // Resize SDL video mode
                    width = event.resize.w;
                    height = event.resize.h;
                    if (!SDL_SetVideoMode(width, height, bpp, flags))
                        fprintf(stderr, "WARNING: Video mode set failed: %s\n", SDL_GetError());

                    // Resize OpenGL viewport
                    glViewport(0, 0, width, height);
                    
                    // Restore OpenGL states
                    InitRender();
                    
                    // TwWindowSize has been called by TwEventSDL, 
                    // so it is not necessary to call it again here.

                    break;
                }
            }
        }

        while ((error = glGetError()) != GL_NO_ERROR) 
            fprintf(stderr, "GL error detected: 0x%04X\n", error);

    } // End of main loop

    // Terminate AntTweakBar
    TwTerminate();

    // Delete GL shaders and buffer
    UninitRender();

    // Terminate SDL
    SDL_Quit();

    return 0;
}  

