/*********************************************************************NVMH3****

Copyright NVIDIA Corporation 2004
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED
*AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL NVIDIA OR ITS SUPPLIERS
BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES
WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS,
BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY OTHER PECUNIARY LOSS)
ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF NVIDIA HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

******************************************************************************/

/*******************************************************************************
*                                                                              *
*   Basic example of the use of the CgFX runtime in a simple OpenGL program    *
*                                                                              *
*******************************************************************************/

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

#ifdef WIN32
#include <windows.h>
#endif

// OpenGL / Cg

#ifdef __APPLE__
#include <OpenGL/gl.h>
#include <GLUT/glut.h>
#else
#include <GL/gl.h>
#include <GL/glut.h>
#endif

#include <Cg/cg.h>
#include <Cg/cgGL.h>

static void Keyboard(unsigned char key, int x, int y);
static void Display();
static void Motion(int x, int y);
static void Mouse(int button, int state, int x, int y);

CGcontext context;
CGeffect effect;
CGtechnique technique;
float Color[3] = { 1, 0, 0 };

int main(int argc, char *argv[])
{
    // Open window
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_ALPHA | GLUT_DEPTH);
    glutInitWindowSize(512, 512);
    glutCreateWindow("CgFX Simple");

    // Set glut callbacks
    glutKeyboardFunc(Keyboard);
    glutMotionFunc(Motion);
    glutMouseFunc(Mouse);
    glutDisplayFunc(Display);

    // Launch rendering loop
    glutMainLoop();

    return 0;
}

static void
Keyboard(unsigned char key, int x, int y)
{
    // Quit application
    if (key == 'q' || key == 'Q' || key == 27 /* escape */)
        exit(0);

    // Change the value of the tweakable parameter
    else if (key == '+') {
        Color[1] += .0125;
        if (Color[1] > 1) Color[1] = 1;
    }
    else if (key == '-') {
        Color[1] -= .0125;
        if (Color[1] < 0) Color[1] = 0;
    }

    // Change the technique used
    else if (key == ' ') {
        // This loop will terminate since we previously made sure that
        // there was at least one valid technique in the effect.
        do {
            technique = cgGetNextTechnique(technique);
            if (!technique)
                technique = cgGetFirstTechnique(effect);
        } while (cgValidateTechnique(technique) == CG_FALSE);
    }
}

static bool MouseLeftButtonDown;
static bool MouseRightButtonDown;
static int MouseLastX;
static int MouseLastY;
static float ViewAngleX = 0, ViewAngleY = 0, ViewDistance = 5;

static void Motion(int x, int y)
{
    // Rotate the view
    if (MouseLeftButtonDown) {
        float speed = 1.f;
        ViewAngleY += speed * (x - MouseLastX);
        ViewAngleX += speed * (y - MouseLastY);
    }

    // Zoom in and out
    else if (MouseRightButtonDown) {
        float speed = 0.05f;
        ViewDistance += speed * (y - MouseLastY);
    }

    MouseLastX = x;
    MouseLastY = y;
}

static void Mouse(int button, int state, int x, int y)
{
    bool buttonDown = (MouseLeftButtonDown || MouseRightButtonDown) ? 
        false : state == GLUT_DOWN;

    switch (button) {
    case GLUT_LEFT_BUTTON:
        MouseLeftButtonDown = buttonDown;
        break;
    case GLUT_RIGHT_BUTTON:
        MouseRightButtonDown = buttonDown;
        break;
    default:
        break;
    }
    if (buttonDown) {
        MouseLastX = x;
        MouseLastY = y;
    }
}

static void
errorCallback()
{
    fprintf(stderr, "Cg error: %s\n", cgGetErrorString(cgGetError()));
    fflush(stderr);
}

//
// Created sized array of lights and connect to the named effect parameter.
//
static void initLights(CGeffect effect, const char *pname)
{
    CGtype ltype = cgGetNamedUserType(effect, "Light");
    CGtype ptype = cgGetNamedUserType(effect, "PointLight");
    CGtype stype = cgGetNamedUserType(effect, "SpotLight");

    // Create shared array of lights
    CGparameter larray = cgCreateParameterArray(context, ltype, 2);

    // Create individual lights
    CGparameter plight = cgCreateParameter(context, ptype);
    CGparameter slight = cgCreateParameter(context, stype);

    // Connect individual lights to shared array elements
    cgConnectParameter(plight, cgGetArrayParameter(larray, 0));
    cgConnectParameter(slight, cgGetArrayParameter(larray, 1));

    // Get effect light array parameter
    CGparameter eparam = cgGetNamedEffectParameter(effect, pname);

    // Connect shared array of lights to effect parameter
    cgConnectParameter(larray, eparam);

    CGparameter member;
    // Initialize point light
    member = cgGetNamedStructParameter(plight, "Plight");
    cgSetParameter3f(member, -10, 10, 10);
    member = cgGetNamedStructParameter(plight, "Clight");
    cgSetParameter3f(member, .6, .6, .5);

    // Initialize spot light
    member = cgGetNamedStructParameter(slight, "Plight");
    cgSetParameter3f(member, 10, 10, 10);
    member = cgGetNamedStructParameter(slight, "Clight");
    cgSetParameter3f(member, .5, 1, .7);
    member = cgGetNamedStructParameter(slight, "target");
    cgSetParameter3f(member, 0., 0., 0.);
}

static void initCgFX()
{
    context = cgCreateContext();

    cgSetErrorCallback(errorCallback);
    cgGLRegisterStates(context);

    cgGLSetOptimalOptions(CG_PROFILE_ARBVP1);
    cgGLSetOptimalOptions(CG_PROFILE_ARBFP1);

    effect = cgCreateEffectFromFile(context, "simple.cgfx", NULL);
    if (!effect) {
        fprintf(stderr, "Unable to create effect!\n");
        const char *listing = cgGetLastListing(context);
        if (listing)
            fprintf(stderr, "%s\n", listing);
        exit(1);
    }

    //
    // Initialize lights for unsized array/interfaces example techinque.
    //
    initLights(effect, "lights");

    technique = cgGetFirstTechnique(effect);
    while (technique) {
        if (cgValidateTechnique(technique) == CG_FALSE)
            fprintf(stderr, "Technique %s did not validate.  Skipping.\n",
                    cgGetTechniqueName(technique));
        technique = cgGetNextTechnique(technique);
    }

    technique = cgGetFirstTechnique(effect);
    while (!cgIsTechniqueValidated(technique))
           technique = cgGetNextTechnique(technique);
    if (!technique) {
        fprintf(stderr, "No valid techniques in effect file!  Exiting...\n");
        exit(1);
    }

    // Initialize textures
    CGparameter p = cgGetFirstEffectParameter(effect);
    while (p) {
        if (cgGetParameterType(p) == CG_SAMPLER2D) {
            GLuint handle;

            glGenTextures(1, &handle);
            cgGLSetupSampler(p, handle);

            int count;
            CGannotation ann;

            int frequency = 20;
            ann = cgGetNamedParameterAnnotation(p, "frequency");
            if (ann) {
                const int *vals = cgGetIntAnnotationValues(ann, &count);
                assert(count == 1);
                frequency = *vals;
            }

            float color0[3] = { 1,1,1 }, color1[3] = { 0,0,0 };
            ann = cgGetNamedParameterAnnotation(p, "color0");
            if (ann) {
                const float *vals = cgGetFloatAnnotationValues(ann, &count);
                assert(count == 3);
                color0[0] = vals[0];
                color0[1] = vals[1];
                color0[2] = vals[2];
            }

            ann = cgGetNamedParameterAnnotation(p, "color1");
            if (ann) {
                const float *vals = cgGetFloatAnnotationValues(ann, &count);
                assert(count == 3);
                color1[0] = vals[0];
                color1[1] = vals[1];
                color1[2] = vals[2];
            }

#define RES 256
            float *data = new float[RES*RES*4];
            float *dp = data;
            for (int i = 0; i < RES; ++i) {
                for (int j = 0; j < RES; ++j) {
                    float u = float(i) / float(RES);
                    float v = float(j) / float(RES);
                    u *= frequency;
                    v *= frequency;
                    if ((int(u) + int(v)) & 1) {
                        *dp++ = color0[0];
                        *dp++ = color0[1];
                        *dp++ = color0[2];
                    }
                    else {
                        *dp++ = color1[0];
                        *dp++ = color1[1];
                        *dp++ = color1[2];
                    }
                    *dp++ = 1.;
                }
            }

            glEnable(GL_TEXTURE_2D);
            glBindTexture(GL_TEXTURE_2D, handle);

            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, RES, RES, 0, GL_RGBA, 
                         GL_FLOAT, data);
            delete[] data;

            glDisable(GL_TEXTURE_2D);
        }
        p = cgGetNextParameter(p);
    }
}


static void drawGeom()
{
    glutSolidTeapot(1);
}


static void
printTitle(const char *name)
{
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(0, 512, 0, 512);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();

    glColor3f(1.0, 1.0, 1.0);
    glRasterPos2f(5, 497);
    int len = strlen(name);
    for (int i = 0; i < len; ++i)
        glutBitmapCharacter(GLUT_BITMAP_9_BY_15, name[i]);

    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
}


static void Display() 
{
    static bool firstCall = true;
    if (firstCall) {
        initCgFX();
        firstCall = false;
    }

    glClearColor(0.25f, 0.25f, 0.25f, 1.);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glEnable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
    glColor3f(.1, .5, .1);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60, 1, .01, 1000);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0,0, -ViewDistance);
    glRotatef(ViewAngleY, 0, 1, 0);
    glRotatef(ViewAngleX, 1, 0, 0);

    CGparameter mvp = cgGetEffectParameterBySemantic(effect, "ModelViewProjection");
    assert(mvp);
    cgGLSetStateMatrixParameter(mvp, CG_GL_MODELVIEW_PROJECTION_MATRIX,
                                CG_GL_MATRIX_IDENTITY);

    CGparameter mv = cgGetEffectParameterBySemantic(effect, "ModelView");
    assert(mv);
    cgGLSetStateMatrixParameter(mv, CG_GL_MODELVIEW_MATRIX,
                                CG_GL_MATRIX_IDENTITY);

    CGparameter mvit = cgGetEffectParameterBySemantic(effect, "ModelViewIT");
    assert(mvit);
    cgGLSetStateMatrixParameter(mvit, CG_GL_MODELVIEW_MATRIX,
                                CG_GL_MATRIX_INVERSE_TRANSPOSE);

    CGparameter c = cgGetNamedEffectParameter(effect, "Color");
    assert(c);
    cgSetParameter3fv(c, Color);

    // passes
    CGpass pass = cgGetFirstPass(technique);
    while (pass) {
        cgSetPassState(pass);
        drawGeom();
        cgResetPassState(pass);
        pass = cgGetNextPass(pass);
    }

    // print technique name in the corner of the window
    char buf[1024];
    sprintf(buf, "Technique: %s", cgGetTechniqueName(technique));
    printTitle(buf);

    if (!(MouseLeftButtonDown || MouseRightButtonDown))
        ViewAngleY += .5f;

    glutSwapBuffers();
    glutPostRedisplay();
}
