/* $Id: tstMouseImpl.cpp $ */
/** @file
 * Main unit test - Mouse class.
 */

/*
 * Copyright (C) 2011-2013 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */

/******************************************************************************
*   Header Files                                                              *
******************************************************************************/
#define IN_VMM_R3 /* Kill most Windows warnings on CFGMR3* implementations. */
#include "MouseImpl.h"
#include "VMMDev.h"
#include "DisplayImpl.h"

#include <VBox/vmm/cfgm.h>
#include <VBox/vmm/pdmdrv.h>
#include <VBox/VMMDev.h>
#include <iprt/assert.h>
#include <iprt/test.h>

#ifndef RT_OS_WINDOWS
NS_DECL_CLASSINFO(Mouse)
NS_IMPL_THREADSAFE_ISUPPORTS1_CI(Mouse, IMouse)
#endif

PDMIVMMDEVPORT VMMDevPort;

class TestVMMDev : public VMMDevMouseInterface
{
    PPDMIVMMDEVPORT getVMMDevPort(void) { return &VMMDevPort; }
};

class TestDisplay : public DisplayMouseInterface
{
    void getFramebufferDimensions(int32_t *px1, int32_t *py1,
                                  int32_t *px2, int32_t *py2);
    int getScreenResolution(uint32_t cScreen, ULONG *pcx, ULONG *pcy,
                            ULONG *pcBPP, LONG *pXOrigin, LONG *pYOrigin);
};

class TestConsole : public ConsoleMouseInterface
{
public:
    VMMDevMouseInterface *getVMMDevMouseInterface() { return &mVMMDev; }
    DisplayMouseInterface *getDisplayMouseInterface() { return &mDisplay; }
    /** @todo why on earth is this not implemented? */
    void onMouseCapabilityChange(BOOL supportsAbsolute, BOOL supportsRelative,
                                 BOOL supportsMT, BOOL needsHostCursor) {}

private:
    TestVMMDev mVMMDev;
    TestDisplay mDisplay;
};

static int pdmdrvhlpAttach(PPDMDRVINS pDrvIns, uint32_t fFlags,
                           PPDMIBASE *ppBaseInterface)
{
    return VERR_PDM_NO_ATTACHED_DRIVER;
}

static struct PDMDRVHLPR3 pdmHlpR3 =
{
    PDM_DRVHLPR3_VERSION,
    pdmdrvhlpAttach,
    NULL,                    /* pfnDetach */
    NULL,                    /* pfnDetachSelf */
    NULL,                    /* pfnMountPrepare */
    NULL,                    /* pfnAssertEMT */
    NULL,                    /* pfnAssertOther */
    NULL,                    /* pfnVMSetError */
    NULL,                    /* pfnVMSetErrorV */
    NULL,                    /* pfnVMSetRuntimeError */
    NULL,                    /* pfnVMSetRuntimeErrorV */
    NULL,                    /* pfnVMState */
    NULL,                    /* pfnVMTeleportedAndNotFullyResumedYet */
    NULL,                    /* pfnGetSupDrvSession */
    NULL,                    /* pfnQueueCreate */
    NULL,                    /* pfnTMGetVirtualFreq */
    NULL,                    /* pfnTMGetVirtualTime */
    NULL,                    /* pfnTMTimerCreate */
    NULL,                    /* pfnSSMRegister */
    NULL,                    /* pfnSSMDeregister */
    NULL,                    /* pfnDBGFInfoRegister */
    NULL,                    /* pfnDBGFInfoDeregister */
    NULL,                    /* pfnSTAMRegister */
    NULL,                    /* pfnSTAMRegisterF */
    NULL,                    /* pfnSTAMRegisterV */
    NULL,                    /* pfnSTAMDeregister */
    NULL,                    /* pfnSUPCallVMMR0Ex */
    NULL,                    /* pfnUSBRegisterHub */
    NULL,                    /* pfnSetAsyncNotification */
    NULL,                    /* pfnAsyncNotificationCompleted */
    NULL,                    /* pfnThreadCreate */
    NULL,                    /* pfnAsyncCompletionTemplateCreate */
#ifdef VBOX_WITH_NETSHAPER
    NULL,                    /* pfnNetShaperAttach */
    NULL,                    /* pfnNetShaperDetach */
#endif
    NULL,                    /* pfnLdrGetRCInterfaceSymbols */
    NULL,                    /* pfnLdrGetR0InterfaceSymbols */
    NULL,                    /* pfnCritSectInit */
    NULL,                    /* pfnCallR0 */
    NULL,                    /* pfnFTSetCheckpoint */
    NULL,                    /* pfnBlkCacheRetain */
    NULL,                    /* pfnVMGetSuspendReason */
    NULL,                    /* pfnVMGetResumeReason */
    NULL,                    /* pfnReserved0 */
    NULL,                    /* pfnReserved1 */
    NULL,                    /* pfnReserved2 */
    NULL,                    /* pfnReserved3 */
    NULL,                    /* pfnReserved4 */
    NULL,                    /* pfnReserved5 */
    NULL,                    /* pfnReserved6 */
    NULL,                    /* pfnReserved7 */
    NULL,                    /* pfnReserved8 */
    NULL,                    /* pfnReserved9 */
    PDM_DRVHLPR3_VERSION     /* u32TheEnd */
};

static struct
{
    int32_t dx;
    int32_t dy;
    int32_t dz;
    int32_t dw;
} mouseEvent;

static int mousePutEvent(PPDMIMOUSEPORT pInterface, int32_t iDeltaX,
                         int32_t iDeltaY, int32_t iDeltaZ, int32_t iDeltaW,
                         uint32_t fButtonStates)
{
    mouseEvent.dx = iDeltaX;
    mouseEvent.dy = iDeltaY;
    mouseEvent.dz = iDeltaZ;
    mouseEvent.dw = iDeltaW;
    return VINF_SUCCESS;
}

static struct
{
    uint32_t cx;
    uint32_t cy;
    int32_t  dz;
    int32_t  dw;
    uint32_t fButtonStates;
} mouseEventAbs;

static int mousePutEventAbs(PPDMIMOUSEPORT pInterface, uint32_t uX,
                            uint32_t uY, int32_t iDeltaZ, int32_t iDeltaW,
                            uint32_t fButtonStates)
{
    mouseEventAbs.cx            = uX;
    mouseEventAbs.cy            = uY;
    mouseEventAbs.dz            = iDeltaZ;
    mouseEventAbs.dw            = iDeltaW;
    mouseEventAbs.fButtonStates = fButtonStates;
    return VINF_SUCCESS;
}

static struct PDMIMOUSEPORT pdmiMousePort =
{
    mousePutEvent,
    mousePutEventAbs,
    NULL                     /* pfnPutEventMT */
};

static void *pdmiBaseQuery(struct PDMIBASE *pInterface, const char *pszIID)
{
    return &pdmiMousePort;
}

static struct PDMIBASE pdmiBase =
{
    pdmiBaseQuery
};

static struct PDMDRVINS pdmdrvInsCore =
{
    PDM_DRVINS_VERSION,
    0,                       /* iInstance */
    NIL_RTRCPTR,             /* pHlpRC */
    NIL_RTRCPTR,             /* pvInstanceDataRC */
    NIL_RTR0PTR,             /* pHelpR0 */
    NIL_RTR0PTR,             /* pvInstanceDataR0 */
    &pdmHlpR3,
    NULL,                    /* pvInstanceDataR3 */
    NIL_RTR3PTR,             /* pReg */
    NIL_RTR3PTR,             /* pCfg */
    &pdmiBase,
    NULL,                    /* pDownBase */
    {                        /* IBase */
        NULL                 /* pfnQueryInterface */
    },
    0,                       /* fTracing */
    0,                       /* idTracing */
#if HC_ARCH_BITS == 32
    { 0 },                   /* au32Padding */
#endif
    {
        { 0 }                /* padding */
    },                       /* Internal */
    { 0 }                    /* achInstanceData */
};

static struct PDMDRVINS *ppdmdrvIns = NULL;

ComObjPtr<Mouse> pMouse;
ConsoleMouseInterface *pConsole = NULL;

static struct
{
    int32_t x;
    int32_t y;
} absoluteMouse;

static int setAbsoluteMouse(PPDMIVMMDEVPORT, int32_t x, int32_t y)
{
    absoluteMouse.x = x;
    absoluteMouse.y = y;
    return VINF_SUCCESS;
}

static int updateMouseCapabilities(PPDMIVMMDEVPORT, uint32_t, uint32_t)
{
    return VINF_SUCCESS;
}

void TestDisplay::getFramebufferDimensions(int32_t *px1, int32_t *py1,
                                           int32_t *px2, int32_t *py2)
{
    if (px1)
        *px1 = -320;
    if (py1)
        *py1 = -240;
    if (px2)
        *px2 = 320;
    if (py2)
        *py2 = 240;
}

int TestDisplay::getScreenResolution(uint32_t cScreen, ULONG *pcx,
                                     ULONG *pcy, ULONG *pcBPP, LONG *pXOrigin, LONG *pYOrigin)
{
    NOREF(cScreen);
    if (pcx)
        *pcx = 640;
    if (pcy)
        *pcy = 480;
    if (pcBPP)
        *pcBPP = 32;
    if (pXOrigin)
        *pXOrigin = 0;
    if (pYOrigin)
        *pYOrigin = 0;
    return S_OK;
}

/******************************************************************************
*   Main test code                                                            *
******************************************************************************/

static int setup(void)
{
    PCFGMNODE pCfg = NULL;
    Mouse *pMouse2;
    int rc = VERR_NO_MEMORY;
    VMMDevPort.pfnSetAbsoluteMouse = setAbsoluteMouse;
    VMMDevPort.pfnUpdateMouseCapabilities = updateMouseCapabilities;
    HRESULT hrc = pMouse.createObject();
    AssertComRC(hrc);
    if (FAILED(hrc))
        return VERR_GENERAL_FAILURE;
    pConsole = new TestConsole;
    pMouse->init(pConsole);
    ppdmdrvIns = (struct PDMDRVINS *) RTMemAllocZ(  sizeof(struct PDMDRVINS)
                                                  + Mouse::DrvReg.cbInstance);
    *ppdmdrvIns = pdmdrvInsCore;
    pMouse2 = pMouse;
    pCfg = CFGMR3CreateTree(NULL);
    if (pCfg)
    {
        rc = CFGMR3InsertInteger(pCfg, "Object", (uintptr_t)pMouse2);
        if (RT_SUCCESS(rc))
            Mouse::DrvReg.pfnConstruct(ppdmdrvIns, pCfg, 0);
    }
    return rc;
}

static void teardown(void)
{
    pMouse.setNull();
    if (pConsole)
        delete pConsole;
    if (ppdmdrvIns)
        RTMemFree(ppdmdrvIns);
}

static bool approxEq(int a, int b, int prec)
{
    return a - b < prec && b - a < prec;
}

/** @test testAbsToVMMDevNewProtocol */
static void testAbsToVMMDevNewProtocol(RTTEST hTest)
{
    PPDMIBASE pBase;
    PPDMIMOUSECONNECTOR pConnector;

    RTTestSub(hTest, "Absolute event to VMMDev, new protocol");
    pBase = &ppdmdrvIns->IBase;
    pConnector = (PPDMIMOUSECONNECTOR)pBase->pfnQueryInterface(pBase,
                                                 PDMIMOUSECONNECTOR_IID);
    pConnector->pfnReportModes(pConnector, true, false, false);
    pMouse->onVMMDevGuestCapsChange(  VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE
                                    | VMMDEV_MOUSE_NEW_PROTOCOL);
    pMouse->PutMouseEventAbsolute(0, 0, 0, 0, 0);
    RTTESTI_CHECK_MSG(approxEq(absoluteMouse.x, 0x8000, 200),
                      ("absoluteMouse.x=%d\n", absoluteMouse.x));
    RTTESTI_CHECK_MSG(approxEq(absoluteMouse.y, 0x8000, 200),
                      ("absoluteMouse.y=%d\n", absoluteMouse.y));
    pMouse->PutMouseEventAbsolute(-319, -239, 0, 0, 0);
    RTTESTI_CHECK_MSG(approxEq(absoluteMouse.x, 0, 200),
                      ("absoluteMouse.x=%d\n", absoluteMouse.x));
    RTTESTI_CHECK_MSG(approxEq(absoluteMouse.y, 0, 200),
                      ("absoluteMouse.y=%d\n", absoluteMouse.y));
    pMouse->PutMouseEventAbsolute(320, 240, 0, 0, 0);
    RTTESTI_CHECK_MSG(approxEq(absoluteMouse.x, 0xffff, 200),
                      ("absoluteMouse.x=%d\n", absoluteMouse.x));
    RTTESTI_CHECK_MSG(approxEq(absoluteMouse.y, 0xffff, 200),
                      ("absoluteMouse.y=%d\n", absoluteMouse.y));
    RTTestSubDone(hTest);
}

/** @test testAbsToVMMDevOldProtocol */
static void testAbsToVMMDevOldProtocol(RTTEST hTest)
{
    PPDMIBASE pBase;
    PPDMIMOUSECONNECTOR pConnector;

    RTTestSub(hTest, "Absolute event to VMMDev, old protocol");
    pBase = &ppdmdrvIns->IBase;
    pConnector = (PPDMIMOUSECONNECTOR)pBase->pfnQueryInterface(pBase,
                                                 PDMIMOUSECONNECTOR_IID);
    pConnector->pfnReportModes(pConnector, true, false, false);
    pMouse->onVMMDevGuestCapsChange(VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE);
    pMouse->PutMouseEventAbsolute(320, 240, 0, 0, 0);
    RTTESTI_CHECK_MSG(approxEq(absoluteMouse.x, 0x8000, 200),
                      ("absoluteMouse.x=%d\n", absoluteMouse.x));
    RTTESTI_CHECK_MSG(approxEq(absoluteMouse.y, 0x8000, 200),
                      ("absoluteMouse.y=%d\n", absoluteMouse.y));
    pMouse->PutMouseEventAbsolute(0, 0, 0, 0, 0);
    RTTESTI_CHECK_MSG(approxEq(absoluteMouse.x, 0, 200),
                      ("absoluteMouse.x=%d\n", absoluteMouse.x));
    RTTESTI_CHECK_MSG(approxEq(absoluteMouse.y, 0, 200),
                      ("absoluteMouse.y=%d\n", absoluteMouse.y));
    pMouse->PutMouseEventAbsolute(-319, -239, 0, 0, 0);
    RTTESTI_CHECK_MSG(approxEq(absoluteMouse.x, -0x8000, 200),
                      ("absoluteMouse.x=%d\n", absoluteMouse.x));
    RTTESTI_CHECK_MSG(approxEq(absoluteMouse.y, -0x8000, 200),
                      ("absoluteMouse.y=%d\n", absoluteMouse.y));
    RTTestSubDone(hTest);
}

/** @test testAbsToAbsDev */
static void testAbsToAbsDev(RTTEST hTest)
{
    PPDMIBASE pBase;
    PPDMIMOUSECONNECTOR pConnector;

    RTTestSub(hTest, "Absolute event to absolute device");
    pBase = &ppdmdrvIns->IBase;
    pConnector = (PPDMIMOUSECONNECTOR)pBase->pfnQueryInterface(pBase,
                                                 PDMIMOUSECONNECTOR_IID);
    pConnector->pfnReportModes(pConnector, false, true, false);
    pMouse->onVMMDevGuestCapsChange(VMMDEV_MOUSE_NEW_PROTOCOL);
    pMouse->PutMouseEventAbsolute(0, 0, 0, 0, 0);
    RTTESTI_CHECK_MSG(approxEq(mouseEventAbs.cx, 0x8000, 200),
                      ("mouseEventAbs.cx=%d\n", mouseEventAbs.cx));
    RTTESTI_CHECK_MSG(approxEq(mouseEventAbs.cy, 0x8000, 200),
                      ("mouseEventAbs.cy=%d\n", mouseEventAbs.cy));
    pMouse->PutMouseEventAbsolute(-319, -239, 0, 0, 3);
    RTTESTI_CHECK_MSG(approxEq(mouseEventAbs.cx, 0, 200),
                      ("mouseEventAbs.cx=%d\n", mouseEventAbs.cx));
    RTTESTI_CHECK_MSG(approxEq(mouseEventAbs.cy, 0, 200),
                      ("mouseEventAbs.cy=%d\n", mouseEventAbs.cy));
    RTTESTI_CHECK_MSG(mouseEventAbs.fButtonStates == 3,
                      ("mouseEventAbs.fButtonStates=%u\n",
                      (unsigned) mouseEventAbs.fButtonStates));
    pMouse->PutMouseEventAbsolute(320, 240, -3, 2, 1);
    RTTESTI_CHECK_MSG(approxEq(mouseEventAbs.cx, 0xffff, 200),
                      ("mouseEventAbs.cx=%d\n", mouseEventAbs.cx));
    RTTESTI_CHECK_MSG(approxEq(mouseEventAbs.cy, 0xffff, 200),
                      ("mouseEventAbs.cy=%d\n", mouseEventAbs.cy));
    RTTESTI_CHECK_MSG(mouseEventAbs.fButtonStates == 1,
                      ("mouseEventAbs.fButtonStates=%u\n",
                      (unsigned) mouseEventAbs.fButtonStates));
    RTTESTI_CHECK_MSG(mouseEventAbs.dz == -3,
                      ("mouseEventAbs.dz=%d\n", (int) mouseEvent.dz));
    RTTESTI_CHECK_MSG(mouseEventAbs.dw == 2,
                      ("mouseEventAbs.dw=%d\n", (int) mouseEvent.dw));
    mouseEventAbs.cx = mouseEventAbs.cy = 0xffff;
    pMouse->PutMouseEventAbsolute(-640, -480, 0, 0, 0);
    RTTESTI_CHECK_MSG(mouseEventAbs.cx == 0xffff,
                      ("mouseEventAbs.cx=%d\n", mouseEventAbs.cx));
    RTTESTI_CHECK_MSG(mouseEventAbs.cy == 0xffff,
                      ("mouseEventAbs.cy=%d\n", mouseEventAbs.cy));
    RTTestSubDone(hTest);
}

/** @todo generate this using the @test blocks above */
typedef void (*PFNTEST)(RTTEST);
static PFNTEST g_tests[] =
{
    testAbsToVMMDevNewProtocol,
    testAbsToVMMDevOldProtocol,
    testAbsToAbsDev,
    NULL
};

int main(void)
{
    /*
     * Init the runtime, test and say hello.
     */
    RTTEST hTest;
    RTEXITCODE rcExit = RTTestInitAndCreate("tstMouseImpl", &hTest);
    if (rcExit != RTEXITCODE_SUCCESS)
        return rcExit;
    RTTestBanner(hTest);

    /*
     * Run the tests.
     */
    for (unsigned i = 0; g_tests[i]; ++i)
    {
        int rc = setup();
        AssertRC(rc);
        if (RT_SUCCESS(rc))
            g_tests[i](hTest);
        teardown();
    }

    /*
     * Summary
     */
    return RTTestSummaryAndDestroy(hTest);
}

