/**
 * Sets up a test file creating the I/O thread.
 *
 * @returns VBox status code.
 * @param   pVM          Pointer to the shared VM instance structure.
 * @param   pTestFile    Pointer to the uninitialized test file structure.
 * @param   iTestId      Unique test id.
 */
static int tstPDMACStressTestFileOpen(PVM pVM, PPDMACTESTFILE pTestFile, unsigned iTestId)
{
    int rc = VERR_NO_MEMORY;

    /* Size is a multiple of 512 */
    pTestFile->cbFileMax     = RTRandU64Ex(FILE_SIZE_MIN, FILE_SIZE_MAX) & ~(511UL);
    pTestFile->cbFileCurr    = 0;
    pTestFile->cbFileSegment = RTRandU32Ex(SEGMENT_SIZE_MIN, RT_MIN(pTestFile->cbFileMax, SEGMENT_SIZE_MAX)) & ~((size_t)511);

    Assert(pTestFile->cbFileMax >= pTestFile->cbFileSegment);

    /* Set up the segments array. */
    pTestFile->cSegments  = pTestFile->cbFileMax / pTestFile->cbFileSegment;
    pTestFile->cSegments += ((pTestFile->cbFileMax % pTestFile->cbFileSegment) > 0) ? 1 : 0;

    pTestFile->paSegs = (PPDMACTESTFILESEG)RTMemAllocZ(pTestFile->cSegments * sizeof(PDMACTESTFILESEG));
    if (pTestFile->paSegs)
    {
        /* Init the segments */
        for (unsigned i = 0; i < pTestFile->cSegments; i++)
        {
            PPDMACTESTFILESEG pSeg = &pTestFile->paSegs[i];

            pSeg->off       = (RTFOFF)i * pTestFile->cbFileSegment;
            pSeg->cbSegment = pTestFile->cbFileSegment;

            /* Let the buffer point to a random position in the test pattern. */
            uint32_t offTestPattern = RTRandU64Ex(0, g_cbTestPattern - pSeg->cbSegment);

            pSeg->pbData = g_pbTestPattern + offTestPattern;
        }

        /* Init task array. */
        pTestFile->cTasksActiveMax = RTRandU32Ex(1, TASK_ACTIVE_MAX);
        pTestFile->paTasks         = (PPDMACTESTFILETASK)RTMemAllocZ(pTestFile->cTasksActiveMax * sizeof(PDMACTESTFILETASK));
        if (pTestFile->paTasks)
        {
            /* Create the template */
            char szDesc[256];

            RTStrPrintf(szDesc, sizeof(szDesc), "Template-%d", iTestId);
            rc = PDMR3AsyncCompletionTemplateCreateInternal(pVM, &pTestFile->pTemplate, tstPDMACStressTestFileTaskCompleted, pTestFile, szDesc);
            if (RT_SUCCESS(rc))
            {
                /* Open the endpoint now. Because async completion endpoints cannot create files we have to do it before. */
                char szFile[RTPATH_MAX];

                RTStrPrintf(szFile, sizeof(szFile), "tstPDMAsyncCompletionStress-%d.tmp", iTestId);

                RTFILE FileTmp;
                rc = RTFileOpen(&FileTmp, szFile, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE);
                if (RT_SUCCESS(rc))
                {
                    RTFileClose(FileTmp);

                    rc = PDMR3AsyncCompletionEpCreateForFile(&pTestFile->hEndpoint, szFile, 0, pTestFile->pTemplate);
                    if (RT_SUCCESS(rc))
                    {
                        char szThreadDesc[256];

                        pTestFile->fRunning = true;

                        /* Create the thread creating the I/O for the given file. */
                        RTStrPrintf(szThreadDesc, sizeof(szThreadDesc), "PDMACThread-%d", iTestId);
                        rc = PDMR3ThreadCreate(pVM, &pTestFile->hThread, pTestFile, tstPDMACTestFileThread,
                                               NULL, 0, RTTHREADTYPE_IO, szThreadDesc);
                        if (RT_SUCCESS(rc))
                        {
                            rc = PDMR3ThreadResume(pTestFile->hThread);
                            AssertRC(rc);

                            RTPrintf(TESTCASE ": Created test file %s cbFileMax=%llu cbFileSegment=%u cSegments=%u cTasksActiveMax=%u\n",
                                     szFile, pTestFile->cbFileMax, pTestFile->cbFileSegment, pTestFile->cSegments, pTestFile->cTasksActiveMax);
                            return VINF_SUCCESS;
                        }

                        PDMR3AsyncCompletionEpClose(pTestFile->hEndpoint);
                    }

                    RTFileDelete(szFile);
                }

                PDMR3AsyncCompletionTemplateDestroy(pTestFile->pTemplate);
            }

            RTMemFree(pTestFile->paTasks);
        }
        else
            rc = VERR_NO_MEMORY;

        RTMemFree(pTestFile->paSegs);
    }
    else
        rc = VERR_NO_MEMORY;

    RTPrintf(TESTCASE ": Opening test file with id %d failed rc=%Rrc\n", iTestId, rc);

    return rc;
}
/**
 *  Entry point.
 */
extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv, char **envp)
{
    int rcRet = 0; /* error count */
    PPDMASYNCCOMPLETIONENDPOINT pEndpointSrc, pEndpointDst;

    RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB);

    if (argc != 3)
    {
        RTPrintf(TESTCASE ": Usage is ./tstPDMAsyncCompletion <source> <dest>\n");
        return 1;
    }

    PVM pVM;
    PUVM pUVM;
    int rc = VMR3Create(1, NULL, NULL, NULL, NULL, NULL, &pVM, &pUVM);
    if (RT_SUCCESS(rc))
    {
        /*
         * Little hack to avoid the VM_ASSERT_EMT assertion.
         */
        RTTlsSet(pVM->pUVM->vm.s.idxTLS, &pVM->pUVM->aCpus[0]);
        pVM->pUVM->aCpus[0].pUVM = pVM->pUVM;
        pVM->pUVM->aCpus[0].vm.s.NativeThreadEMT = RTThreadNativeSelf();

        /*
         * Create the template.
         */
        PPDMASYNCCOMPLETIONTEMPLATE pTemplate;
        rc = PDMR3AsyncCompletionTemplateCreateInternal(pVM, &pTemplate, AsyncTaskCompleted, NULL, "Test");
        if (RT_FAILURE(rc))
        {
            RTPrintf(TESTCASE ": Error while creating the template!! rc=%d\n", rc);
            return 1;
        }

        /*
         * Create event semaphore.
         */
        rc = RTSemEventCreate(&g_FinishedEventSem);
        AssertRC(rc);

        /*
         * Create the temporary buffers.
         */
        for (unsigned i=0; i < NR_TASKS; i++)
        {
            g_AsyncCompletionTasksBuffer[i] = (uint8_t *)RTMemAllocZ(BUFFER_SIZE);
            if (!g_AsyncCompletionTasksBuffer[i])
            {
                RTPrintf(TESTCASE ": out of memory!\n");
                return ++rcRet;
            }
        }

        /* Create the destination as the async completion API can't do this. */
        RTFILE FileTmp;
        rc = RTFileOpen(&FileTmp, argv[2], RTFILE_O_READWRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_DENY_NONE);
        if (RT_FAILURE(rc))
        {
            RTPrintf(TESTCASE ": Error while creating the destination!! rc=%d\n", rc);
            return ++rcRet;
        }
        RTFileClose(FileTmp);

        /* Create our file endpoint */
        rc = PDMR3AsyncCompletionEpCreateForFile(&pEndpointSrc, argv[1], 0, pTemplate);
        if (RT_SUCCESS(rc))
        {
            rc = PDMR3AsyncCompletionEpCreateForFile(&pEndpointDst, argv[2], 0, pTemplate);
            if (RT_SUCCESS(rc))
            {
                PDMR3PowerOn(pVM);

                /* Wait for all threads to finish initialization. */
                RTThreadSleep(100);

                int fReadPass = true;
                uint64_t cbSrc;
                size_t   offSrc = 0;
                size_t   offDst = 0;
                uint32_t cTasksUsed = 0;

                rc = PDMR3AsyncCompletionEpGetSize(pEndpointSrc, &cbSrc);
                if (RT_SUCCESS(rc))
                {
                    /* Copy the data. */
                    for (;;)
                    {
                        if (fReadPass)
                        {
                            cTasksUsed = (BUFFER_SIZE * NR_TASKS) <= (cbSrc - offSrc)
                                         ? NR_TASKS
                                         :   ((cbSrc - offSrc) / BUFFER_SIZE)
                                           + ((cbSrc - offSrc) % BUFFER_SIZE) > 0
                                         ? 1
                                         : 0;

                            g_cTasksLeft = cTasksUsed;

                            for (uint32_t i = 0; i < cTasksUsed; i++)
                            {
                                size_t cbRead = ((size_t)offSrc + BUFFER_SIZE) <= cbSrc ? BUFFER_SIZE : cbSrc - offSrc;
                                RTSGSEG DataSeg;

                                DataSeg.pvSeg = g_AsyncCompletionTasksBuffer[i];
                                DataSeg.cbSeg = cbRead;

                                rc = PDMR3AsyncCompletionEpRead(pEndpointSrc, offSrc, &DataSeg, 1, cbRead, NULL,
                                                                &g_AsyncCompletionTasks[i]);
                                AssertRC(rc);
                                offSrc += cbRead;
                                if (offSrc == cbSrc)
                                    break;
                            }
                        }
                        else
                        {
                            g_cTasksLeft = cTasksUsed;

                            for (uint32_t i = 0; i < cTasksUsed; i++)
                            {
                                size_t cbWrite = (offDst + BUFFER_SIZE) <= cbSrc ? BUFFER_SIZE : cbSrc - offDst;
                                RTSGSEG DataSeg;

                                DataSeg.pvSeg = g_AsyncCompletionTasksBuffer[i];
                                DataSeg.cbSeg = cbWrite;

                                rc = PDMR3AsyncCompletionEpWrite(pEndpointDst, offDst, &DataSeg, 1, cbWrite, NULL,
                                                                 &g_AsyncCompletionTasks[i]);
                                AssertRC(rc);
                                offDst += cbWrite;
                                if (offDst == cbSrc)
                                    break;
                            }
                        }

                        rc = RTSemEventWait(g_FinishedEventSem, RT_INDEFINITE_WAIT);
                        AssertRC(rc);

                        if (!fReadPass && (offDst == cbSrc))
                            break;
                        else if (fReadPass)
                            fReadPass = false;
                        else
                        {
                            cTasksUsed = 0;
                            fReadPass = true;
                        }
                    }
                }
                else
                {
                    RTPrintf(TESTCASE ": Error querying size of the endpoint!! rc=%d\n", rc);
                    rcRet++;
                }

                PDMR3PowerOff(pVM);
                PDMR3AsyncCompletionEpClose(pEndpointDst);
            }
            PDMR3AsyncCompletionEpClose(pEndpointSrc);
        }

        rc = VMR3Destroy(pUVM);
        AssertMsg(rc == VINF_SUCCESS, ("%s: Destroying VM failed rc=%Rrc!!\n", __FUNCTION__, rc));
        VMR3ReleaseUVM(pUVM);

        /*
         * Clean up.
         */
        for (uint32_t i = 0; i < NR_TASKS; i++)
        {
            RTMemFree(g_AsyncCompletionTasksBuffer[i]);
        }
    }
    else
    {
        RTPrintf(TESTCASE ": failed to create VM!! rc=%Rrc\n", rc);
        rcRet++;
    }

    return rcRet;
}