#define ZBUFFER         0
#define DBLBUFFER       1
#define USE_COLOR_INDEX 1

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <ptypes32.h>
#include <pwin32.h>

long WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );
long DlgProcRotate ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );
void DoGlStuff( HWND hWnd, HDC hDc );
HGLRC hrcInitGL(HWND hwnd, HDC hdc);
void vResizeDoubleBuffer(HWND hwnd, HDC hdc);
void vCleanupGL(HGLRC hrc);

#include <GL\gl.h>

#define TERMINATE   DbgPrint("%s (%d)\n", __FILE__, __LINE__), ExitProcess(0)

#define WINDSIZEX(Rect)   (Rect.right - Rect.left)
#define WINDSIZEY(Rect)   (Rect.bottom - Rect.top)

// Default Logical Palette indexes
#define BLACK_INDEX     0
#define WHITE_INDEX     19
#define RED_INDEX       13
#define GREEN_INDEX     14
#define BLUE_INDEX      16
#define YELLOW_INDEX    15
#define MAGENTA_INDEX   17
#define CYAN_INDEX      18

// Global variables defining current position and orientation.
GLfloat AngleX = 145.0;
GLfloat AngleY = 50.0;
GLfloat AngleZ = 0.0;
GLfloat DeltaAngle[3] = { 0.0, 0.0, 0.0 };
GLfloat OffsetX = 0.0;
GLfloat OffsetY = 0.0;
GLfloat OffsetZ = -3.0;
GLuint  DListCube;
UINT    guiTimerTick = 128;

HGLRC ghrc = (HGLRC) 0;

#ifdef DBLBUFFER
HDC     ghdcMem;
HBITMAP ghbmBackBuffer = (HBITMAP) 0, ghbmOld;
#endif
HWND hdlgRotate;

int WINAPI
WinMain(    HINSTANCE   hInstance,
            HINSTANCE   hPrevInstance,
            LPSTR       lpCmdLine,
            int         nCmdShow
        )
{
    static char szAppName[] = "TimeCube";
    HWND hwnd;
    MSG msg;
    RECT Rect;
    WNDCLASS wndclass;

    if ( !hPrevInstance )
    {
        //wndclass.style          = CS_HREDRAW | CS_VREDRAW;
        wndclass.style          = 0;
        wndclass.lpfnWndProc    = (WNDPROC)WndProc;
        wndclass.cbClsExtra     = 0;
        wndclass.cbWndExtra     = 0;
        wndclass.hInstance      = hInstance;
        wndclass.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wndclass.hbrBackground  = GetStockObject(WHITE_BRUSH);
        wndclass.lpszMenuName   = NULL;
        wndclass.lpszClassName  = szAppName;

        // With a NULL icon handle, app will paint into the icon window.
        wndclass.hIcon          = NULL;
        //wndclass.hIcon          = LoadIcon(hInstance, "CubeIcon");

        RegisterClass(&wndclass);
    }

    /*
     *  Make the windows a reasonable size and pick a
     *  position for it.
     */

    Rect.left   = GetPrivateProfileInt("Window", "left",   100, "timecube.ini");
    Rect.top    = GetPrivateProfileInt("Window", "top",    100, "timecube.ini");;
    Rect.right  = GetPrivateProfileInt("Window", "right",  200, "timecube.ini");;
    Rect.bottom = GetPrivateProfileInt("Window", "bottom", 200, "timecube.ini");;
    guiTimerTick= GetPrivateProfileInt("Animate", "Timer", 32,  "timecube.ini");;

    AdjustWindowRect( &Rect, WS_OVERLAPPEDWINDOW, FALSE );

    hwnd = CreateWindow  (  szAppName,              // window class name
                            "TimeCube",             // window caption
                            WS_OVERLAPPEDWINDOW,    // window style
                            Rect.left,              // initial x position
                            Rect.top,               // initial y position
                            WINDSIZEX(Rect),        // initial x size
                            WINDSIZEY(Rect),        // initial y size
                            NULL,                   // parent window handle
                            NULL,                   // window menu handle
                            hInstance,              // program instance handle
                            NULL                    // creation parameter

                        );

    ShowWindow( hwnd, nCmdShow );
    UpdateWindow( hwnd );

    hdlgRotate = CreateDialog(hInstance, "RotateDlg", hwnd, DlgProcRotate);

    SetTimer(hwnd, 1, guiTimerTick, NULL);

    while ( GetMessage( &msg, NULL, 0, 0 ))
    {
        if ( (hdlgRotate == 0) || !IsDialogMessage(hdlgRotate, &msg) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
    }

    return( msg.wParam );
}


long
WndProc (   HWND hWnd,
            UINT message,
            WPARAM wParam,
            LPARAM lParam
        )
{
    HDC hDc;
    PAINTSTRUCT ps;
    int iWidth, iHeight;

    switch ( message )
    {
        case WM_PAINT:
            hDc = BeginPaint( hWnd, &ps );

            if (ghrc == (HGLRC) 0)
                ghrc = hrcInitGL(hWnd, hDc);

            DoGlStuff( hWnd, hDc );

            EndPaint( hWnd, &ps );
            return(0);

        case WM_SIZE:
            iWidth = LOWORD(lParam);
            iHeight = HIWORD(lParam);

            #if 0
            SetWindowPos (hWnd,                         // hwnd
                          HWND_TOP,                     // z-order
                          0, 0,                         // position
                          max(iWidth, iHeight),         // new width
                          max(iWidth, iHeight),         // new height
                          SWP_NOMOVE);   // keep old postion
            #endif

            hDc = GetDC(hWnd);
            vResizeDoubleBuffer(hWnd, hDc);
            ReleaseDC(hWnd, hDc);
            return(0);

        case WM_CHAR:
            switch(wParam)
            {
                case 'd':
                case 'D':
                    OffsetX += 0.2;
                    break;

                case 'a':
                case 'A':
                    OffsetX -= 0.2;
                    break;

            // !!! Note: currently the coordinate system is upside down, so
            // !!!       so up and down are reversed.

                case 's':
                case 'S':
                    OffsetY += 0.2;
                    break;

                case 'w':
                case 'W':
                    OffsetY -= 0.2;
                    break;

                case 'q':
                case 'Q':
                    OffsetZ += 0.2;
                    break;

                case 'e':
                case 'E':
                    OffsetZ -= 0.2;
                    break;

                case ',':
                case '<':
                    guiTimerTick = guiTimerTick << 1;
                    guiTimerTick = min(0x40000000, guiTimerTick);

                    KillTimer(hWnd, 1);
                    SetTimer(hWnd, 1, guiTimerTick, NULL);
                    break;

                case '.':
                case '>':
                    guiTimerTick = guiTimerTick >> 1;
                    guiTimerTick = max(1, guiTimerTick);

                    KillTimer(hWnd, 1);
                    SetTimer(hWnd, 1, guiTimerTick, NULL);
                    break;

                default:
                    break;
            }

            return 0;

        case WM_TIMER:
            AngleX += DeltaAngle[0];
            AngleY += DeltaAngle[1];
            AngleZ += DeltaAngle[2];

            hDc = GetDC(hWnd);

            if (ghrc == (HGLRC) 0)
                ghrc = hrcInitGL(hWnd, hDc);

            DoGlStuff( hWnd, hDc );

            ReleaseDC(hWnd, hDc);

            return 0;

        case WM_DESTROY:
            vCleanupGL(ghrc);
            KillTimer(hWnd, 1);
            PostQuitMessage( 0 );
            DestroyWindow(hdlgRotate);
            return( 0 );

    }
    return( DefWindowProc( hWnd, message, wParam, lParam ) );
}


BOOL
DlgProcRotate(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    HWND hwndCtrl;
    int  iCtrl, iIndex;
    long lPos, lVal;
    static char ach[80];

    switch(msg)
    {
        case WM_INITDIALOG:
            for (iCtrl = 10; iCtrl < 13; iCtrl += 1)
            {
                hwndCtrl = GetDlgItem(hwnd, iCtrl);
                SetScrollRange(hwndCtrl, SB_CTL, 0, 90, FALSE);
                SetScrollPos(hwndCtrl, SB_CTL, 45, FALSE);
            }

            return TRUE;

        case WM_VSCROLL:
            hwndCtrl = lParam;
            iCtrl = GetWindowLong(hwndCtrl, GWL_ID);
            iIndex = iCtrl - 10;

            lVal = (long) DeltaAngle[iIndex];

            switch(LOWORD(wParam))
            {
                case SB_BOTTOM:
                    lVal = -45;
                    lPos = 90;
                    DeltaAngle[iIndex] = -45.0;
                    break;

                case SB_TOP:
                    lVal = 45;
                    lPos = 0;
                    DeltaAngle[iIndex] = 45.0;
                    break;

                case SB_PAGEDOWN:
                    lVal -= 4;
                case SB_LINEDOWN:
                    lVal = max(-45, lVal - 1);
                    lPos = 45 - lVal;
                    DeltaAngle[iIndex] = (float) lVal;
                    break;

                case SB_PAGEUP:
                    lVal += 4;
                case SB_LINEUP:
                    lVal = min(45, lVal + 1);
                    lPos = 45 - lVal;
                    DeltaAngle[iIndex] = (float) lVal;
                    break;

                case SB_THUMBPOSITION:
                case SB_THUMBTRACK:
                    lPos = (long) HIWORD(wParam);
                    lVal = 45 - lPos;               // invert and unbias
                    DeltaAngle[iIndex] = (float) lVal;
                    break;

                default:
                    return FALSE;
            }

        // Update scroll bar.

            SetScrollPos(hwndCtrl, SB_CTL, lPos, TRUE);

        // Update the static text with new value.

            wsprintf(ach, "%ld", lVal);
            SetDlgItemText(hwnd, iCtrl + 10, ach);

            return TRUE;

        default:
            break;
    }

    return FALSE;
}


void vResizeDoubleBuffer(HWND hwnd, HDC hdc)
{
    RECT Rect;

    /* Get the size of the client area */

    GetClientRect( hwnd, &Rect );

    if (ghbmBackBuffer != (HBITMAP) 0)
        DeleteObject(SelectObject(ghdcMem, ghbmOld));

    ghbmBackBuffer = CreateCompatibleBitmap(hdc, WINDSIZEX(Rect), WINDSIZEY(Rect));
    ghbmOld = SelectObject(ghdcMem, ghbmBackBuffer);

    //!!! [GilmanW] OpenGL hack !!!
    //!!!
    //!!! For some reason we need to prepare the memory DC.  GL
    //!!! drawing seems limited to the area drawn to by GDI calls.
    //!!! By BitBlt'ing the entire memory DC, the whole thing is
    //!!! is available to GL.
    //!!!
    //!!! There must be something we need to update on the server
    //!!! side so that this is not necessary.
    BitBlt(ghdcMem, 0, 0, WINDSIZEX(Rect), WINDSIZEY(Rect), NULL, 0, 0, BLACKNESS);
}


HGLRC hrcInitGL(HWND hwnd, HDC hdc)
{
    HGLRC hrc;

#if !USE_COLOR_INDEX
    static GLfloat ClearColor[] =   {
                                        (GLfloat)0.0,   // Red
                                        (GLfloat)0.0,   // Green
                                        (GLfloat)0.0,   // Blue
                                        (GLfloat)1.0    // Alpha

                                    };

    static GLfloat Cyan[] =     {
                                    (GLfloat)0.0,   // Read
                                    (GLfloat)0.666, // Green
                                    (GLfloat)0.666, // Blue
                                    (GLfloat)1.0    // Alpha
                                };

    static GLfloat Yellow[] =   {
                                    (GLfloat)0.666, // Red
                                    (GLfloat)0.666, // Green
                                    (GLfloat)0.0,   // Blue
                                    (GLfloat)1.0    // Alpha
                                };

    static GLfloat Magenta[] =  {
                                    (GLfloat)0.666, // Red
                                    (GLfloat)0.0,   // Green
                                    (GLfloat)0.666, // Blue
                                    (GLfloat)1.0    // Alpha
                                };

    static GLfloat Red[] =      {
                                    (GLfloat)1.0,   // Red
                                    (GLfloat)0.0,   // Green
                                    (GLfloat)0.0,   // Blue
                                    (GLfloat)1.0    // Alpha
                                };

    static GLfloat Green[] =    {
                                    (GLfloat)0.0,   // Red
                                    (GLfloat)1.0,   // Green
                                    (GLfloat)0.0,   // Blue
                                    (GLfloat)1.0    // Alpha
                                };

    static GLfloat Blue[] =     {
                                    (GLfloat)0.0,   // Red
                                    (GLfloat)0.0,   // Green
                                    (GLfloat)1.0,   // Blue
                                    (GLfloat)1.0    // Alpha
                                };
#endif

    /* Create a Rendering Context */

#if DBLBUFFER
    ghdcMem = CreateCompatibleDC(hdc);
    SelectObject(ghdcMem, GetStockObject(DEFAULT_PALETTE));

    vResizeDoubleBuffer(hwnd, hdc);

    hrc = wglCreateContext( ghdcMem );
#else
    hrc = wglCreateContext( hdc );
#endif

    /* Make it Current */

#if DBLBUFFER
    wglMakeCurrent( ghdcMem, hrc );
#else
    wglMakeCurrent( hdc, hrc );
#endif

    // !!! Note: currently the coordinate system is upside down, so we
    // !!!       need to reverse the default "front face" definition.

    glFrontFace(GL_CW);

    /* Set the clear color */

#if USE_COLOR_INDEX
    glClearIndex(BLACK_INDEX);
#else
    glClearColor( ClearColor[0], ClearColor[1], ClearColor[2], ClearColor[3] );
#endif

    /* Turn off dithering */

    glDisable(GL_DITHER);

    /* Turn on z-buffer */

#if ZBUFFER
    glEnable(GL_DEPTH_TEST);
#else
    glDisable(GL_DEPTH_TEST);
#endif

    /* Turn on backface culling */

    glEnable(GL_CULL_FACE);

    /* Generate a display list for a cube */

    DListCube = glGenLists(1);

    glNewList(DListCube, GL_COMPILE);
        glBegin(GL_QUADS);

#if USE_COLOR_INDEX
            glIndexi(BLUE_INDEX);
#else
            glColor4fv( Blue );
#endif
            glVertex3f( (GLfloat) 0.7, (GLfloat) 0.7, (GLfloat) 0.7);
            glVertex3f( (GLfloat) 0.7, (GLfloat) -0.7, (GLfloat) 0.7);
            glVertex3f( (GLfloat) 0.7, (GLfloat) -0.7, (GLfloat) -0.7);
            glVertex3f( (GLfloat) 0.7, (GLfloat) 0.7, (GLfloat) -0.7);

#if USE_COLOR_INDEX
            glIndexi(GREEN_INDEX);
#else
            glColor4fv( Green );
#endif
            glVertex3f( (GLfloat) 0.7, (GLfloat) 0.7, (GLfloat) -0.7);
            glVertex3f( (GLfloat) 0.7, (GLfloat) -0.7, (GLfloat) -0.7);
            glVertex3f( (GLfloat) -0.7, (GLfloat) -0.7, (GLfloat) -0.7);
            glVertex3f( (GLfloat) -0.7, (GLfloat) 0.7, (GLfloat) -0.7);

#if USE_COLOR_INDEX
            glIndexi(RED_INDEX);
#else
            glColor4fv( Red );
#endif
            glVertex3f( (GLfloat) -0.7, (GLfloat) 0.7, (GLfloat) -0.7);
            glVertex3f( (GLfloat) -0.7, (GLfloat) -0.7, (GLfloat) -0.7);
            glVertex3f( (GLfloat) -0.7, (GLfloat) -0.7, (GLfloat) 0.7);
            glVertex3f( (GLfloat) -0.7, (GLfloat) 0.7, (GLfloat) 0.7);

#if USE_COLOR_INDEX
            glIndexi(CYAN_INDEX);
#else
            glColor4fv( Cyan );
#endif
            glVertex3f( (GLfloat) -0.7, (GLfloat) 0.7, (GLfloat) 0.7);
            glVertex3f( (GLfloat) -0.7, (GLfloat) -0.7, (GLfloat) 0.7);
            glVertex3f( (GLfloat) 0.7, (GLfloat) -0.7, (GLfloat) 0.7);
            glVertex3f( (GLfloat) 0.7, (GLfloat) 0.7, (GLfloat) 0.7);

#if USE_COLOR_INDEX
            glIndexi(YELLOW_INDEX);
#else
            glColor4fv( Yellow );
#endif
            glVertex3f( (GLfloat) 0.7, (GLfloat) 0.7, (GLfloat) 0.7);
            glVertex3f( (GLfloat) 0.7, (GLfloat) 0.7, (GLfloat) -0.7);
            glVertex3f( (GLfloat) -0.7, (GLfloat) 0.7, (GLfloat) -0.7);
            glVertex3f( (GLfloat) -0.7, (GLfloat) 0.7, (GLfloat) 0.7);

#if USE_COLOR_INDEX
            glIndexi(MAGENTA_INDEX);
#else
            glColor4fv( Magenta );
#endif
            glVertex3f( (GLfloat) 0.7, (GLfloat) -0.7, (GLfloat) 0.7);
            glVertex3f( (GLfloat) -0.7, (GLfloat) -0.7, (GLfloat) 0.7);
            glVertex3f( (GLfloat) -0.7, (GLfloat) -0.7, (GLfloat) -0.7);
            glVertex3f( (GLfloat) 0.7, (GLfloat) -0.7, (GLfloat) -0.7);

        glEnd();
    glEndList();

    return hrc;
}

void
vCleanupGL(hrc)
{
    /*  Destroy our context */

    wglDeleteContext( hrc );

#if DBLBUFFER
    DeleteObject(SelectObject(ghdcMem, ghbmOld));
    DeleteDC(ghdcMem);
#endif
}

void
DoGlStuff( HWND hWnd, HDC hDc )
{
    RECT Rect;
    HGLRC hRc;

    /* Get the size of the client area */

    GetClientRect( hWnd, &Rect );

    /* Set up the projection matrix */

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-1.0, 1.0, -1.0, 1.0, 1.5, 20.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glTranslatef(OffsetX, OffsetY, OffsetZ);

    glRotatef(AngleX, 1.0, 0.0, 0.0);
    glRotatef(AngleY, 0.0, 1.0, 0.0);
    glRotatef(AngleZ, 0.0, 0.0, 1.0);

    glViewport(0, 0, WINDSIZEX(Rect), WINDSIZEY(Rect));

    /* Clear the color buffer */

#if ZBUFFER
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
#else
    glClear( GL_COLOR_BUFFER_BIT );
#endif

    /* Draw the cube */

    /* Draw the cube */

    glCallList(DListCube);
    glFlush();

#if DBLBUFFER
    BitBlt(hDc, 0, 0, Rect.right-Rect.left, Rect.bottom-Rect.top, ghdcMem, 0, 0, SRCCOPY);
    GdiFlush();
#endif

}