// Geometric Tools, Inc.
// http://www.geometrictools.com
// Copyright (c) 1998-2005.  All Rights Reserved
//
// The Wild Magic Library (WM3) source code is supplied under the terms of
// the license agreement
//     http://www.geometrictools.com/License/WildMagic3License.pdf
// and may not be copied or disclosed except in accordance with the terms
// of that agreement.

#include "Wm3WindowApplication.h"
#include "Wm3GlxRenderer.h"
using namespace Wm3;

const int WindowApplication::KEY_ESCAPE             = 0x1B;
const int WindowApplication::KEY_HOME               = 0x95;
const int WindowApplication::KEY_LEFT_ARROW         = 0x96;
const int WindowApplication::KEY_UP_ARROW           = 0x97;
const int WindowApplication::KEY_RIGHT_ARROW        = 0x98;
const int WindowApplication::KEY_DOWN_ARROW         = 0x99;
const int WindowApplication::KEY_PAGE_UP            = 0x9A;
const int WindowApplication::KEY_PAGE_DOWN          = 0x9B;
const int WindowApplication::KEY_END                = 0x9C;
const int WindowApplication::KEY_INSERT             = 0x9E;
const int WindowApplication::KEY_DELETE             = 0x9F;
const int WindowApplication::KEY_F1                 = 0xBE;
const int WindowApplication::KEY_F2                 = 0xBF;
const int WindowApplication::KEY_F3                 = 0xC0;
const int WindowApplication::KEY_F4                 = 0xC1;
const int WindowApplication::KEY_F5                 = 0xC2;
const int WindowApplication::KEY_F6                 = 0xC3;
const int WindowApplication::KEY_F7                 = 0xC4;
const int WindowApplication::KEY_F8                 = 0xC5;
const int WindowApplication::KEY_F9                 = 0xC6;
const int WindowApplication::KEY_F10                = 0xC7;
const int WindowApplication::KEY_F11                = 0xC8;
const int WindowApplication::KEY_F12                = 0xC9;

const int WindowApplication::KEY_SHIFT              = 0x0001;
const int WindowApplication::KEY_CONTROL            = 0x0004;
const int WindowApplication::KEY_ALT                = 0x0008;
const int WindowApplication::KEY_COMMAND            = 0x0040;

const int WindowApplication::MOUSE_LEFT_BUTTON      = 0x0001;
const int WindowApplication::MOUSE_MIDDLE_BUTTON    = 0x0002;
const int WindowApplication::MOUSE_RIGHT_BUTTON     = 0x0003;
const int WindowApplication::MOUSE_DOWN             = 0x0004;
const int WindowApplication::MOUSE_UP               = 0x0005;

const int WindowApplication::MODIFIER_CONTROL       = 0x0004;
const int WindowApplication::MODIFIER_LBUTTON       = 0x0001;
const int WindowApplication::MODIFIER_MBUTTON       = 0x0002;
const int WindowApplication::MODIFIER_RBUTTON       = 0x0003;
const int WindowApplication::MODIFIER_SHIFT         = 0x0001;

// Indices into the extra data storage for button up-down state and for key
// key modifier up-down state.
#define GLXAPP_BUTTONDOWN  0
#define GLXAPP_SHIFTDOWN   GLXAPP_BUTTONDOWN + 8*sizeof(bool)
#define GLXAPP_CONTROLDOWN GLXAPP_SHIFTDOWN + sizeof(bool)
#define GLXAPP_ALTDOWN     GLXAPP_CONTROLDOWN + sizeof(bool)
#define GLXAPP_COMMANDDOWN GLXAPP_ALTDOWN + sizeof(bool)

//----------------------------------------------------------------------------
void WindowApplication::SetMousePosition (int iX, int iY)
{
    WindowApplication* pkTheApp = (WindowApplication*)TheApplication;
    GlxRenderer* pkRenderer = (GlxRenderer*)pkTheApp->GetRenderer();
    Display* pkDisplay = pkRenderer->GetDisplay();
    Window ulWindow = pkRenderer->GetWindow();

    XWarpPointer(pkDisplay,0,ulWindow,0,0,0,0,iX,iY);
    XFlush(pkDisplay);
}
//----------------------------------------------------------------------------
void WindowApplication::GetMousePosition (int& riX, int& riY) const
{
    WindowApplication* pkTheApp = (WindowApplication*)TheApplication;
    GlxRenderer* pkRenderer = (GlxRenderer*)pkTheApp->GetRenderer();
    Display* pkDisplay = pkRenderer->GetDisplay();
    Window ulWindow = pkRenderer->GetWindow();

    Window ulRootWindow, ulChildWindow;
    int iRootX, iRootY;
    unsigned int uiModifier;
    XQueryPointer(pkDisplay,ulWindow,&ulRootWindow,&ulChildWindow,&iRootX,
        &iRootY,&riX,&riY,&uiModifier);
}
//----------------------------------------------------------------------------
int WindowApplication::GetStringWidth (const char* acText) const
{
    assert( acText && strlen(acText) > 0 );
    return 8*(int)strlen(acText);
}
//----------------------------------------------------------------------------
int WindowApplication::GetCharWidth (const char) const
{
    return 8;
}
//----------------------------------------------------------------------------
int WindowApplication::GetFontHeight () const
{
    return 13;
}
//----------------------------------------------------------------------------
int WindowApplication::Main (int iQuantity, char** apcArgument)
{
    WindowApplication* pkTheApp = (WindowApplication*)TheApplication;
    pkTheApp->KEY_TERMINATE = WindowApplication::KEY_ESCAPE;

    // Allow work to be done before the window is created.
    if ( !pkTheApp->OnPrecreate() )
        return -1;

    // Connect to the X server.
    const char* acDisplayName = 0;
    Display* pkDisplay = XOpenDisplay(acDisplayName);
    if ( !pkDisplay )
        return -2;

    // Make sure the X server has OpenGL GLX extensions.
    int iErrorBase, iEventBase;
    Bool bSuccess = glXQueryExtension(pkDisplay,&iErrorBase,&iEventBase);
    assert( bSuccess );
    if ( !bSuccess )
        return -3;

    // Partial construction of a GLX renderer.  The window for the renderer
    // is not yet constructed.  When it is, the window identifier is supplied
    // to the renderer to complete its construction.
    GlxRenderer* pkGlxRenderer = new GlxRenderer(pkDisplay,m_kBufferParams,
        pkTheApp->GetWidth(),pkTheApp->GetHeight());
    if ( !pkGlxRenderer->IsConstructed() )
        return -4;

    pkTheApp->SetRenderer(pkGlxRenderer);

    // Create an X Window with the visual information created by the
    // renderer constructor.
    XVisualInfo* pkVisual = pkGlxRenderer->GetVisual();
    
    // The visual information might not be the default, so create an X
    // colormap to use.
    Window ulRootWindow = RootWindow(pkDisplay,pkVisual->screen);
    Colormap kCMap = XCreateColormap(pkDisplay,ulRootWindow,
        pkVisual->visual,AllocNone);

    // Set the event mask to include exposure (paint), button presses (mouse),
    // and key presses (keyboard).
    XSetWindowAttributes kWindowAttributes;
    kWindowAttributes.colormap = kCMap;
    kWindowAttributes.border_pixel = 0;
    kWindowAttributes.event_mask =
        ButtonPressMask |
        ButtonReleaseMask |
        PointerMotionMask |
        Button1MotionMask |
        Button2MotionMask |
        Button3MotionMask |
        KeyPressMask |
        KeyReleaseMask |
        ExposureMask |
        StructureNotifyMask;

    unsigned int uiWidth = (unsigned int)pkTheApp->GetWidth();
    unsigned int uiHeight = (unsigned int)pkTheApp->GetHeight();
    unsigned int uiBorderWidth = 0;
    unsigned long ulValueMask = CWBorderPixel | CWColormap | CWEventMask;
    Window ulWindow = XCreateWindow(pkDisplay,ulRootWindow,
        pkTheApp->GetXPosition(),pkTheApp->GetYPosition(),uiWidth,uiHeight,
        uiBorderWidth,pkVisual->depth,InputOutput,pkVisual->visual,
        ulValueMask,&kWindowAttributes);

    const char* acIconName = pkTheApp->GetWindowTitle();
    Pixmap kIconPixmap = None;
    XSizeHints* pkHints = 0;
    XSetStandardProperties(pkDisplay,ulWindow,pkTheApp->GetWindowTitle(),
        acIconName,kIconPixmap,apcArgument,iQuantity,pkHints);

    // Finish the construction of the renderer.
    pkGlxRenderer->FinishConstruction(ulWindow);
    if ( !pkGlxRenderer->IsConstructed() )
        return -5;

    if ( pkTheApp->OnInitialize() )
    {
        // display the window
        XMapWindow(pkDisplay,ulWindow);

        // start the message pump
        bool bDoPump = true;
        while ( bDoPump )
        {
            if ( !XPending(pkDisplay) )
            {
                pkTheApp->OnIdle();
                continue;
            }

            XEvent kEvent;
            XNextEvent(pkDisplay,&kEvent);
            int iIndex;
            bool bState;

            if ( kEvent.type == ButtonPress || kEvent.type == ButtonRelease )
            {
                pkTheApp->OnMouseClick(kEvent.xbutton.button,
                    kEvent.xbutton.type,kEvent.xbutton.x,kEvent.xbutton.y,
                    kEvent.xbutton.state);

                if ( kEvent.type == ButtonPress )
                {
                    iIndex = GLXAPP_BUTTONDOWN + kEvent.xbutton.button;
                    bState = true;
                    pkTheApp->SetExtraData(iIndex,sizeof(bool),&bState);
                }
                else
                {
                    iIndex = GLXAPP_BUTTONDOWN + kEvent.xbutton.button;
                    bState = false;
                    pkTheApp->SetExtraData(iIndex,sizeof(bool),&bState);
                }

                continue;
            }

            if ( kEvent.type == MotionNotify )
            {
                int iButton = 0;

                iIndex = GLXAPP_BUTTONDOWN + 1;
                pkTheApp->GetExtraData(iIndex,sizeof(bool),&bState);
                if ( bState )
                {
                    iButton = WindowApplication::MOUSE_LEFT_BUTTON;
                }
                else
                {
                    iIndex = GLXAPP_BUTTONDOWN + 2;
                    pkTheApp->GetExtraData(iIndex,sizeof(bool),&bState);
                    if ( bState )
                    {
                        iButton = WindowApplication::MOUSE_MIDDLE_BUTTON;
                    }
                    else
                    {
                        iIndex = GLXAPP_BUTTONDOWN + 3;
                        pkTheApp->GetExtraData(iIndex,sizeof(bool),&bState);
                        if ( bState )
                        {
                            iButton = WindowApplication::MOUSE_RIGHT_BUTTON;
                        }
                    }
                }

                if ( iButton > 0 )
                {
                    pkTheApp->OnMotion(iButton,kEvent.xmotion.x,
                        kEvent.xmotion.y,kEvent.xmotion.state);
                }
                else
                {
                    pkTheApp->OnPassiveMotion(kEvent.xmotion.x,
                        kEvent.xmotion.y);
                }
                continue;
            }

            if ( kEvent.type == KeyPress || kEvent.type == KeyRelease )
            {
                KeySym kKeySym = XKeycodeToKeysym(pkDisplay,
                    kEvent.xkey.keycode,0);

                int iKey = (kKeySym & 0x00FF);

                // quit application if the KEY_TERMINATE key is pressed
                if ( iKey == pkTheApp->KEY_TERMINATE )
                {
                    XDestroyWindow(pkDisplay,ulWindow);
                    bDoPump = false;
                    continue;
                }

                // Adjust for special keys that exist on the key pad and on
                // the number pad.
                if ( (kKeySym & 0xFF00) != 0 )
                {
                    if ( 0x50 <= iKey && iKey <= 0x57 )
                    {
                        // keypad Home, {L,U,R,D}Arrow, Pg{Up,Dn}, End
                        iKey += 0x45;
                    }
                    else if ( iKey == 0x63 )
                    {
                        // keypad Insert
                        iKey = 0x9e;
                    }
                    else if ( iKey == 0xFF )
                    {
                        // keypad Delete
                        iKey = 0x9f;
                    }
                }

                if ( (WindowApplication::KEY_HOME <= iKey
                &&   iKey <= WindowApplication::KEY_END)
                ||   (WindowApplication::KEY_F1 <= iKey
                &&   iKey <= WindowApplication::KEY_F12) )
                {
                    if ( kEvent.type == KeyPress )
                    {
                        pkTheApp->OnSpecialKeyDown(iKey,kEvent.xbutton.x,
                            kEvent.xbutton.y);
                    }
                    else
                    {
                        pkTheApp->OnSpecialKeyUp(iKey,kEvent.xbutton.x,
                            kEvent.xbutton.y);
                    }
                }
                else
                {
                    // Get key-modifier state.
                    bState = ( kEvent.xbutton.state &
                        WindowApplication::KEY_SHIFT ) != 0;
                    pkTheApp->SetExtraData(GLXAPP_SHIFTDOWN,sizeof(bool),
                        &bState);

                    // Adjust for shift state.
                    unsigned char ucKey = (unsigned char)iKey;
                    if ( bState && 'a' <= ucKey && ucKey <= 'z' )
                        ucKey = (unsigned char)(iKey - 32);

                    bState = ( kEvent.xbutton.state &
                        WindowApplication::KEY_CONTROL ) != 0;
                    pkTheApp->SetExtraData(GLXAPP_CONTROLDOWN,sizeof(bool),
                        &bState);
                    
                    bState = ( kEvent.xbutton.state &
                        WindowApplication::KEY_ALT ) != 0;
                    pkTheApp->SetExtraData(GLXAPP_ALTDOWN,sizeof(bool),
                        &bState);

                    bState = ( kEvent.xbutton.state &
                        WindowApplication::KEY_COMMAND ) != 0;
                    pkTheApp->SetExtraData(GLXAPP_COMMANDDOWN,sizeof(bool),
                        &bState);

                    if ( kEvent.type == KeyPress )
                    {
                        pkTheApp->OnKeyDown(ucKey,kEvent.xbutton.x,
                            kEvent.xbutton.y);
                    }
                    else
                    {
                        pkTheApp->OnKeyUp(ucKey,kEvent.xbutton.x,
                            kEvent.xbutton.y);
                    }
                }
                continue;
            }

            if ( kEvent.type == Expose )
            {
                pkTheApp->OnDisplay();
                continue;
            }

            if ( kEvent.type == ConfigureNotify )
            {
                pkTheApp->OnMove(kEvent.xconfigure.x,kEvent.xconfigure.y);
                pkTheApp->OnResize(kEvent.xconfigure.width,
                    kEvent.xconfigure.height);
                continue;
            }
        }
    }

    pkTheApp->OnTerminate();
    XCloseDisplay(pkDisplay);
    return 0;
}
//----------------------------------------------------------------------------

