/************************************************************************
    VRGL           
Willem de Jonge (willem@argcargv.com), Beta Release v0.1,2003
Bob Kuehne (rpk@blue-newt.com), updates, 2005-2006
                                                                 
 VRGL is a modified OpenGL libarary, which makes it possible to use  
off-axis projections with Unreal Tournament 2003. In principle,      
VRGL is generic and can also be used with other OpenGL applications  
to get an off-axis frustum.

Original Code Copyright (C) 2004 Willem de Jonge      
Modifications Copyright (C) 2005 Bob Kuehne / Blue Newt Software
                                    
This library is free software; you can redistribute it and/or modify    
it under the terms of the GNU Lesser General Public License as  
published by the Free Software Foundation; either version 2.1 of the    
License, or (at your option) any later version.         
                                    
This library is distributed in the hope that it will be useful, but 
WITHOUT ANY WARRANTY; without even the implied warranty of      
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU    
Lesser General Public License for more details.         
                                    
You should have received a copy of the GNU Lesser General Public    
License along with this library; if not, write to the Free Software 
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 
USA                                 
                                    
************************************************************************/

#include <io.h>
#include <fcntl.h>

#include <iostream>
#include <string>
using namespace std;

#include "stdafx.h"
#include "CaveUTGL.h"
#include "projectioncorrection.h"
#include "CaveUTGL.h"
#include "inifile.h"
#include "matrixstack.h"
//#include "test.h"

GLJumpTable glJump;
HMODULE gl32Handle = 0;

ProjectionDistortion distorter;

GLenum currMatrixMode = GL_MODELVIEW; // tracks state of GL_MATRIX_MODE
MatrixStack projStack;                // mimics opengl projection stack

// projection parameters (read from ini file on init)
bool  OffAxis   = true;
float FOVleft   = -45.0f;
float FOVright  =  45.0f;
float FOVtop    =  20.0f;
float FOVbottom = -40.0f;
float CaveTranslation[3]  = { 0.0, 0.0, 0.0 };
float CaveRotation[3]     = { 0.0, 0.0, 0.0 };

// blending parameters
bool  BlendEnabled  = false;
float LeftEdgeBegin  = 0.0;
float LeftEdgeEnd    = 0.25;
float RightEdgeBegin = 0.75;
float RightEdgeEnd   = 1.0;
float BlendExponent = 2.0; 
float AllGamma     = 0.0;
float RedGamma     = 1.0;
float GreenGamma   = 1.0;
float BlueGamma    = 1.0;

// color parameters
float  RedScale     = 1.0;
float  RedBias      = 0.0;
float  BlueScale    = 1.0;
float  BlueBias     = 0.0;
float  GreenScale   = 1.0;
float  GreenBias    = 0.0;

// spherical projection parameters
std::string ImagePath = "";
std::string ImageName = "";
float ImageFrame = 6000;
bool  OGLCorrect = true;
bool  EnableSpherical = false;
float Width      =  1024.0f;
float Height     =  768.0f;
float Radius     =  10.0f;
float SphereOverlap = 00;
float SphereLeft   = -45.0f;
float SphereRight  =  45.0f;
float SphereTop    =  30.0f;
float SphereBottom = -30.0f;
float SphereRotation = 0.0f;
float FOPleft    = -45.0f;
float FOPright   =  45.0f;
float FOPtop     =  30.0f;
float FOPbottom  = -30.0f;
float FOPpitch = 0.0f;
float FOPyaw = 0.0f;
float ProjectorX = 0;
float ProjectorY = 0;
float ProjectorZ = 0;
float EyeX       = 0;
float EyeY       = 0;
float EyeZ       = 0;

// debug parameters
bool  Debug      = true;
bool  Grid      = true;
bool  BlendDebug = false;

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
                     )
{
    printf( "DllMain - OpenGL32.dll replacement - caveut\n" );
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_DETACH:
            return dllFini();
        break;

        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        default:
        break;
    }
    return TRUE;
}

void createConsole()
{
    int hCrt;
    FILE *hf;
    static int initialized = 0;

    if(initialized == 1)
    {
        return;
    }

#if defined( USE_VRGL_DEBUG_CONSOLE )
	AllocConsole();
#endif

    // Setup stdout
    hCrt = _open_osfhandle( (long)GetStdHandle(STD_OUTPUT_HANDLE),_O_TEXT );
    hf = _fdopen(hCrt, "w" );
    *stdout = *hf;
    setvbuf(stdout, NULL, _IONBF, 0);

    // Setup stderr
    hCrt = _open_osfhandle( (long)GetStdHandle(STD_ERROR_HANDLE), _O_TEXT );
    hf = _fdopen(hCrt, "w" );
    *stderr = *hf;
    setvbuf(stderr, NULL, _IONBF, 0);
}

BOOL dllInit()
{
    // only initialize once, might be called more than once because
    // dllInit is called from every wgl function
    // reason for this is that we we cannot initialize in DllMain
    // because LoadLibrary() cannot be called from DllMain (see WinAPI help on DllMain)
    if (gl32Handle!=0)
        return TRUE;

    // load original opengl32.dll
    char gl32Name[MAX_PATH];
    if (GetSystemDirectory(gl32Name, MAX_PATH)==0)
        return FALSE;
    strcat(gl32Name, "\\opengl32.dll");
    gl32Handle = LoadLibrary(gl32Name);
    if(gl32Handle==NULL)
        return FALSE;

    // initialize jump table
    if (!glJump.init())
        return false;

    // load parameters
    loadParams();

    // create console if debugging
#if defined( USE_VRGL_DEBUG_CONSOLE )
    if ( Debug == true ) createConsole();
#endif
	loadParams();

    // run tests
    if ( Debug == true )
    {
        unsigned int nfail = 0; // tests();
        if ( nfail )
        {
            cout << "tests failed: " << nfail << endl;
        }
        else
        {
            cout << "tests passed!" << endl;
        }
    }

    return TRUE;
}

BOOL dllFini()
{
    // unload original opengl32.dll
    if (gl32Handle)
        FreeLibrary(gl32Handle);

    return TRUE;
}

// "hand coded" part op GLJumpTable, get funtion pointer to original opengl dll
bool GLJumpTable::getFunction(const char* name, void** fptr)
{
    *fptr = GetProcAddress(gl32Handle, name);
    return *fptr!=0;
}

void loadParams()
{
    IniFile ini;
    bool success = ini.read("CaveUT.ini");
    
    std::string section;

    section = "CaveUT.Spectator";
    
    ini.get(section, "EnableOffAxis",     OffAxis, true);
    ini.get(section, "FOVleft",     FOVleft, -45.0f);
    ini.get(section, "FOVright",    FOVright, 45.0f);
    ini.get(section, "FOVtop",      FOVtop, 30.0f);
    ini.get(section, "FOVbottom",   FOVbottom, -30.0f);

    ini.get(section, "CaveOffsetX",   CaveTranslation[0], 0.0f);
    ini.get(section, "CaveOffsetY",   CaveTranslation[1], 0.0f);
    ini.get(section, "CaveOffsetZ",   CaveTranslation[2], 0.0f);

    ini.get(section, "CaveRoll",      CaveRotation[0], 0.0f);
    ini.get(section, "CavePitch",     CaveRotation[1], 0.0f);
    ini.get(section, "CaveYaw",       CaveRotation[2], 0.0f);

    section = "VRGL.SphereCorrection";

    ini.get(section, "Enable",     EnableSpherical, false);
    ini.get(section, "UseOGL",     OGLCorrect, true);
    ini.get(section, "Width",      Width,     1024.0f);
    ini.get(section, "Height",     Height,    768.0f);
    ini.get(section, "Radius",     Radius,    10.0f);
    ini.get(section, "SphereOverlap",    SphereOverlap,  -0.0f);
    ini.get(section, "SphereLeft",    SphereLeft,  -45.0f);
    ini.get(section, "SphereRight",   SphereRight,  45.0f);
    ini.get(section, "SphereTop",     SphereTop,   -30.0f);
    ini.get(section, "SphereBottom",  SphereBottom, 30.0f);
    ini.get(section, "SphereRotation",  SphereRotation, 0.0f);
    ini.get(section, "FOPleft",    FOPleft,  -45.0f);
    ini.get(section, "FOPright",   FOPright,  45.0f);
    ini.get(section, "FOPtop",     FOPtop,   -30.0f);
    ini.get(section, "FOPbottom",  FOPbottom, 30.0f);
    ini.get(section, "FOPpitch", FOPpitch, 0.0f);
    ini.get(section, "FOPyaw", FOPyaw, 0.0f);
    ini.get(section, "ProjectorX",  ProjectorX, 0.0f);
    ini.get(section, "ProjectorY",  ProjectorY, 0.0f);
    ini.get(section, "ProjectorZ",  ProjectorZ, 0.0f);
    ini.get(section, "EyeX",  EyeX, 0.0f);
    ini.get(section, "EyeY",  EyeY, 0.0f);
    ini.get(section, "EyeZ",  EyeZ, 0.0f);

    section = "VRGL.Debug";

    ini.get(section, "Enable",        Debug,    true );
    ini.get(section, "Grid",          Grid,     false );
    ini.get(section, "Blend",         BlendDebug,  false );
    ini.get(section, "ImagePath",     ImagePath, "" );
    ini.get(section, "ImageName",     ImageName, "" );
    ini.get(section, "ImageFrame",    ImageFrame, 6000 );

    section = "VRGL.Blend";
    ini.get(section, "Enable",          BlendEnabled,   false );
    ini.get(section, "LeftEdgeBegin",   LeftEdgeBegin,  0.0 );
    ini.get(section, "LeftEdgeEnd",     LeftEdgeEnd,    0.25 );
    ini.get(section, "RightEdgeBegin",  RightEdgeBegin, 0.75 );
    ini.get(section, "RightEdgeEnd",    RightEdgeEnd,   1.0 );
    ini.get(section, "CurvePower",   BlendExponent,  2.0);
    ini.get(section, "AllGamma",     AllGamma,       0.0);
    ini.get(section, "RedGamma",     RedGamma,       1.0);
    ini.get(section, "GreenGamma",   GreenGamma,     1.0);
    ini.get(section, "BlueGamma",    BlueGamma,      1.0);

    section = "VRGL.Color";
    ini.get( section, "RedScale",     RedScale,     1.0 );
    ini.get( section, "RedBias",      RedBias,      0.0 );
    ini.get( section, "GreenScale",   GreenScale,   1.0 );
    ini.get( section, "GreenBias",    GreenBias,    0.0 );
    ini.get( section, "BlueScale",    BlueScale,    1.0 );
    ini.get( section, "BlueBias",     BlueBias,     0.0 );

    distorter.setBlendParams( BlendEnabled, BlendDebug, LeftEdgeBegin, LeftEdgeEnd,
                              RightEdgeBegin, RightEdgeEnd, BlendExponent );
    distorter.setBlendGamma( AllGamma, RedGamma, GreenGamma, BlueGamma );
    distorter.setColorScaleBias( RedScale, GreenScale, BlueScale, RedBias, GreenBias, BlueBias );
    distorter.setSphere(Radius, SphereOverlap);
    distorter.setViewModel(SphereLeft, SphereRight, SphereTop, SphereBottom, SphereRotation );
    distorter.setEye(EyeX, EyeY, EyeZ);
    distorter.setProjector(ProjectorX, ProjectorY, ProjectorZ,
        FOPleft, FOPright, FOPtop, FOPbottom, FOPpitch, FOPyaw );
}

void loadOffAxisMatrix()
{
    // if current matrix (on projStack) is a perspective load a custom
    // off-axis projection matix to opengl, for non-perspective pass
    // current to opengl (this way we can still use the applications menus etc)

    if (projStack.IsPerspective())
    {
        // perspective, extract near and far planes
        GLfloat n,f;
        projStack.CurrentMatrix().GetNearFar(n,f);

        // construct custum off-axis matrix
        Matrix4 M;
        // simple test use right z-direction
        bool swapz = projStack.CurrentMatrix().a32>0;
        M.Frustum(FOVleft,FOVright,FOVbottom,FOVtop,n,f,swapz);

        // load it to opengl
        glJump.glLoadMatrixf(M);
    }
    else
        // not perspective, load top of projection stack to opengl
        glJump.glLoadMatrixf(projStack.CurrentMatrix());
}

// track GL_MATRIX_MODE
GLAPI void GLAPIENTRY glMatrixMode (GLenum mode)
{
    glJump.glMatrixMode (mode);
    currMatrixMode = mode;
}

//////////////////////////////////////////////////////////////////////////
// functions to track and adjust projection matrix
//////////////////////////////////////////////////////////////////////////

GLAPI void GLAPIENTRY glPopMatrix ()
{
    glJump.glPopMatrix();
    if (currMatrixMode==GL_PROJECTION && OffAxis)
        projStack.PopMatrix();
}

GLAPI void GLAPIENTRY glPushMatrix ()
{
    glJump.glPushMatrix();
    if (currMatrixMode==GL_PROJECTION && OffAxis)
        projStack.PushMatrix();
}

GLAPI void GLAPIENTRY glLoadIdentity( void )
{
    if (currMatrixMode==GL_PROJECTION && OffAxis)
        projStack.LoadIdentity();
    glJump.glLoadIdentity();
}

GLAPI void GLAPIENTRY glLoadMatrixf (const GLfloat *m)
{
    if (currMatrixMode==GL_PROJECTION && OffAxis)
    {
        projStack.LoadMatrix(m);
        loadOffAxisMatrix();
    }
    else
        glJump.glLoadMatrixf (m);
}

GLAPI void GLAPIENTRY glMultMatrixf (const GLfloat *m)
{
    if (currMatrixMode==GL_PROJECTION && OffAxis)
    {
        projStack.MultMatrix(m);
        loadOffAxisMatrix();
    }
    else
        glJump.glMultMatrixf (m);
}

GLAPI void GLAPIENTRY glTranslatef( GLfloat x, GLfloat y, GLfloat z )
{
    if (currMatrixMode==GL_PROJECTION && OffAxis)
    {
        projStack.Translate(x, y, z);
        loadOffAxisMatrix();
    }
    else
        glJump.glTranslatef(x, y, z);
}

GLAPI void GLAPIENTRY glScalef( GLfloat x, GLfloat y, GLfloat z )
{
    if (currMatrixMode==GL_PROJECTION && OffAxis)
    {
        projStack.Scale(x, y, z);
        loadOffAxisMatrix();
    }
    else
        glJump.glScalef(x, y, z);
}

GLAPI void GLAPIENTRY glOrtho( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val )
{
    if (currMatrixMode==GL_PROJECTION && OffAxis)
    {
        projStack.Ortho(left, right, bottom, top, near_val, far_val);
        loadOffAxisMatrix();
    }
    else
        glJump.glOrtho(left, right, bottom, top, near_val, far_val);
}

GLAPI void GLAPIENTRY glFrustum( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val )
{
    if (currMatrixMode==GL_PROJECTION && OffAxis)
    {
        projStack.Frustum(left, right, bottom, top, near_val, far_val);
        loadOffAxisMatrix();
    }
    else
        glJump.glFrustum(left, right, bottom, top, near_val, far_val);
}

GLAPI int GLAPIENTRY wglSwapBuffers( HDC hdc )
{
/* rpk - 10/31/05 - the glGet doesnt seem to work. is our context not active? no, since
    we issue gl calls correctly. are we having problems with the config?
    GLint viewport[4];
    glGetIntegerv(GL_VIEWPORT, viewport);
    glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
    GLint scissor[4];
    glGetIntegerv(GL_SCISSOR_BOX, scissor);
    glScissor(scissor[0], scissor[1], scissor[2], scissor[3]);
*/
    glViewport(0, 0, Width, Height );
    glScissor(0, 0, Width, Height );

    if (EnableSpherical)
        distorter.distortionCorrect( Debug, Grid, OGLCorrect, ImagePath, ImageName, ImageFrame );

    //glClearColor(1, 0, 0, 0);
    //glClear(GL_COLOR_BUFFER_BIT);

    return glJump.wglSwapBuffers(hdc);
}

GLAPI void GLAPIENTRY glLoadMatrixd( const GLdouble *m )
{
    if (currMatrixMode==GL_PROJECTION && OffAxis)
    {
        Matrix4 fm(m);
        projStack.LoadMatrix(fm);
        loadOffAxisMatrix();
    }
    else
        glJump.glLoadMatrixd(m);
}

GLAPI void GLAPIENTRY glMultMatrixd( const GLdouble *m )
{
    if (currMatrixMode==GL_PROJECTION && OffAxis)
    {
        Matrix4 fm(m);
        projStack.MultMatrix(fm.m);
        loadOffAxisMatrix();
    }
    else
        glJump.glMultMatrixd(m);
}

GLAPI void GLAPIENTRY glRotated( GLdouble angle, GLdouble x, GLdouble y, GLdouble z )
{
    if (currMatrixMode==GL_PROJECTION && OffAxis)
    {
        projStack.Rotate(angle, x, y, z);
        loadOffAxisMatrix();
    }
    else
        glJump.glRotated(angle, x, y, z);
}

GLAPI void GLAPIENTRY glRotatef( GLfloat angle, GLfloat x, GLfloat y, GLfloat z )
{
    if (currMatrixMode==GL_PROJECTION && OffAxis)
    {
        projStack.Rotate(angle, x, y, z);
        loadOffAxisMatrix();
    }
    else
        glJump.glRotatef(angle, x, y, z);
}

GLAPI void GLAPIENTRY glScaled( GLdouble x, GLdouble y, GLdouble z )
{
    if (currMatrixMode==GL_PROJECTION && OffAxis)
    {
        projStack.Scale(x, y, z);
        loadOffAxisMatrix();
    }
    else
        glJump.glScaled(x, y, z);
}

GLAPI void GLAPIENTRY glTranslated( GLdouble x, GLdouble y, GLdouble z )
{
    if (currMatrixMode==GL_PROJECTION && OffAxis)
    {
        projStack.Translate(x, y, z);
        loadOffAxisMatrix();
    }
    else
        glJump.glTranslated(x, y, z);
}

GLAPI void GLAPIENTRY glGetFloatv( GLenum pname, GLfloat *params )
{
    if (pname==GL_PROJECTION_MATRIX && OffAxis)
    {
        for (int i=0; i<16; i++)
            params[i] = projStack.CurrentMatrix().m[i];
    }
    else if (pname==GL_TRANSPOSE_PROJECTION_MATRIX && OffAxis)
    {
        const Matrix4& tm = projStack.CurrentMatrix();
        params[ 0] = tm.a00; params[ 1] = tm.a01; params[ 2] = tm.a02; params[ 3] = tm.a03;
        params[ 4] = tm.a10; params[ 5] = tm.a11; params[ 6] = tm.a12; params[ 7] = tm.a13;
        params[ 8] = tm.a20; params[ 9] = tm.a21; params[10] = tm.a22; params[11] = tm.a23;
        params[12] = tm.a30; params[13] = tm.a31; params[14] = tm.a32; params[15] = tm.a33;
    }
    else
        glJump.glGetFloatv(pname, params);
}


GLAPI int GLAPIENTRY wglMakeCurrent( HDC hdc, HGLRC hgl )
{
    dllInit();
    int r = glJump.wglMakeCurrent(hdc, hgl);
    if(r==TRUE && hdc!=NULL && hgl!=NULL)
        glJump.initExtensions();
    return r;
}

//////////////////////////////////////////////////////////////////////////

