/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
// plInputDevice.cpp
//#include "STRING"

#include "hsConfig.h"
#include "hsWindows.h"

#include "plInputDevice.h"
#include "plInputManager.h"
#include "plAvatarInputInterface.h"
#include "plMessage/plInputEventMsg.h"
#include "pnMessage/plTimeMsg.h"
#include "hsUtils.h"
#include "plgDispatch.h"

#include "plPipeline/plPlates.h"
#include "plPipeline/plDebugText.h"
#include "plGImage/plMipmap.h"

#include "hsWindows.h"
#include "plPipeline.h"

// The resolution that uses the base size of the cursor. 
// All other resolutions will scale the cursor size to keep the same physical size.
#define BASE_WIDTH 1024
#define BASE_HEIGHT 768

plKeyboardDevice* plKeyboardDevice::fInstance = nil;
bool plKeyboardDevice::fKeyboardState[256];
hsBool plKeyboardDevice::fIgnoreCapsLock = false;

plKeyboardDevice::plKeyboardDevice() :
fShiftKeyDown(false),
fCapsLockKeyDown(false),
fAltKeyDown(false),
fCtrlKeyDown(false),
fCapsLockLock(false),
fControlMode(STANDARD_MODE)
{
    fInstance = this;
    InitKeyboardState();
}

plKeyboardDevice::~plKeyboardDevice()
{
}

void plKeyboardDevice::InitKeyboardState()
{
    static bool initialized = false;
    if (!initialized)
    {
        for (unsigned int i = 0; i < 256; ++i)
            plKeyboardDevice::fKeyboardState[i] = false;
        initialized = true;
    }
}

void plKeyboardDevice::ReleaseAllKeys()
{
    // send a key-up message for all "normal" keys
    for (unsigned int i = 0; i < 256; ++i)
    {
        if ((i == KEY_SHIFT) || (i == KEY_CTRL) || (i == KEY_CAPSLOCK))
            continue; // these are handled slightly differently

        if (fKeyboardState[i])
        {
            fKeyboardState[i] = false;

            // fake a key-up command
            plKeyEventMsg* pMsg = TRACKED_NEW plKeyEventMsg;
            pMsg->SetKeyCode( (plKeyDef)i );
            pMsg->SetKeyDown( false );
            pMsg->SetShiftKeyDown( fShiftKeyDown );
            pMsg->SetCtrlKeyDown( fCtrlKeyDown );
            pMsg->SetCapsLockKeyDown( fCapsLockLock );
            pMsg->SetRepeat( false );
            plgDispatch::MsgSend( pMsg );
        }
    }

    // send key messages for shift and ctrl if necessary because the keys above need to have
    // the proper states of the shift and ctrl keys sent with their messages, and our internal
    // flags for these keys need to be cleared. We don't send a key-up message for caps lock
    // because it doesn't really operate like every other key on the keyboard
    if (fKeyboardState[KEY_SHIFT])
    {
        fKeyboardState[KEY_SHIFT] = false;
        fShiftKeyDown = false;

        plKeyEventMsg* pMsg = TRACKED_NEW plKeyEventMsg;
        pMsg->SetKeyCode( KEY_SHIFT );
        pMsg->SetKeyDown( false );
        pMsg->SetShiftKeyDown( false );
        pMsg->SetCtrlKeyDown( false );
        pMsg->SetCapsLockKeyDown( fCapsLockLock );
        pMsg->SetRepeat( false );
        plgDispatch::MsgSend( pMsg );
    }
    if (fKeyboardState[KEY_CTRL])
    {
        fKeyboardState[KEY_CTRL] = false;
        fCtrlKeyDown = false;

        plKeyEventMsg* pMsg = TRACKED_NEW plKeyEventMsg;
        pMsg->SetKeyCode( KEY_CTRL );
        pMsg->SetKeyDown( false );
        pMsg->SetShiftKeyDown( false );
        pMsg->SetCtrlKeyDown( false );
        pMsg->SetCapsLockKeyDown( fCapsLockLock );
        pMsg->SetRepeat( false );
        plgDispatch::MsgSend( pMsg );
    }
}

hsBool plKeyboardDevice::IsCapsLockKeyOn()
{
    return fCapsLockLock;
}

void plKeyboardDevice::Shutdown()
{
}

void plKeyboardDevice::HandleKeyEvent(plOSMsg message, plKeyDef key, bool bKeyDown, hsBool bKeyRepeat, wchar_t c)
{
    // update the internal keyboard state
    unsigned int keyCode = (unsigned int)key;
    if ((key >= 0) && (key < 256))
        fKeyboardState[key] = bKeyDown;

    if (key == KEY_SHIFT)
    {
        fShiftKeyDown = bKeyDown;
//      return;
    }
    if (key == KEY_CTRL)
    {
        fCtrlKeyDown = bKeyDown;
//      return;
    }
    if (key == KEY_CAPSLOCK)
    {
        // Keyboards toggle the light on key-down, so I'm going with that.
        if (bKeyDown && !bKeyRepeat)
        {
            fCapsLockLock = !fCapsLockLock;
            plAvatarInputInterface::GetInstance()->ForceAlwaysRun(fCapsLockLock);
        }
    }

    // send a key event...
    plKeyEventMsg* pMsg = TRACKED_NEW plKeyEventMsg;
    pMsg->SetKeyChar( c );
    pMsg->SetKeyCode( key );
    pMsg->SetKeyDown( bKeyDown );
    pMsg->SetShiftKeyDown( fShiftKeyDown );
    pMsg->SetCtrlKeyDown( fCtrlKeyDown );
    pMsg->SetCapsLockKeyDown( fCapsLockLock );
    pMsg->SetRepeat(bKeyRepeat);
    plgDispatch::MsgSend( pMsg );
}

void plKeyboardDevice::HandleWindowActivate(bool bActive, HWND hWnd)
{
    if (bActive)
    {
        fCtrlKeyDown = false;
    }
    else
    {
        ReleaseAllKeys(); // send key-up events for everything since we're losing focus
    }

}


//
//
//
// plMouseDevice
//
//

bool plMouseDevice::bMsgAlways = true;
bool plMouseDevice::bCursorHidden = false;
bool plMouseDevice::bCursorOverride = false;
bool plMouseDevice::bInverted = false;
hsScalar plMouseDevice::fWidth = BASE_WIDTH;
hsScalar plMouseDevice::fHeight = BASE_HEIGHT;
plMouseDevice* plMouseDevice::fInstance = 0;

plMouseDevice::plMouseDevice()
{
    fXPos = 0;
    fYPos = 0;
    fCursorID = CURSOR_UP;
    fButtonState = 0;
    fOpacity = 1.f;

    fCursor = nil;
    CreateCursor( fCursorID );
    plMouseDevice::fInstance = this;
    fXMsg = nil;
    fYMsg = nil;
    fB2Msg = nil;

    fLeftBMsg[0] = nil;
    fLeftBMsg[1] = nil;
    fRightBMsg[0] = nil;
    fRightBMsg[1] = nil;
    fMiddleBMsg[0] = nil;
    fMiddleBMsg[1] = nil;
    
}

plMouseDevice::~plMouseDevice()
{
    plPlateManager::Instance().DestroyPlate( fCursor );
    fCursor = nil;
    plMouseDevice::fInstance = nil;
}
void plMouseDevice::SetDisplayResolution(hsScalar Width, hsScalar Height)
{
    fWidth = Width;
    fHeight = Height;
    IUpdateCursorSize();
}

void    plMouseDevice::CreateCursor( char* cursor )
{
    if( fCursor == nil )
    {
        plPlateManager::Instance().CreatePlate( &fCursor );
        fCursor->CreateFromResource(cursor);
    }
    else
    {
        fCursor->ReloadFromResource(cursor);
    }
    fCursor->SetPosition( 0, 0, 0 );
    IUpdateCursorSize();

    fCursor->SetVisible( true );
    fCursor->SetOpacity( fOpacity );
}

void plMouseDevice::IUpdateCursorSize()
{
    if(fCursor)
    {
        // set the size of the cursor based on resolution.
        fCursor->SetSize( 2*fCursor->GetMipmap()->GetWidth()/fWidth, 2*fCursor->GetMipmap()->GetHeight()/fHeight );
    }
}

void plMouseDevice::AddNameToCursor(const char* name)
{
    if (fInstance && name)
    {
        plDebugText     &txt = plDebugText::Instance();
        txt.DrawString(fInstance->fWXPos + 12 ,fInstance->fWYPos - 7,name);
    }
}
void plMouseDevice::AddCCRToCursor()
{
    if (fInstance)
    {
        plDebugText     &txt = plDebugText::Instance();
        txt.DrawString(fInstance->fWXPos + 12, fInstance->fWYPos - 17, "CCR");
    }
}
void plMouseDevice::AddIDNumToCursor(UInt32 idNum)
{
    if (fInstance && idNum)
    {
        plDebugText     &txt = plDebugText::Instance();
        char str[256];
        sprintf(str, "%d",idNum);
        txt.DrawString(fInstance->fWXPos + 12 ,fInstance->fWYPos + 3,str);
    }
}
        

void plMouseDevice::SetCursorX(hsScalar x)
{
    /// Set the cursor position
    if( fCursor == nil  && !plMouseDevice::bCursorHidden)
        CreateCursor( fCursorID );
    
    if (fCursor)
        fCursor->SetPosition( ( x * 2.0f ) - 1.0f, 
                          ( fYPos * 2.0f ) - 1.0f );

//  plDebugText     &txt = plDebugText::Instance();
//  txt.DrawString(fWXPos + 20,fWYPos - 5,"test");

}
void plMouseDevice::SetCursorY(hsScalar y)
{
    /// Set the cursor position
    if( fCursor == nil && !plMouseDevice::bCursorHidden)
        CreateCursor( fCursorID );
    
    if (fCursor)
        fCursor->SetPosition( ( fXPos * 2.0f ) - 1.0f, 
                          ( y * 2.0f ) - 1.0f );

//  plDebugText     &txt = plDebugText::Instance();
//  txt.DrawString(fWXPos + 20,fWYPos - 10,"test");

}


void plMouseDevice::HideCursor(hsBool override)
{
    if( fInstance->fCursor != nil )
        fInstance->fCursor->SetVisible( false );

    plMouseDevice::bCursorOverride = (override != 0);
    plMouseDevice::bCursorHidden = true;

}

void plMouseDevice::ShowCursor(hsBool override)
{
    if( !plMouseDevice::bCursorHidden )
        return;
    if (plMouseDevice::bCursorOverride && !override)
        return;

    plMouseDevice::bCursorHidden = false;
    plMouseDevice::bCursorOverride = false;
    
    if( fInstance->fCursor == nil )
        fInstance->CreateCursor( fInstance->fCursorID );
    fInstance->fCursor->SetVisible( true );
}

void plMouseDevice::NewCursor(char* cursor)
{
    fInstance->fCursorID = cursor;
    fInstance->CreateCursor(cursor);
    fInstance->SetCursorX(fInstance->GetCursorX());
    fInstance->SetCursorY(fInstance->GetCursorY());
    
    if (!plMouseDevice::bCursorHidden)
        fInstance->fCursor->SetVisible( true );
}

void    plMouseDevice::SetCursorOpacity( hsScalar opacity )
{
    fInstance->fOpacity = opacity;
    if( fInstance->fCursor != nil )
        fInstance->fCursor->SetOpacity( opacity );
}

hsBool plMouseDevice::MsgReceive(plMessage* msg)
{   
    plEvalMsg* pEMsg = plEvalMsg::ConvertNoRef(msg);
    if (pEMsg)
    {
        if (fXMsg)
        {
            plgDispatch::MsgSend(fXMsg);
            fXMsg = nil;
        }
        else
        {
            plMouseEventMsg* pMsg = TRACKED_NEW plMouseEventMsg;
            pMsg->SetXPos( fXPos );
            pMsg->SetYPos( fYPos );
            pMsg->SetDX(0);
            pMsg->SetDY(0);
            plgDispatch::MsgSend(pMsg);
        }

        if (fYMsg)
        {
            plgDispatch::MsgSend(fYMsg);
            fYMsg = nil;
        }
        else
        {
            plMouseEventMsg* pMsg = TRACKED_NEW plMouseEventMsg;
            pMsg->SetXPos( fXPos );
            pMsg->SetYPos( fYPos );
            pMsg->SetDX(0);
            pMsg->SetDY(0);
            plgDispatch::MsgSend(pMsg);
        }

        if( fB2Msg )
        {
            fB2Msg->Send();
            fB2Msg = nil;
        }

        // look for mouse button events in the queues to be sent now
        // ...Left mouse button
        if ( fLeftBMsg[0] != nil)
        {
            fLeftBMsg[0]->Send();
            // slide queue elements over... get 'em on the next eval
            fLeftBMsg[0] = fLeftBMsg[1];
            fLeftBMsg[1] = nil;
        }
        // ...Right mouse button
        if ( fRightBMsg[0] != nil)
        {
            fRightBMsg[0]->Send();
            // slide queue elements over... get 'em on the next eval
            fRightBMsg[0] = fRightBMsg[1];
            fRightBMsg[1] = nil;
        }
        // ...middle mouse button
        if ( fMiddleBMsg[0] != nil)
        {
            fMiddleBMsg[0]->Send();
            // slide queue elements over... get 'em on the next eval
            fMiddleBMsg[0] = fMiddleBMsg[1];
            fMiddleBMsg[1] = nil;
        }
    
    }
    
    plIMouseXEventMsg* pXMsg = plIMouseXEventMsg::ConvertNoRef(msg);
    if (pXMsg)
    {
        // send a mouse event
        plMouseEventMsg* pMsg = TRACKED_NEW plMouseEventMsg;
        if (pXMsg->fX == 999)
            pMsg->SetXPos( fXPos + 0.001f );
        else
        if (pXMsg->fX == -999)
            pMsg->SetXPos( fXPos - 0.001f );
        else
            pMsg->SetXPos(pXMsg->fX);
        pMsg->SetYPos( fYPos );
        pMsg->SetDX( ( fXPos - pMsg->GetXPos())  );
        pMsg->SetDY(0);
        
        if (pMsg->GetDX() == 0.0f && !plMouseDevice::bMsgAlways)
        {   
            delete pMsg;
            return true;
        }
        if (fXMsg)
            delete fXMsg;
        fXMsg = pMsg;
        
        if (pXMsg->fX == 999)
            fXPos += 0.01;
        else
        if (pXMsg->fX == -999)
            fXPos -= 0.01;
        else
            fXPos = pXMsg->fX;

        SetCursorX(fXPos);
        fWXPos = pXMsg->fWx;
        return true;
    }

    plIMouseYEventMsg* pYMsg = plIMouseYEventMsg::ConvertNoRef(msg);
    if (pYMsg)
    {
        // send a mouse event
        plMouseEventMsg* pMsg = TRACKED_NEW plMouseEventMsg;
        pMsg->SetXPos( fXPos );
        if (pYMsg->fY == 999)
            pMsg->SetYPos( fYPos + 0.01f );
        else
        if (pYMsg->fY == -999)
            pMsg->SetYPos( fYPos - 0.01f );
        else
            pMsg->SetYPos(pYMsg->fY);
        pMsg->SetDX(0);
        pMsg->SetDY(fYPos  - pMsg->GetYPos());
        
        if (pMsg->GetDY() == 0.0f && !plMouseDevice::bMsgAlways)
        {   
            delete pMsg;
            return true;
        }
        if (fYMsg)
            delete fYMsg;
        fYMsg = pMsg;
        
        if (pYMsg->fY == 999)
            fYPos += 0.01;
        else
        if (pYMsg->fY == -999)
            fYPos -= 0.01;
        else
            fYPos = pYMsg->fY;

        fWYPos = pYMsg->fWy;
        SetCursorY(fYPos);
        
        return true;
    }
    plIMouseBEventMsg* pBMsg = plIMouseBEventMsg::ConvertNoRef(msg);
    if (pBMsg)
    {
        
        // send a mouse event
        plMouseEventMsg* pMsg = TRACKED_NEW plMouseEventMsg;
        pMsg->SetXPos( fXPos );
        pMsg->SetYPos( fYPos );
        pMsg->SetDX(0);
        pMsg->SetDY(0);

        bool deleteMe = true;

        // which button is different?
        if (pBMsg->fButton & kLeftButtonDown && !(fButtonState & kLeftButtonDown))
        {
            // left button now down
            fButtonState |= kLeftButtonDown;
            pMsg->SetButton( kLeftButtonDown );
            deleteMe = false;
        }
        else
        if (pBMsg->fButton & kLeftButtonUp && fButtonState & kLeftButtonDown)
        {
            // left button now up
            fButtonState &= ~kLeftButtonDown;
            pMsg->SetButton( kLeftButtonUp );
            deleteMe = false;
        }
        else
        if (pBMsg->fButton & kRightButtonDown && !(fButtonState & kRightButtonDown))
        {
            // right button now down
            fButtonState |= kRightButtonDown;
            pMsg->SetButton( kRightButtonDown );
            deleteMe = false;
        }
        else
        if (pBMsg->fButton & kRightButtonUp && fButtonState & kRightButtonDown)
        {
            // right button now up
            fButtonState &= ~kRightButtonDown;
            pMsg->SetButton( kRightButtonUp );
            deleteMe = false;
        }
        else
        if (pBMsg->fButton & kMiddleButtonDown && !(fButtonState & kMiddleButtonDown))
        {
            // mouse wheel button now down
            fButtonState |= kMiddleButtonDown;
            pMsg->SetButton( kMiddleButtonDown );
            deleteMe = false;
        }
        else
        if (pBMsg->fButton & kMiddleButtonUp && fButtonState & kMiddleButtonDown)
        {
            // right button now up
            fButtonState &= ~kMiddleButtonDown;
            pMsg->SetButton( kMiddleButtonUp );
            deleteMe = false;
        }

        if (pBMsg->fButton & kRightButtonDblClk)
        {
            // right button dbl clicked, send TWO messages
            plMouseEventMsg* pMsg2 = TRACKED_NEW plMouseEventMsg;
            pMsg2->SetXPos( fXPos );
            pMsg2->SetYPos( fYPos );
            pMsg2->SetDX(0);
            pMsg2->SetDY(0);
            pMsg2->SetButton( kRightButtonDblClk );

            if( fB2Msg != nil )
                delete fB2Msg;
            fB2Msg = pMsg2;

            pMsg->SetButton( kRightButtonDown );
            deleteMe = false;
        }
        else
        if (pBMsg->fButton & kLeftButtonDblClk)
        {
            // left button dbl clicked, send TWO messages
            plMouseEventMsg* pMsg2 = TRACKED_NEW plMouseEventMsg;
            pMsg2->SetXPos( fXPos );
            pMsg2->SetYPos( fYPos );
            pMsg2->SetDX(0);
            pMsg2->SetDY(0);
            pMsg2->SetButton( kLeftButtonDblClk );

            if( fB2Msg != nil )
                delete fB2Msg;
            fB2Msg = pMsg2;

            pMsg->SetButton( kLeftButtonDown );
            deleteMe = false;
        }

        if( deleteMe )
        {
            // mouse button state not changed
            delete pMsg;
            return true;
        }

        // we are going to save up to two button mouse events per button (left and right)
        // that will be dispatched on the next eval

        // which button is this for?
        if ( pMsg->GetButton() == kLeftButtonDown || pMsg->GetButton() == kLeftButtonUp )
        {
            // see if the queue is just empty
            if ( fLeftBMsg[0] == nil)
            {
                // nothing to think about... goes in first slot
                fLeftBMsg[0] = pMsg;
            }
            else if (fLeftBMsg[1] == nil)
            {
                // nothing to think about... goes in second slot
                fLeftBMsg[1] = pMsg;
            }
            else
            {
                // else queue if full... need to make some decisions
                plMouseEventMsg* lastMsg = plMouseEventMsg::ConvertNoRef(pMsg);
                // ...if this is an up event and [1] is a down then we need to remove both
                //    ...because we can't lose the up event and the down will have no match
                if ( pMsg->GetButton() == kLeftButtonUp && lastMsg && lastMsg->GetButton() == kLeftButtonDown)
                {
                    delete pMsg;
                    delete fLeftBMsg[1];
                    fLeftBMsg[1] = nil;
                }
                // ... otherwise ignore this event
                else
                {
                    delete pMsg;
                }
            }
        }
        else if ( pMsg->GetButton() == kRightButtonDown || pMsg->GetButton() == kRightButtonUp )
        {
            // see if the queue is just empty
            if ( fRightBMsg[0] == nil)
            {
                // nothing to think about... goes in first slot
                fRightBMsg[0] = pMsg;
            }
            else if (fRightBMsg[1] == nil)
            {
                // nothing to think about... goes in second slot
                fRightBMsg[1] = pMsg;
            }
            else
            {
                // else queue if full... need to make some decisions
                plMouseEventMsg* lastMsg = plMouseEventMsg::ConvertNoRef(pMsg);
                // ...if this is an up event and [1] is a down then we need to remove both
                //    ...because we can't lose the up event and the down will have no match
                if ( pMsg->GetButton() == kRightButtonUp && lastMsg && lastMsg->GetButton() == kRightButtonDown)
                {
                    delete pMsg;
                    delete fRightBMsg[1];
                    fRightBMsg[1] = nil;
                }
                // ... otherwise ignore this event
                else
                {
                    delete pMsg;
                }
            }
        }
        else if ( pMsg->GetButton() == kMiddleButtonDown || pMsg->GetButton() == kMiddleButtonUp )
        {
            // see if the queue is just empty
            if ( fMiddleBMsg[0] == nil)
            {
                // nothing to think about... goes in first slot
                fMiddleBMsg[0] = pMsg;
            }
            else if (fMiddleBMsg[1] == nil)
            {
                // nothing to think about... goes in second slot
                fMiddleBMsg[1] = pMsg;
            }
            else
            {
                // else queue if full... need to make some decisions
                plMouseEventMsg* lastMsg = plMouseEventMsg::ConvertNoRef(pMsg);
                // ...if this is an up event and [1] is a down then we need to remove both
                //    ...because we can't lose the up event and the down will have no match
                if ( pMsg->GetButton() == kMiddleButtonUp && lastMsg && lastMsg->GetButton() == kMiddleButtonDown)
                {
                    delete pMsg;
                    delete fMiddleBMsg[1];
                    fMiddleBMsg[1] = nil;
                }
                // ... otherwise ignore this event
                else
                {
                    delete pMsg;
                }
            }
        }
        // we are going to dispatch the mouse button events right away
        // and not wait for the next eval, because we shouldn't miss one of these
        
        return true;
    }
    return false;
}



void plMouseDevice::HandleWindowActivate(bool bActive, HWND hWnd)
{
    if ( bActive )
    {
        RECT rect;
        ::GetClientRect(hWnd,&rect);

//      rect.right /= plInputManager::GetInstance()->GetMouseScale();
//      rect.bottom /= plInputManager::GetInstance()->GetMouseScale();

        ::MapWindowPoints( hWnd, NULL, (POINT *)&rect, 2 );
        ::ShowCursor( FALSE );
        SetCapture(hWnd);

    }
    else
    {
        ReleaseCapture();
        ::ShowCursor( TRUE );
    }   
}