#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <commdlg.h>
#include <ptypes32.h>
#include <pwin32.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <math.h>
#include <sys\types.h>
#include <sys\timeb.h>

#include <GL/glaux.h>
#include "tk.h"

#include "sscommon.h"
#include "trackbal.h"

static void Draw(void);
static void DrawAuto(void);
static void Init(void);
static void InitLighting(void);
static void Reshape(int, int);
static void SetModelView( void );
static GLenum Key(int key, GLenum mask);

static int GetStringExtent( char *string, POINTFLOAT *extent );
static void CalcStringMetrics( char *string );

static void Init(void);
static void InitLighting(void);
static void Reshape(int, int);
static GLenum Key(int key, GLenum mask);
VOID Arg( LPSTR );

// Global variables
GLuint  dlFont, dpFont;
GLint firstGlyph;
GLint Width = 300, Height = 300;
POINTFLOAT strSize;
int strLen;
GLfloat xRot = 0.0f, yRot = 0.0f, zRot = 0.0f;
GLdouble zTrans = -200.0;
GLfloat extrusion;
GLenum doubleBuffer = GL_TRUE;
GLenum bLighting = GL_TRUE;
GLenum fontStyle = WGL_FONT_POLYGONS;
LPGLYPHMETRICSFLOAT lpgmf;

// output from tesselator always ccw
GLenum orientation = GL_CCW;

char *texFileName = 0;
GLint matIndex = 1;
BOOL bAutoRotate = TRUE;
BOOL bCullBack   = TRUE;
BOOL bTwoSidedLighting = FALSE;

BOOL    bInitTexture = TRUE;
BOOL    bTexture = FALSE;
BOOL    bAntialias = FALSE;

char singleChar[] = "A";
char string[] = "OpenGL!";
char *drawString = singleChar;

#if defined( TIME )
#undef TIME
#endif
typedef struct _timeb TIME;

enum {
    TIMER_START = 0,
    TIMER_STOP,
    TIMER_TIMING,
    TIMER_RESET
};
static int gTimerStatus = TIMER_RESET;

int WINAPI
WinMain(    HINSTANCE   hInstance,
            HINSTANCE   hPrevInstance,
            LPSTR       lpCmdLine,
            int         nCmdShow
        )
{
    GLenum type;

    Arg( lpCmdLine );

    tkInitPosition( 0, 0, Width, Height );

    type = TK_DEPTH16;
    type |= TK_RGB;
    type |= (doubleBuffer) ? TK_DOUBLE : TK_SINGLE;
    tkInitDisplayMode(type);

    if (tkInitWindow("Fontal assault") == GL_FALSE) {
	    tkQuit();
    }

    Init();

    tkExposeFunc( Reshape );
    tkReshapeFunc( Reshape );
    tkKeyDownFunc( Key );
    tkMouseDownFunc( trackball_MouseDown );
    tkMouseUpFunc( trackball_MouseUp );

    trackball_Init( Width, Height );

    if( bAutoRotate ) {
        tkIdleFunc( DrawAuto );
        tkDisplayFunc(0);
    } else {
        tkDisplayFunc(Draw);
    }

    tkExec();
    return 1;
}

static GLenum Key(int key, GLenum mask)
{

    switch (key) {
      case TK_ESCAPE:
	    tkQuit();

      case TK_LEFT:
	    yRot -= 5;
	    break;
      case TK_RIGHT:
	    yRot += 5;
	    break;
      case TK_UP:
	    xRot -= 5;
	    break;
      case TK_DOWN:
	    xRot += 5;
	    break;
      case TK_X:
	    zRot += 5;
	    break;
      case TK_x:
	    zRot -= 5;
	    break;
      case TK_Z:
	    zTrans -= strSize.y/10.0;
        SetModelView();
	    break;
      case TK_z:
	    zTrans += strSize.y/10.0;
        SetModelView();
	    break;
      case TK_l:
	    if( bLighting = !bLighting ) {
            glEnable(GL_LIGHTING);
            glEnable(GL_LIGHT0);
        }
        else
        {
            glDisable(GL_LIGHTING);
            glDisable(GL_LIGHT0);
        }
	    break;
      case TK_m:
        if( !bTexture ) {
            if( ++matIndex > NUM_TEA_MATERIALS )
                matIndex =0;
            ss_SetMaterialIndex( matIndex );
        }
	    break;
      case TK_n:
            singleChar[0] += 1;
	    break;
      case TK_N:
            singleChar[0] -= 1;
	    break;
      
#if 1
      case TK_o:
	    orientation = (orientation == GL_CCW) ? GL_CW : GL_CCW;
        glFrontFace( orientation );
	    break;
#else
      case TK_o:
            if( bAntialias = !bAntialias ) {
                if( fontStyle == WGL_FONT_LINES ) {
                    glEnable( GL_LINE_SMOOTH );
                    glEnable( GL_BLEND );
                } else {
                    glDisable( GL_DEPTH_TEST );
                    glEnable( GL_POLYGON_SMOOTH );
                    glEnable( GL_BLEND );
                }
            } else  {
                glDisable( GL_POLYGON_SMOOTH );
                glDisable( GL_LINE_SMOOTH );
                glDisable( GL_BLEND );
                glEnable( GL_DEPTH_TEST );
            }
            break;
#endif
      case TK_t:
        bTexture = !bTexture;
        if( bTexture ) {
            ss_SetMaterialIndex( WHITE );
            glEnable( GL_TEXTURE_2D );
            glEnable(GL_TEXTURE_GEN_S);
            glEnable(GL_TEXTURE_GEN_T);
        } else {
            ss_SetMaterialIndex( matIndex );
            glDisable( GL_TEXTURE_2D );
            glDisable(GL_TEXTURE_GEN_S);
            glDisable(GL_TEXTURE_GEN_T);
        }
        break;
      case TK_s:
        // switch between wireframe and solid polygons
	    fontStyle = (fontStyle == WGL_FONT_LINES) ? WGL_FONT_POLYGONS :
                    WGL_FONT_LINES;
	    break;

      case TK_c:
        drawString = (drawString == singleChar) ? string : singleChar;
        CalcStringMetrics( drawString );
        // change viewing matrix based on string extent
        Reshape( Width, Height );
        //SetModelView();
        break;

      case TK_u:
        // cUllface stuff
        bCullBack = !bCullBack;
        if( bCullBack ) {
            glCullFace( GL_BACK );
            glEnable( GL_CULL_FACE );
        } else {
            glDisable( GL_CULL_FACE );
        }
        break;

      case TK_2:
        bTwoSidedLighting = !bTwoSidedLighting;
        if( bTwoSidedLighting ) {
            glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1 );
        } else {
            glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 0 );
        }
        break;

      case TK_a:
        // animate or not
        bAutoRotate= !bAutoRotate;
        if( bAutoRotate ) {
            tkIdleFunc(DrawAuto);
            tkDisplayFunc(0);
        } else {
            tkIdleFunc(0);
            tkDisplayFunc(Draw);
        }
        break;

        case TK_f :
            switch( gTimerStatus ) {
                case TIMER_START:
                    break;
                case TIMER_STOP:
                case TIMER_RESET:
                    gTimerStatus = TIMER_START;
                    break;
                case TIMER_TIMING:
                    gTimerStatus = TIMER_STOP;
                    break;
                default:
                    break;
            }
        break;

      default:
	    return GL_FALSE;
    }
    return GL_TRUE;
}

static void InitLighting(void)
{
    static float ambient[] = {0.1f, 0.1f, 0.1f, 1.0f};
    static float diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
    static float position[] = {0.0f, 0.0f, 150.0f, 0.0f};

    static float back_mat_shininess[] = {50.0f};
    static float back_mat_specular[] = {0.5f, 0.5f, 0.2f, 1.0f};
    static float back_mat_diffuse[] = {1.0f, 0.0f, 0.0f, 1.0f};
    static float lmodel_ambient[] = {1.0f, 1.0f, 1.0f, 1.0f};
    static float decal[] = {(GLfloat) GL_DECAL}; 
    static float modulate[] = {(GLfloat) GL_MODULATE};
    static float repeat[] = {(GLfloat) GL_REPEAT}; 
    static float nearest[] = {(GLfloat) GL_NEAREST};
    TK_RGBImageRec *image;

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    
    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);
    glFrontFace( orientation );

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    if( bLighting ) {
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
    }

    // override back material
    glMaterialfv( GL_BACK, GL_AMBIENT, ambient );
    glMaterialfv( GL_BACK, GL_DIFFUSE, back_mat_diffuse );
    glMaterialfv( GL_BACK, GL_SPECULAR, back_mat_specular );
    glMaterialfv( GL_BACK, GL_SHININESS, back_mat_shininess );

    if( bTwoSidedLighting ) {
        glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1 );
    } else {
        glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 0 );
    }

    // set back face culling mode

    if( bCullBack ) {
        glCullFace( GL_BACK );
        glEnable( GL_CULL_FACE );
    } else {
        glDisable( GL_CULL_FACE );
    }

    // Initialize materials from screen saver common lib
    ss_InitMaterials();
    matIndex = JADE;
    ss_SetMaterialIndex( matIndex );

    // do texturing preparations

    if( bInitTexture ) {
        //glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, decal);
        glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, modulate);
        glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, repeat);
        glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, repeat);
        glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, nearest);
        glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, nearest);
        if (texFileName) {
	        image = tkRGBImageLoad(texFileName);
	        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	        gluBuild2DMipmaps(GL_TEXTURE_2D, 3, image->sizeX, image->sizeY,
			              GL_RGB, GL_UNSIGNED_BYTE, image->data);
        }
        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
        if( bTexture ) {
            glEnable( GL_TEXTURE_2D );
            glEnable(GL_TEXTURE_GEN_S);
            glEnable(GL_TEXTURE_GEN_T);
            ss_SetMaterialIndex( WHITE );
        }
    }
}

void Init(void)
{
    CHOOSEFONT cf;
    LOGFONT    lf;
    HFONT      hfont, hfontOld;
    GLfloat     chordal_deviation;
    int numGlyphs=224;
    HWND  hwnd;
    HDC   hdc;
    TIME baseTime;
    TIME thisTime;
    double elapsed;
    char buf[100];

    hdc = tkGetHDC();
    hwnd = tkGetHWND();

    // Create and select a font.

    cf.lStructSize = sizeof(CHOOSEFONT);
    cf.hwndOwner = hwnd;
    cf.hDC = hdc;
    cf.lpLogFont = &lf;
    cf.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT | CF_TTONLY;

    memset(&lf, 0, sizeof(LOGFONT));
    lstrcpy(lf.lfFaceName, "Arial");
    lf.lfHeight = 50;

    ChooseFont(&cf);

    // lf reflects user's final choices

    hfont = CreateFontIndirect(&lf);

    hfontOld = SelectObject(hdc, hfont);

    // Generate a display list for font

    dlFont = glGenLists(numGlyphs);
    dpFont = glGenLists(numGlyphs);

    chordal_deviation = 0.005f;
    extrusion = 0.25f;

    firstGlyph = 32;

    lpgmf = (LPGLYPHMETRICSFLOAT) LocalAlloc( LMEM_FIXED, numGlyphs *
                                    sizeof(GLYPHMETRICSFLOAT) );
    wglUseFontOutlinesA(hdc, firstGlyph, numGlyphs, dlFont, chordal_deviation, 
                       extrusion, WGL_FONT_LINES, lpgmf );

    _ftime( &baseTime );
    wglUseFontOutlinesA(hdc, firstGlyph, numGlyphs, dpFont, chordal_deviation, 
                        extrusion, WGL_FONT_POLYGONS, NULL );
    glFinish();
    _ftime( &thisTime );
    elapsed = thisTime.time + thisTime.millitm/1000.0 -
       (baseTime.time + baseTime.millitm/1000.0);
    sprintf( buf, "Setup time = %5.2f seconds", elapsed );
    SendMessage(hwnd, WM_SETTEXT, 0, (LPARAM)buf);

    CalcStringMetrics( drawString );

    /* Set the clear color */

    glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );

    // Initialize lighting
    InitLighting();

    // Blend func for AA
    glBlendFunc( GL_SRC_ALPHA, GL_ONE );
}

static void Reshape( int width, int height )
{
    static GLdouble zNear = 0.1, zFar = 1000.0, fovy = 90.0;
    float aspect;

    trackball_Resize( width, height );

    /* Set up the projection matrix */

    Width = width;
    Height = height;
    aspect = Height == 0 ? 1.0f : Width/(float)Height;

    glViewport(0, 0, Width, Height );
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    zFar = 10.0f * strSize.x;
    gluPerspective(fovy, aspect, zNear, zFar );
    glMatrixMode(GL_MODELVIEW);

    SetModelView();
}

static void SetModelView( void )
{
    glLoadIdentity();
#if 1
    glTranslated( 0, 0, zTrans );
#else
    /* this has side effect of flipping around viewpoint when pass through
       origin
    */
    gluLookAt( 0, 0, -zTrans,
               0, 0, 0,
               0, 1, 0 );
#endif
}

void CalcFrameRate( char *buf, struct _timeb baseTime, int frameCount ) {
    static struct _timeb thisTime;
    double elapsed, frameRate;

    _ftime( &thisTime );
    elapsed = thisTime.time + thisTime.millitm/1000.0 -
       (baseTime.time + baseTime.millitm/1000.0);
    if( elapsed == 0.0 )
        sprintf( buf, "Frame rate = unknown" );
    else {
        frameRate = frameCount / elapsed;
        sprintf( buf, "Frame rate = %4.1f fps", frameRate );
    }
}
static void DrawAuto(void)
{
    POINT pt;
    float  matRot[4][4];
    static int frameCount = 0;
    static struct _timeb baseTime;
    char buf[1024];
    HWND  hwnd;
    BOOL bTiming = TRUE;

    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    if( fontStyle == WGL_FONT_LINES )
        glListBase(dlFont-32);
    else
        glListBase(dpFont-32);

    glPushMatrix();

    trackball_CalcRotMatrix( matRot );
    glMultMatrixf(&(matRot[0][0]));

    // now draw text, centered in window

    glTranslated( -strSize.x/2.0, -strSize.y/2.0, extrusion/2.0 );

    glCallLists(strLen, GL_UNSIGNED_BYTE, (GLubyte *) drawString);

    glPopMatrix();

    glFlush();
    if( doubleBuffer )
        tkSwapBuffers();

    hwnd = tkGetHWND();

    switch( gTimerStatus ) {
        case TIMER_START:
            gTimerStatus = TIMER_TIMING;
            frameCount = 0;
            sprintf( buf, "Timing..." );
            SendMessage(hwnd, WM_SETTEXT, 0, (LPARAM)buf);
 	        _ftime( &baseTime );
            break;
        case TIMER_STOP:
            gTimerStatus = TIMER_RESET;
            frameCount++;
            CalcFrameRate( buf, baseTime, frameCount );
            SendMessage(hwnd, WM_SETTEXT, 0, (LPARAM)buf);
            break;
        case TIMER_TIMING:
            frameCount++;
            break;
        case TIMER_RESET:
        default:
            break;
    }
}

static void Draw(void)
{

    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    if( fontStyle == WGL_FONT_LINES )
        glListBase(dlFont-32);
    else
        glListBase(dpFont-32);

    glPushMatrix();

    glRotatef( xRot, 1.0f, 0.0f, 0.0f );
    glRotatef( yRot, 0.0f, 1.0f, 0.0f );
    glRotatef( zRot, 0.0f, 0.0f, 1.0f );

    // center the string in window
    glTranslated( -strSize.x/2.0, -strSize.y/2.0, extrusion/2.0 );
    glCallLists(strLen, GL_UNSIGNED_BYTE, (GLubyte *) drawString);

    glPopMatrix();

    glFlush();
    if( doubleBuffer )
        tkSwapBuffers();
}

static int GetStringExtent( char *string, POINTFLOAT *extent )
{
    int len, strLen;
    unsigned char *c;
    GLYPHMETRICSFLOAT *pgmf;

    extent->x = extent->y = 0.0f;
    len = strLen = lstrlenA( string );
    c = string;

    for( ; strLen; strLen--, c++ ) {
        if( *c < firstGlyph )
            continue;
        pgmf = &lpgmf[ *c - firstGlyph ];
        extent->x += pgmf->gmfCellIncX;
        if( pgmf->gmfBlackBoxY > extent->y )
            extent->y = pgmf->gmfBlackBoxY;
    }
    return len;
}

static void CalcStringMetrics( char *string )
{
    // get string size for window reshape
    strLen = GetStringExtent( string, &strSize );

    // set zTrans based on glyph extent
    zTrans = - 3.0 * (strSize.y / 2.0f);
}

VOID Arg(LPSTR lpstr)
{
    while (*lpstr && *lpstr != '-') lpstr++;
    // only one arg for now (e.g.: -f 3.rgb)
    lpstr++;
    if( *lpstr++ == 'f' ) {
        texFileName = ++lpstr; 
    }
}