//////////////////////////////////////////////////////////////////////////////
//
// Asura_PS2_USBKeyboard.cpp
//
// Created by Neil Massey.
//
// Copyright (c) 2002 Rebellion. All rights reserved.
//
// Derived class implementation of USB keyboard support
//
//////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"
#include "Input/Asura_PS2_USBkeyboard.h"
#include "Input/Logitech/lgkbmerr.h"

#include "Debugging/Asura_Logfile.h"
#include "Input/Asura_HIDToAscii.h"

#include <string.h>     // for memset

#if ASURA_PS2_SUPPORT_KEYBOARD

Asura_PS2_USBKeyboard::Asura_PS2_USBKeyboard(int iPortNumber) 
                      : Asura_PS2_USBDevice(iPortNumber)
{
    m_xDeviceType = USB_DT_KEYBOARD;
    m_iRepeatedKey = 0;
    m_bLEDStateValid = false;
    m_uNextBufferEntry = 0;
}

Asura_PS2_USBKeyboard::~Asura_PS2_USBKeyboard(void)
{
}

void Asura_PS2_USBKeyboard::Update(void)
{
    switch(m_eCurrentPhase)
    {
        case USB_PHASE_NONE:
        break;

        case USB_PHASE_INITIALISING:
        {
            // Reset variables
            memset(&m_xKbdDeviceDesc,       0, sizeof(m_xKbdDeviceDesc));
            memset(&m_xKbdEventList,        0, sizeof(m_xKbdEventList));
            memset(&m_xKbdState,            0, sizeof(m_xKbdState));
            memset(&m_abAsuraKeyBuffer,     0, sizeof(m_abAsuraKeyBuffer));

            m_iEnumNeeded  = LGKBM_HINT_NOENUMNEEDED;
            m_iKbdDevice = LGKBM_INVALID_DEVICE;
            m_iLocation = -1;
            m_eCurrentPhase = USB_PHASE_ENUMERATING;
            m_bDoTranslate = false;
            m_bLEDStateValid = false;
        }
        break;

        case USB_PHASE_ENUMERATING:
        {
            // Enumerate only one keyboard as we only support one
            int iResult = lgKbdEnumerate(0, &m_xKbdDeviceDesc);
            if(iResult == LGKBM_SUCCESS)
            {
                m_eCurrentPhase = USB_PHASE_ENUMERATED;
            }
            else
            {
                // possible return values
                // LGKBM_ERR_NO_MORE_DEVICES There are no more devices to be enumerated. 
                // If this error is returned on the first call, then there are no devices attached. 
                // LGKBM_ERR_ALREADY_OPENED The device has already been opened 
                // LGKBM_ERR_DEVICE_LOST Device has been unplugged during calls to lgKbdEnumerate(). 
                // LGKBM_ERROR An error occurred while issuing the RPC call.
                m_eCurrentPhase = USB_PHASE_ERROR;
            }
        }
        break;

        case USB_PHASE_ENUMERATED:
        {
            // Try to open the keyboard
            int iResult = lgKbdOpen(0, &m_iKbdDevice);
            if(iResult == LGKBM_SUCCESS)
            {
                m_eCurrentPhase = USB_PHASE_GET_LOCATIONS;
            }
            else
            {
                Asura_OutputToDebugger(("lgKbdOpen failed with code %d\n", iResult));
                m_eCurrentPhase = USB_PHASE_ERROR;
            }
        }
        break;

        case USB_PHASE_GET_LOCATIONS:
        {
            m_iLocation = static_cast<int>(m_xKbdDeviceDesc.device_location[0]);
// (MH - 15.01.2004) this method of forcing device numbers to match locations
// appears flawed when you consider that we should support hubs. The new PSX
// model of PS2 has only a single USB port. Therefore we allow any location
// and skip this test.
//            // Check whether it's plugged into the right port - we only support one keyboard remember!
//            if(m_iLocation != m_iDeviceNumber)
//            {
//                m_eCurrentPhase = USB_PHASE_ERROR;
//            }
//            else
//            {
//                m_eCurrentPhase = USB_PHASE_READY;
//            }
            m_eCurrentPhase = USB_PHASE_READY;
        }
        break;

        case USB_PHASE_BUSY:
        {
            m_eCurrentPhase = USB_PHASE_READY;
        }
        break;

        case USB_PHASE_ERROR:
        {
            // Error - so close the device and set the state to poll for new devices
            if (m_iKbdDevice != LGKBM_INVALID_DEVICE)
            {
                int iResult = lgKbdClose(m_iKbdDevice);
                if (iResult != LGKBM_SUCCESS)
                {
                    Asura_OutputToDebugger(("lgKbdClose failed with code %d\n", iResult));
                }
                m_iKbdDevice  = LGKBM_INVALID_DEVICE;
            }
            m_eCurrentPhase = USB_PHASE_NO_DEVICES;
        }
        break;

        case USB_PHASE_NO_DEVICES:
        {
            // Check for an enumhint and enumerate if needed
            if(m_iEnumNeeded == LGKBM_HINT_ENUMNEEDED)
            {
                m_eCurrentPhase = USB_PHASE_INITIALISING;
            }
            lgKbdEnumHint(&m_iEnumNeeded);
        }
        break;

        case USB_PHASE_CALIBRATE:
            // Doesn't do anything - here to stop compiler complaining
        break;

        case USB_PHASE_READY:
        {
            u_int iRC = 0;
            m_bDoTranslate  = false;
            // Check previous frames return of hint needed
            if(m_iEnumNeeded == LGKBM_HINT_ENUMNEEDED)
            {
                m_eCurrentPhase = USB_PHASE_ERROR;
            }
            else 
            {
                // Check for plug / unplug hint
                int iResult = lgKbdReadState(m_iKbdDevice, &m_xKbdState);
                switch(iResult)
                {
                    case LGKBM_SUCCESS:
                    {
                        // Read data
                        iRC = lgKbdRead(m_iKbdDevice, &m_xKbdEventList);
                        // Check for error
                        if(iRC == LGKBM_ERR_INVALID_PARAMETER ||
                           iRC == LGKBM_ERR_DEVICE_LOST ||
                           iRC == LGKBM_ERROR)
                        {
                            m_eCurrentPhase = USB_PHASE_ERROR;
                        }
                
                        // Check didn't overflow or rollover
                        m_bDoTranslate = (iRC == LGKBM_SUCCESS);
                        // See if device unplugged for next frame
                        lgKbdEnumHint(&m_iEnumNeeded);
                    }
                    break;

                    case LGKBM_ERR_DEVICE_LOST:
                    {
                        // device unplugged
                        m_eCurrentPhase = USB_PHASE_ERROR;
                    }
                    break;

                    case LGKBM_ERR_ROLLOVER:
					case LGKBM_ERR_BUFFER_OVERFLOW:
                    {
                        // too many keys pressed at once
                        // could go to busy state to sit out for a frame but 
                        // I don't think we need to.
                    }
                    break;

                    default:
                    {
                        Asura_OutputToDebugger(("unexpected return value (%x) from lgKbdReadState()\n", iResult));
                        m_eCurrentPhase = USB_PHASE_ERROR;
                    }
                    break;
                }
            }
        }
        break;

        case USB_PHASE_DEVICE_SPECIFIC:
            // Doesn't do anything - here to stop compiler complaining
        break;
    }
}

void Asura_PS2_USBKeyboard::Translate(u_char *piAsuraInputBufferOffset, float *piAsuraAxis_BufferOffset)
{
    ResetKeyStrokeBuffer();

    if(!m_bDoTranslate)
    {
        // Maybe clear the buffer?
        for(int i=0; i<ASURA_KEYID_END_OF_KEYBOARD; i++)
        {
            m_abAsuraKeyBuffer[i] = false;
        }
        m_iRepeatedKey = 0;
        return;
    }

    // Translate into characters
    for(int i=0; i<m_xKbdEventList.num_events; ++i)
    {
        lgKbdEvent* pxCurrentEvent = &(m_xKbdEventList.events[i]);
        // Check if either shift pressed
        int iAsciiValue = 0;
        m_bLEDStateValid = true;
        // Check event type                                
        if(pxCurrentEvent->event_type == LGKBM_KEY_DOWN)
        {
            // write into buffer
            m_abAsuraKeyBuffer[m_iRepeatedKey] = false;
            m_iRepeatedKey = 0;
            iAsciiValue = GetAsuraKeyIDFromHID(pxCurrentEvent->key_info.key_code, IsKeyToggled(ASURA_KEYID_NUMLOCK));
            m_abAsuraKeyBuffer[iAsciiValue] = true;

            //write keyid to keystroke buffer
            if (m_uNextBufferEntry < uASURA_PS2_KEYBUFFERSIZE)
            {
                m_aiAsuraKeyStrokeBuffer[m_uNextBufferEntry] = iAsciiValue;
                m_abAsuraKeyStrokeShiftBuffer[m_uNextBufferEntry] =
                    m_abAsuraKeyBuffer[ASURA_KEYID_LEFTSHIFT] || m_abAsuraKeyBuffer[ASURA_KEYID_RIGHTSHIFT];
                m_abAsuraKeyStrokeCapsBuffer[m_uNextBufferEntry] = false;
                u_short usLedState = 0;
                if (lgKbdGetLedState(m_iKbdDevice, &usLedState) == LGKBM_SUCCESS)
                {
                    m_abAsuraKeyStrokeCapsBuffer[m_uNextBufferEntry] = (usLedState & LGKBD_CAPSLOCK_MASK) != 0;
                }
                m_uNextBufferEntry++;
            }
        }
        else if(pxCurrentEvent->event_type == LGKBM_KEY_UP)
        {
            // On key up clear both the shifted and non shifted keys
            // Clear repeat key
            m_abAsuraKeyBuffer[m_iRepeatedKey] = false;
            m_iRepeatedKey = 0;
            // Clear non-shifted 
            iAsciiValue = GetAsuraKeyIDFromHID(pxCurrentEvent->key_info.key_code, IsKeyToggled(ASURA_KEYID_NUMLOCK));
            m_abAsuraKeyBuffer[iAsciiValue] = false;
        }
        else if(pxCurrentEvent->event_type == LGKBM_KEY_REPEAT)
        {
            // Check for key delay
            iAsciiValue = GetAsuraKeyIDFromHID(pxCurrentEvent->key_info.key_code, IsKeyToggled(ASURA_KEYID_NUMLOCK));
            m_abAsuraKeyBuffer[iAsciiValue] = true;
            m_iRepeatedKey = iAsciiValue;
        }
    }
    // Translate ascii table into asura_userinput
    for(int i=0; i<ASURA_KEYID_END_OF_KEYBOARD; i++)
    {
        piAsuraInputBufferOffset[i] = m_abAsuraKeyBuffer[i] * 255;
    }
    // Emulate key up from repeat key
    if(m_iRepeatedKey != 0)
    {
        m_abAsuraKeyBuffer[m_iRepeatedKey] = false;
        m_iRepeatedKey = 0;
    }

    // Asura's own shift, ctrl and alt
    if(piAsuraInputBufferOffset[ASURA_KEYID_LEFTSHIFT] ||
        piAsuraInputBufferOffset[ASURA_KEYID_RIGHTSHIFT])
    {
        piAsuraInputBufferOffset[ASURA_KEYID_SHIFT] = 255;
    }

    if(piAsuraInputBufferOffset[ASURA_KEYID_LEFTCTRL] ||
        piAsuraInputBufferOffset[ASURA_KEYID_RIGHTCTRL])
    {
        piAsuraInputBufferOffset[ASURA_KEYID_CTRL] = 255;
    }

    if(piAsuraInputBufferOffset[ASURA_KEYID_LEFTALT] ||
        piAsuraInputBufferOffset[ASURA_KEYID_RIGHTALT])
    {
        piAsuraInputBufferOffset[ASURA_KEYID_ALT] = 255;
    }
}

bool Asura_PS2_USBKeyboard::Available(void)
{
    return m_eCurrentPhase == USB_PHASE_READY;
}

void Asura_PS2_USBKeyboard::Initialise(void)
{
    // Set initial phase only - everything else handled by state machine above
    m_eCurrentPhase = USB_PHASE_NO_DEVICES;

    // Default to repeat being off.
    SetRepeatEnabled( false );
}

void Asura_PS2_USBKeyboard::SetRepeatEnabled( bool bEnable )
{
    if( bEnable )
    {
        lgKbdSetRepeatRate(LGKBM_DEFAULT_INITIAL_DELAY, LGKBM_DEFAULT_REPEAT_INTERVAL);
    }
    else
    {
        lgKbdSetRepeatRate( 0, 0 );
    }
}

bool Asura_PS2_USBKeyboard::IsKeyToggled(int iKey) const
{
    if (m_eCurrentPhase != USB_PHASE_READY)
    {
        return false;
    }
    u_short usLedState = 0;
    if (m_bLEDStateValid &&
        lgKbdGetLedState(m_iKbdDevice, &usLedState) == LGKBM_SUCCESS)
    {
        switch (iKey)
        {
            case ASURA_KEYID_CAPSLOCK:  
                return (usLedState & LGKBD_CAPSLOCK_MASK) != 0;
            case ASURA_KEYID_NUMLOCK:
                return (usLedState & LGKBD_NUMLOCK_MASK) != 0;
            case ASURA_KEYID_SCROLLOK:
                return (usLedState & LGKBD_SCROLLLOCK_MASK) != 0;
            default:
                break;
        }
    }
    return false;
}


#endif // #if ASURA_PS2_SUPPORT_KEYBOARD

