////////////////////////////////////////////////////////////////////////////////
// Input.cpp
//
//  DirectInput handling.

#include "stdhdr.h"

#include "Input.h"

// Force the inclusion of the libraries we need
#pragma comment(lib, "kernel32.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "dinput8.lib")

////////////////////////////////////////////////////////////////////////////////
// Input support

// DirectInput object
IDirectInput8           *g_pDI;
// Array of devices that we will be using
LPDIRECTINPUTDEVICE8    *g_pDeviceArray = NULL;
// Number of devices to handle
int                     g_nDevices = 0;

// Global structure for enumeration
static  DIACTIONFORMAT  g_diaf;

BOOL CALLBACK DIEnumDevicesBySemanticsCallback(LPCDIDEVICEINSTANCE lpddi,  
    IDirectInputDevice8 *lpdid, DWORD dwFlags, DWORD dwRemaining, LPVOID pvRef);

// Initialise DirectInput
BOOL InitDirectInput()
{
    HRESULT h;

    // Create an IDirectInput8 object that we will use to create devices.
    h = DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8,
        (void **)&g_pDI, NULL);
    if (FAILED(h))
        return FALSE;

    ZeroMemory(&g_diaf, sizeof(DIACTIONFORMAT));
    g_diaf.dwSize = sizeof(DIACTIONFORMAT);
    g_diaf.dwActionSize = sizeof(DIACTION);
    g_diaf.rgoAction = g_rgActions;
    g_diaf.dwNumActions = g_nActions;
    g_diaf.dwDataSize = g_diaf.dwNumActions * sizeof(DWORD);
    g_diaf.guidActionMap = g_guidApp;
    g_diaf.dwGenre = g_dwGenre;
    g_diaf.dwBufferSize = 16;
    g_diaf.lAxisMin = -100;
    g_diaf.lAxisMax = 100;
    _tcscpy(g_diaf.tszActionMap, g_tszActionMapName);
    h = g_pDI->EnumDevicesBySemantics(NULL, &g_diaf, 
        DIEnumDevicesBySemanticsCallback,
        NULL, DIEDBSFL_ATTACHEDONLY);
    if (FAILED(h))
        return FALSE;

    return TRUE;
}

// Shut down DirectInput
void ExitDirectInput()
{
    for (int iDevice = 0; iDevice < g_nDevices; iDevice++)
        g_pDeviceArray[iDevice]->Release();

    delete [] g_pDeviceArray;
    g_pDeviceArray = NULL;
    g_nDevices = 0;

    if (g_pDI != NULL)
    {
        g_pDI->Release();
        g_pDI = NULL;
    }
}

// Acquire (obtain control of) the devices
void AcquireDevices()
{
    for (int iDevice = 0; iDevice < g_nDevices; iDevice++)
        g_pDeviceArray[iDevice]->Acquire();
}

// Unacquire (release control of) the devices
void UnacquireDevices()
{
    if (g_pDeviceArray == NULL)
        return;

    for (int iDevice = 0; iDevice < g_nDevices; iDevice++)
        g_pDeviceArray[iDevice]->Unacquire();
}

// Callback function to map appropriate devices
BOOL CALLBACK DIEnumDevicesBySemanticsCallback(LPCDIDEVICEINSTANCE lpddi,  
    IDirectInputDevice8 *lpdid, DWORD dwFlags, DWORD dwRemaining, LPVOID pvRef)
{
    HRESULT     h;

    // Devices of type DI8DEVTYPE_DEVICECTRL are specialized devices not generally
    // considered appropriate to control game actions. We just ignore these.
    if (GET_DIDEVICE_TYPE(lpddi->dwDevType) == DI8DEVTYPE_DEVICECTRL)
        return DIENUM_CONTINUE;

    // Assign exclusive control of this device to us.
    h = lpdid->SetCooperativeLevel(hWndMain, DISCL_EXCLUSIVE | DISCL_FOREGROUND);
    if (FAILED(h))
        return DIENUM_CONTINUE;

    // Build the action map for the device. This will map each action to
    //  the most appropriate function on the device.
    h = lpdid->BuildActionMap(&g_diaf, NULL, DIDBAM_DEFAULT);
    if (FAILED(h))
        return DIENUM_CONTINUE;

    for (DWORD i = 0; i < g_diaf.dwNumActions; i++)
    {
        if (g_diaf.rgoAction[i].dwHow != DIAH_UNMAPPED)
            break;
    }
    if (i < g_diaf.dwNumActions)
    {
        // If any controls were mapped, we will be using this device,
        //  so set the action map on this device.
        h = lpdid->SetActionMap(&g_diaf, NULL, DIDSAM_DEFAULT);
        if (FAILED(h))
            return DIENUM_CONTINUE;

        if (g_pDeviceArray == NULL)
            g_pDeviceArray = new LPDIRECTINPUTDEVICE8[dwRemaining + 1];

        g_pDeviceArray[g_nDevices] = lpdid;
        g_nDevices++;

        // Add a reference to this device, since DirectInput will 
        //  release the device when we return.
        lpdid->AddRef();
    }
    return DIENUM_CONTINUE;
}

#define INPUT_DATA_LIMIT    20

// Check each device for actions
void CheckInput()
{
    DIDEVICEOBJECTDATA  pdidod[INPUT_DATA_LIMIT];
    DWORD               dwObjCount;

    if (g_pDeviceArray == NULL)
        return;

    for (int iDevice = 0; iDevice < g_nDevices; iDevice++)
    {
        // Poll the device for data. 
        g_pDeviceArray[iDevice]->Poll(); 
 
        // Retrieve the data.
        dwObjCount = INPUT_DATA_LIMIT;
        g_pDeviceArray[iDevice]->GetDeviceData(sizeof(DIDEVICEOBJECTDATA),
                                               pdidod,
                                               &dwObjCount, 0);
        for (DWORD i = 0; i < dwObjCount; i++)
            // Handle the actions regardless of what device returned them.
            HandleAction(pdidod[i].uAppData, pdidod[i].dwData);
    }
}

////////////////////////////////////////////////////////////////////////////////
// Input configuration support

// Display the input configuration, without allowing the user to change it.
BOOL DisplayInput()
{
    DICONFIGUREDEVICESPARAMS dicdp;
    
    ZeroMemory(&dicdp, sizeof(dicdp));

    dicdp.dwSize = sizeof(dicdp);
    dicdp.dwcUsers = 1;
    dicdp.lptszUserNames = NULL;
    dicdp.dwcFormats = 1;
    dicdp.lprgFormats = &g_diaf;
    dicdp.hwnd = hWndMain;
    dicdp.lpUnkDDSTarget = NULL;

    // Set up a colour set, which will allow us to make the configuration box
    //  look like the rest of our program.
    // If this is initialised to zero, DirectInput will use the default colour set.
    dicdp.dics.dwSize = sizeof(DICOLORSET);

    // Let go of any devices so that the configuration interface can have a go
    UnacquireDevices();

    // Display action configuration
    g_pDI->ConfigureDevices(NULL, &dicdp, DICD_DEFAULT, NULL);

    AcquireDevices();
    return TRUE;
}

// Display the input configuration box
BOOL ConfigureInput()
{
    DICONFIGUREDEVICESPARAMS dicdp;
    
    ZeroMemory(&dicdp, sizeof(dicdp));

    dicdp.dwSize = sizeof(dicdp);
    dicdp.dwcUsers = 1;
    dicdp.lptszUserNames = NULL;
    dicdp.dwcFormats = 1;
    dicdp.lprgFormats = &g_diaf;
    dicdp.hwnd = hWndMain;
    dicdp.lpUnkDDSTarget = NULL;

    // Set up a colour set, which will allow us to make the configuration box
    //  look like the rest of our program.
    // If this is initialised to zero, DirectInput will use the default colour set.
    dicdp.dics.dwSize = sizeof(DICOLORSET);

    // Let go of any devices so that the configuration interface can have a go
    UnacquireDevices();

    // Display action configuration
    g_pDI->ConfigureDevices(NULL, &dicdp, DICD_EDIT, NULL);

    // Devices are no longer valid, so reinitialise.
    for (int iDevice = 0; iDevice < g_nDevices; iDevice++)
        g_pDeviceArray[iDevice]->Release();

    delete [] g_pDeviceArray;
    g_pDeviceArray = NULL;
    g_nDevices = 0;

    HRESULT h = g_pDI->EnumDevicesBySemantics(NULL, &g_diaf, 
        DIEnumDevicesBySemanticsCallback,
        NULL, DIEDBSFL_ATTACHEDONLY);
    if (FAILED(h))
        return FALSE;

    AcquireDevices();

    return TRUE;
}