/*************************************************************************\
* Copyright (c) 2017 UChicago Argonne LLC, as operator of Argonne
* National Laboratory.
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/

#include <string.h>

#define EPICS_DBCA_PRIVATE_API

#include "dbAccess.h"
#include "dbStaticLib.h"
#include "dbTest.h"
#include "dbUnitTest.h"
#include "epicsEvent.h"
#include "errlog.h"
#include "registryFunction.h"
#include "subRecord.h"
#include "testMain.h"

static int startCounter, doneCounter;
static epicsEventId asyncEvent, doneEvent;

static
long asyncSubr(subRecord *prec)
{
    testDiag("Processing %s, pact=%d", prec->name, prec->pact);

    if (!prec->pact) {
        epicsEventTrigger(asyncEvent);
        prec->pact = 1;     /* Make asynchronous */
    }

    return 0;
}

static
long doneSubr(subRecord *prec)
{
    epicsEventTrigger(doneEvent);
    return 0;
}

static
void checkAsyncInput(const char *rec, int init, dbCommon *async)
{
    char inp[16], proc[16];

    testDiag("Checking record '%s'", rec);

    strcpy(inp, rec);
    strcat(inp, ".INP");
    strcpy(proc, rec);
    strcat(proc, ".PROC");

    if (init) {
        testdbGetFieldEqual(rec, DBF_LONG, init);

        testdbPutFieldOk(inp, DBF_STRING, "async");
    }

    testdbPutFieldOk(proc, DBF_CHAR, 1);

    epicsEventWait(asyncEvent);
    testdbGetFieldEqual("startCounter", DBF_LONG, ++startCounter);
    testdbGetFieldEqual("doneCounter", DBF_LONG, doneCounter);

    dbScanLock(async);
    async->rset->process(async);
    dbScanUnlock(async);

    epicsEventWait(doneEvent);
    testdbGetFieldEqual("startCounter", DBF_LONG, startCounter);
    testdbGetFieldEqual("doneCounter", DBF_LONG, ++doneCounter);
}

static
void testAsynInputs(dbCommon *async)
{
    const char * records[] = {
        "ai0", "bi0", "di0", "ii0", "li0", "mi0", "si0", NULL,
        "bi1",  /* bi1 must be first in this group */
        "ai1", "di1", "ii1", "li1", "mi1", "si1", NULL,
    };
    const char ** rec = &records[0];
    int init = 1;   /* bi1 initializes to 1 */

    testDiag("============ Starting %s ============", EPICS_FUNCTION);

    startCounter = doneCounter = 0;
    testdbPutFieldOk("startCounter", DBF_LONG, startCounter);
    testdbPutFieldOk("doneCounter", DBF_LONG, doneCounter);

    epicsEventTryWait(asyncEvent);
    epicsEventTryWait(doneEvent);

    while (*rec) {  /* 1st group don't need initializing */
        checkAsyncInput(*rec++, 0, async);
    }
    rec++;
    while (*rec) {
        checkAsyncInput(*rec++, init, async);
        init = 9;   /* remainder initialize to 9 */
    }

    testDiag("============= Ending %s =============", EPICS_FUNCTION);
}

static
void checkAsyncOutput(const char *rec, dbCommon *async)
{
    char proc[16];

    testDiag("Checking record '%s'", rec);

    strcpy(proc, rec);
    strcat(proc, ".PROC");

    testdbCaWaitForConnect(dbGetDevLink(testdbRecordPtr(rec)));

    testdbPutFieldOk(proc, DBF_CHAR, 1);

    epicsEventWait(asyncEvent);
    testdbGetFieldEqual("startCounter", DBF_LONG, ++startCounter);
    testdbGetFieldEqual("doneCounter", DBF_LONG, doneCounter);

    dbScanLock(async);
    async->rset->process(async);
    dbScanUnlock(async);

    epicsEventWait(doneEvent);
    testdbGetFieldEqual("startCounter", DBF_LONG, startCounter);
    testdbGetFieldEqual("doneCounter", DBF_LONG, ++doneCounter);
}

static
void testAsyncOutputs(dbCommon *async)
{
    const char * records[] = {
        "ao1", "bo1", "do1", "io1", "lo1", "lso1", "mo1", "so1", NULL,
    };
    const char ** rec = &records[0];

    testDiag("============ Starting %s ============", EPICS_FUNCTION);

    startCounter = doneCounter = 0;
    testdbPutFieldOk("startCounter", DBF_LONG, startCounter);
    testdbPutFieldOk("doneCounter", DBF_LONG, doneCounter);

    epicsEventTryWait(asyncEvent);
    epicsEventTryWait(doneEvent);

    while (*rec) {
        checkAsyncOutput(*rec++, async);
    }

    testDiag("============= Ending %s =============", EPICS_FUNCTION);
}

void recTestIoc_registerRecordDeviceDriver(struct dbBase *);

MAIN(asyncSoftTest)
{
    dbCommon *async;

    testPlan(128);

    testdbPrepare();
    testdbReadDatabase("recTestIoc.dbd", NULL, NULL);

    recTestIoc_registerRecordDeviceDriver(pdbbase);
    registryFunctionAdd("asyncSubr", (REGISTRYFUNCTION) asyncSubr);
    registryFunctionAdd("doneSubr", (REGISTRYFUNCTION) doneSubr);

    testdbReadDatabase("asyncSoftTest.db", NULL, NULL);

    eltc(0);
    testIocInitOk();
    eltc(1);

    async = testdbRecordPtr("async");
    asyncEvent = epicsEventCreate(epicsEventEmpty);
    doneEvent = epicsEventCreate(epicsEventEmpty);

    testAsynInputs(async);
    testAsyncOutputs(async);

    testIocShutdownOk();
    testdbCleanup();

    return testDone();
}
