//
// Sample showing how to use GLSL programs
//
// Author: Alex V. Boreskoff <alexboreskoff@mtu-net.ru>, <steps3d@narod.ru>
//


#pragma comment (lib, "opengl32.lib")  
#pragma comment (lib, "glu32.lib")     
#pragma comment (lib, "glut32.lib")
#pragma comment (lib, "glew32.lib")
#pragma comment (lib, "glew32s.lib")

#ifdef  _WIN32
    #include    <windows.h>
#else
    #include	<stdint.h>
    #define GLX_GLXEXT_LEGACY
#endif

#ifdef	MACOSX
	#include	<dlfcn.h>
	#include	<OpenGL/gl.h>
	#include	<OpenGL/glu.h>
	#include	<OpenGL/OpenGL.h>
	#include	<OpenGL/glext.h>
	#include	<GLUT/glut.h>
#else
	#include    "GL/glew.h"
	#include    <GL/gl.h>
	#include    <GL/glu.h>
	#include	<GL/glut.h>
#endif

#ifdef  _WIN32
    #include    <GL/wglew.h>
#else
    #include    <GL/glxew.h>
#endif

#include    <stdio.h>
#include    <stdlib.h>
#include	<string.h>
#include    <ctype.h>

float	eye   []  = { 7, 5, 7 };          	// camera position
float	light []  = { 5, 0, 4 };          	// light position
float	rot   []  = {  0, 0, 0 };
float   angle     = 0;
int     mouseOldX = 0;
int     mouseOldY = 0;

GLhandleARB program        = 0;         // program handles
GLhandleARB vertexShader   = 0;
GLhandleARB fragmentShader = 0;

//
// Returns 1 if an OpenGL error occurred, 0 otherwise.
//

bool    checkOpenGLError ()
{
    bool    retCode = true;

    for ( ; ; )
    {
        GLenum  glErr = glGetError ();

        if ( glErr == GL_NO_ERROR )
            return retCode;

        printf ( "glError: %s\n", gluErrorString ( glErr ) );

        retCode = false;
        glErr   = glGetError ();
    }

    return retCode;
}


//
// Print out the information log for a shader object or a program object
//

void printInfoLog ( GLhandleARB object )
{
    GLint       logLength     = 0;
    GLsizei     charsWritten  = 0;
    GLcharARB * infoLog;

    checkOpenGLError ();             		// check for OpenGL errors

    glGetObjectParameterivARB ( object, GL_OBJECT_INFO_LOG_LENGTH_ARB, &logLength );

    if ( !checkOpenGLError() )              // check for OpenGL errors
        exit ( 1 );

    if ( logLength > 0 )
    {
        infoLog = (GLcharARB*) malloc ( logLength );

        if ( infoLog == NULL )
        {
            printf("ERROR: Could not allocate InfoLog buffer\n");

            exit ( 1 );
        }

        glGetInfoLogARB ( object, logLength, &charsWritten, infoLog );

        printf ( "InfoLog:\n%s\n\n", infoLog );
        free   ( infoLog );
    }

    if ( !checkOpenGLError () )             // check for OpenGL errors
        exit ( 1 );
}

bool    loadShader ( GLhandleARB shader, const char * fileName )
{
    printf ( "Loading %s\n", fileName );

	FILE * file = fopen ( fileName, "rb" );
	
	if ( file == NULL )
	{
		printf ( "Error opening %s\n", fileName );
        exit   ( 1 );
	}

	fseek ( file, 0, SEEK_END );
	
	GLint	size = ftell ( file );
	
	if ( size < 1 )
	{
		fclose ( file );
		printf ( "Error loading file %s\n", fileName );
		exit   ( 1 );
	}
	
	char * buf = (char *) malloc ( size );
	
	fseek ( file, 0, SEEK_SET );
	
	if ( fread ( buf, 1, size, file ) != size )
	{
		fclose ( file );
		printf ( "Error loading file %s\n", fileName );
		exit   ( 1 );
	}

	fclose ( file );
	
    GLint   compileStatus;

    glShaderSourceARB ( shader, 1, (const char **) &buf, &size );

	free ( buf );
                                        // compile the particle vertex shader, and print out
    glCompileShaderARB ( shader );

    if ( !checkOpenGLError() )          // check for OpenGL errors
        return false;

    glGetObjectParameterivARB ( shader, GL_OBJECT_COMPILE_STATUS_ARB, &compileStatus );
    printInfoLog              ( shader );

    return compileStatus != 0;
}

void init ()
{
    glClearColor ( 0.0, 0.0, 0.0, 1.0 );
    glEnable     ( GL_DEPTH_TEST );
    glDepthFunc  ( GL_LEQUAL );

    glHint ( GL_POLYGON_SMOOTH_HINT,         GL_NICEST );
    glHint ( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
}

void display ()
{
    glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glMatrixMode   ( GL_MODELVIEW );
    glPushMatrix   ();

    glRotatef    ( rot [0], 1, 0, 0 );
    glRotatef    ( rot [1], 0, 1, 0 );
    glRotatef    ( rot [2], 0, 0, 1 );

    glutSolidTeapot ( 2 );

    glPopMatrix     ();
    glutSwapBuffers ();
}

void reshape ( int w, int h )
{
   glViewport     ( 0, 0, (GLsizei)w, (GLsizei)h );
   glMatrixMode   ( GL_PROJECTION );
   glLoadIdentity ();
   gluPerspective ( 60.0, (GLfloat)w/(GLfloat)h, 1.0, 60.0 );
   glMatrixMode   ( GL_MODELVIEW );
   glLoadIdentity ();
   gluLookAt      ( eye [0], eye [1], eye [2],    	// eye
                    0, 0, 0,                		// center
                    0.0, 0.0, 1.0 );        		// up
}

void motion ( int x, int y )
{
    rot [1] -= ((mouseOldY - y) * 180.0f) / 200.0f;
    rot [2] -= ((mouseOldX - x) * 180.0f) / 200.0f;
    rot [0]  = 0;

	for ( int i = 0; i < 3; i++ )
		if ( rot [i] > 360 )
			rot [i] -= 360;
		else
		if ( rot [i] < -360 )
			rot [i] += 360;

	mouseOldX = x;
    mouseOldY = y;

    glutPostRedisplay ();
}

void mouse ( int button, int state, int x, int y )
{
    if ( state == GLUT_DOWN )
    {
        mouseOldX = x;
        mouseOldY = y;
    }
}

void key ( unsigned char key, int x, int y )
{
    if ( key == 27 || key == 'q' || key == 'Q' )    //  quit requested
        exit ( 0 );
}

void    animate ()
{
    angle  = 0.004f * glutGet ( GLUT_ELAPSED_TIME );

    glutPostRedisplay ();
}

int main ( int argc, char * argv [] )
{
                                // initialize glut
    glutInit            ( &argc, argv );
    glutInitDisplayMode ( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
    glutInitWindowSize  ( 500, 500 );


                                // create window
    glutCreateWindow ( "Simple example of using GLSL shaders" );

                                // register handlers
    glutDisplayFunc  ( display );
    glutReshapeFunc  ( reshape );
    glutKeyboardFunc ( key     );
    glutMouseFunc    ( mouse   );
    glutMotionFunc   ( motion  );
    glutIdleFunc     ( animate );

    init  ();
    
	if ( glewInit () != GLEW_OK )
	{
		printf ( "Error in glewInit\n" );
		
		return 1;
	}
	
    if ( !GLEW_ARB_shading_language_100 )
    {
        printf ( "GL_ARB_shading_language_100 NOT supported.\n" );

        return 1;
    }

    if ( !GLEW_ARB_shader_objects )
    {
        printf ( "GL_ARB_shader_objects NOT supported" );

        return 2;
    }
    
    GLint       linked;

                                        // create a vertex shader object and a fragment shader object
    vertexShader   = glCreateShaderObjectARB ( GL_VERTEX_SHADER_ARB   );
    fragmentShader = glCreateShaderObjectARB ( GL_FRAGMENT_SHADER_ARB );

                                        // load source code strings into shaders
    if ( !loadShader ( vertexShader, "simplest.vsh" ) )
        exit ( 1 );

    if ( !loadShader ( fragmentShader, "simplest.fsh" ) )
        exit ( 1 );

                                        // create a program object and attach the
                                        // two compiled shaders
    program = glCreateProgramObjectARB ();

    glAttachObjectARB ( program, vertexShader   );
    glAttachObjectARB ( program, fragmentShader );

                                        // link the program object and print out the info log
    glLinkProgramARB ( program );

    if ( !checkOpenGLError() )          // check for OpenGL errors
        exit ( 1 );

    glGetObjectParameterivARB ( program, GL_OBJECT_LINK_STATUS_ARB, &linked );

    printInfoLog ( program );

    if ( !linked )
        return 0;

                                        // install program object as part of
                                        // current state
    glUseProgramObjectARB ( program );
    glutMainLoop          ();

    return 0;
}
