/*************************************************************************\
* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne
*     National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/* $Revision-Id$
 * 
 * Record Support Routines for the Array Subroutine Record type,
 * derived from Andy Foster's genSub record, with some features
 * removed and asynchronous support added.
 *
 *      Original Author: Andy Foster
 *      Revised by: Andrew Johnson
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "alarm.h"
#include "cantProceed.h"
#include "dbDefs.h"
#include "dbEvent.h"
#include "dbAccess.h"
#include "dbFldTypes.h"
#include "dbStaticLib.h"
#include "errMdef.h"
#include "errlog.h"
#include "recSup.h"
#include "devSup.h"
#include "special.h"
#include "registryFunction.h"
#include "epicsExport.h"
#include "recGbl.h"

#define GEN_SIZE_OFFSET
#include "aSubRecord.h"
#undef  GEN_SIZE_OFFSET


typedef long (*GENFUNCPTR)(struct aSubRecord *);

/* Create RSET - Record Support Entry Table*/

#define report             NULL
#define initialize          NULL
static long init_record(aSubRecord *, int);
static long process(aSubRecord *);
static long special(DBADDR *, int);
#define get_value          NULL
static long cvt_dbaddr(DBADDR *);
static long get_array_info(DBADDR *, long *, long *);
static long put_array_info(DBADDR *, long );
#define get_units          NULL
static long get_precision(DBADDR *, long *);
#define get_enum_str       NULL
#define get_enum_strs      NULL
#define put_enum_str       NULL
#define get_graphic_double NULL
#define get_control_double NULL
#define get_alarm_double   NULL

rset aSubRSET = {
    RSETNUMBER,
    report,
    initialize,
    init_record,
    process,
    special,
    get_value,
    cvt_dbaddr,
    get_array_info,
    put_array_info,
    get_units,
    get_precision,
    get_enum_str,
    get_enum_strs,
    put_enum_str,
    get_graphic_double,
    get_control_double,
    get_alarm_double
};
epicsExportAddress(rset, aSubRSET);

static long initFields(epicsEnum16 *pft, epicsUInt32 *pno, epicsUInt32 *pne,
    const char **fldnames, void **pval, void **povl);
static long fetch_values(aSubRecord *prec);
static void monitor(aSubRecord *);
static long do_sub(aSubRecord *);

#define NUM_ARGS        21
#define MAX_ARRAY_SIZE 10000000

/* These are the names of the Input fields */
static const char *Ifldnames[] = {
    "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K",
    "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U"
};

/* These are the names of the Output fields */
static const char *Ofldnames[] = {
    "VALA", "VALB", "VALC", "VALD", "VALE", "VALF", "VALG",
    "VALH", "VALI", "VALJ", "VALK", "VALL", "VALM", "VALN",
    "VALO", "VALP", "VALQ", "VALR", "VALS", "VALT", "VALU"
};


static long init_record(aSubRecord *prec, int pass)
{
    STATIC_ASSERT(sizeof(prec->onam)==sizeof(prec->snam));
    GENFUNCPTR     pfunc;
    long           status;
    int            i;

    status = 0;
    if (pass == 0) {
        /* Allocate memory for arrays */
        initFields(&prec->fta,  &prec->noa,  &prec->nea,  Ifldnames,
            &prec->a,    NULL);
        initFields(&prec->ftva, &prec->nova, &prec->neva, Ofldnames,
            &prec->vala, &prec->ovla);
        return 0;
    }

    /* Initialize the Subroutine Name Link */
    switch (prec->subl.type) {
    case CONSTANT:
        recGblInitConstantLink(&prec->subl, DBF_STRING, prec->snam);
        break;

    case PV_LINK:
    case DB_LINK:
    case CA_LINK:
        break;

    default:
        recGblRecordError(S_db_badField, (void *)prec,
            "aSubRecord(init_record) Bad SUBL link type");
        return S_db_badField;
    }

    /* Initialize Input Links */
    for (i = 0; i < NUM_ARGS; i++) {
        struct link *plink = &(&prec->inpa)[i];
        switch (plink->type) {
        case CONSTANT:
            if ((&prec->noa)[i] < 2) {
                if (recGblInitConstantLink(plink, (&prec->fta)[i], (&prec->a)[i])) {
                    prec->udf = FALSE;
                } else
                    prec->udf = TRUE;
            }
            break;

        case PV_LINK:
        case CA_LINK:
        case DB_LINK:
            break;

        default:
            recGblRecordError(S_db_badField, (void *)prec,
                "aSubRecord(init_record) Illegal INPUT LINK");
            status = S_db_badField;
            break;
        }
    }

    if (status)
        return status;

    /* Initialize Output Links */
    for (i = 0; i < NUM_ARGS; i++) {
        switch ((&prec->outa)[i].type) {
        case CONSTANT:
        case PV_LINK:
        case CA_LINK:
        case DB_LINK:
            break;

        default:
            recGblRecordError(S_db_badField, (void *)prec,
                "aSubRecord(init_record) Illegal OUTPUT LINK");
            status = S_db_badField;
        }
    }
    if (status)
        return status;

    /* Call the user initialization routine if there is one */
    if (prec->inam[0] != 0) {
        pfunc = (GENFUNCPTR)registryFunctionFind(prec->inam);
        if (pfunc) {
            pfunc(prec);
        } else {
            recGblRecordError(S_db_BadSub, (void *)prec,
                "aSubRecord::init_record - INAM subr not found");
            return S_db_BadSub;
        }
    }

    if (prec->lflg == aSubLFLG_IGNORE &&
        prec->snam[0] != 0) {
        pfunc = (GENFUNCPTR)registryFunctionFind(prec->snam);
        if (pfunc)
            prec->sadr = pfunc;
        else {
            recGblRecordError(S_db_BadSub, (void *)prec,
                "aSubRecord::init_record - SNAM subr not found");
            return S_db_BadSub;
        }
    }
    strcpy(prec->onam, prec->snam);
    prec->oval = prec->val;
    return 0;
}


static long initFields(epicsEnum16 *pft, epicsUInt32 *pno, epicsUInt32 *pne,
    const char **fldnames, void **pval, void **povl)
{
    int i;
    long status = 0;

    for (i = 0; i < NUM_ARGS; i++, pft++, pno++, pne++, pval++) {
        epicsUInt32 num;
        epicsUInt32 flen;

        if (*pft > DBF_ENUM)
            *pft = DBF_CHAR;

        if (*pno == 0)
            *pno = 1;

        flen = dbValueSize(*pft);
        num = *pno * flen;

        if (num > MAX_ARRAY_SIZE) {
            epicsPrintf("Link %s - Array too large! %d Bytes\n", fldnames[i], num);
            *pno = num = 0;
            status = S_db_errArg;
        } else
            *pval = (char *)callocMustSucceed(*pno, flen,
                "aSubRecord::init_record");

        *pne = *pno;

        if (povl) {
            if (num)
                *povl = (char *)callocMustSucceed(*pno, flen,
                    "aSubRecord::init_record");
            povl++;
        }
    }
    return status;
}


static long process(aSubRecord *prec)
{
    int pact = prec->pact;
    long status = 0;

    if (!pact) {
        prec->pact = TRUE;
        status = fetch_values(prec);
        prec->pact = FALSE;
    }

    if (!status) {
        status = do_sub(prec);
        prec->val = status;
    }

    if (!pact && prec->pact)
        return 0;

    prec->pact = TRUE;

    /* Push the output link values */
    if (!status) {
        int i;
        for (i = 0; i < NUM_ARGS; i++)
            dbPutLink(&(&prec->outa)[i], (&prec->ftva)[i], (&prec->vala)[i],
                (&prec->neva)[i]);
    }

    recGblGetTimeStamp(prec);
    monitor(prec);
    recGblFwdLink(prec);
    prec->pact = FALSE;

    return 0;
}

static long fetch_values(aSubRecord *prec)
{
    long status;
    int i;

    if (prec->lflg == aSubLFLG_READ) {
        /* Get the Subroutine Name and look it up if changed */
        status = dbGetLink(&prec->subl, DBR_STRING, prec->snam, 0, 0);
        if (status)
            return status;

        if (prec->snam[0] != 0 &&
            strcmp(prec->snam, prec->onam)) {
            GENFUNCPTR pfunc = (GENFUNCPTR)registryFunctionFind(prec->snam);

            if (!pfunc)
                return S_db_BadSub;

            prec->sadr = pfunc;
            strcpy(prec->onam, prec->snam);
        }
    }

    /* Get the input link values */
    for (i = 0; i < NUM_ARGS; i++) {
        long nRequest = (&prec->noa)[i];
        status = dbGetLink(&(&prec->inpa)[i], (&prec->fta)[i], (&prec->a)[i], 0,
            &nRequest);
        if (nRequest > 0)
            (&prec->nea)[i] = nRequest;
        if (status)
            return status;
    }
    return 0;
}

static long get_precision(DBADDR *paddr, long *precision)
{
    aSubRecord *prec = (aSubRecord *)paddr->precord;

    *precision = prec->prec;
    recGblGetPrec(paddr, precision);
    return 0;
}


static void monitor(aSubRecord *prec)
{
    int            i;
    unsigned short monitor_mask;

    monitor_mask = recGblResetAlarms(prec) | DBE_VALUE | DBE_LOG;

    /* Post events for VAL field */
    if (prec->val != prec->oval) {
        db_post_events(prec, &prec->val, monitor_mask);
        prec->oval = prec->val;
    }

    /* Event posting on VAL arrays depends on the setting of prec->eflg */
    switch (prec->eflg) {
    case aSubEFLG_NEVER:
        break;
    case aSubEFLG_ON_CHANGE:
        for (i = 0; i < NUM_ARGS; i++) {
            epicsUInt32 alen = dbValueSize((&prec->ftva)[i]) * (&prec->neva)[i];
            void *povl = (&prec->ovla)[i];
            void *pval = (&prec->vala)[i];
            if (memcmp(povl, pval, alen)) {
                memcpy(povl, pval, alen);
                db_post_events(prec, pval, monitor_mask);
            }
        }
        break;
    case aSubEFLG_ALWAYS:
        for (i = 0; i < NUM_ARGS; i++)
            db_post_events(prec, (&prec->vala)[i], monitor_mask);
        break;
    }
    return;
}


static long do_sub(aSubRecord *prec)
{
    GENFUNCPTR pfunc = prec->sadr;
    long status;

    if (prec->snam[0] == 0)
        return 0;

    if (pfunc == NULL) {
        recGblSetSevr(prec, BAD_SUB_ALARM, INVALID_ALARM);
        return S_db_BadSub;
    }
    status = pfunc(prec);
    if (status < 0)
        recGblSetSevr(prec, SOFT_ALARM, prec->brsv);
    else
        prec->udf = FALSE;

    return status;
}


static long cvt_dbaddr(DBADDR *paddr)
{
    aSubRecord *prec = (aSubRecord *)paddr->precord;
    int fieldIndex = dbGetFieldIndex(paddr);

    if (fieldIndex >= aSubRecordA &&
        fieldIndex <= aSubRecordU) {
        int offset = fieldIndex - aSubRecordA;

        paddr->pfield      = (&prec->a  )[offset];
        paddr->no_elements = (&prec->noa)[offset];
        paddr->field_type  = (&prec->fta)[offset];
    }
    else if (fieldIndex >= aSubRecordVALA &&
             fieldIndex <= aSubRecordVALU) {
        int offset = fieldIndex - aSubRecordVALA;

        paddr->pfield      = (&prec->vala)[offset];
        paddr->no_elements = (&prec->nova)[offset];
        paddr->field_type  = (&prec->ftva)[offset];
    }
    else {
        errlogPrintf("aSubRecord::cvt_dbaddr called for %s.%s\n",
            prec->name, paddr->pfldDes->name);
        return 0;
    }
    paddr->dbr_field_type = paddr->field_type;
    paddr->field_size     = dbValueSize(paddr->field_type);
    return 0;
}


static long get_array_info(DBADDR *paddr, long *no_elements, long *offset)
{
    aSubRecord *prec = (aSubRecord *)paddr->precord;
    int fieldIndex = dbGetFieldIndex(paddr);

    if (fieldIndex >= aSubRecordA &&
        fieldIndex <= aSubRecordU) {
        *no_elements = (&prec->nea)[fieldIndex - aSubRecordA];
    }
    else if (fieldIndex >= aSubRecordVALA &&
             fieldIndex <= aSubRecordVALU) {
        *no_elements = (&prec->neva)[fieldIndex - aSubRecordVALA];
    }
    else {
        errlogPrintf("aSubRecord::get_array_info called for %s.%s\n",
            prec->name, paddr->pfldDes->name);
    }
    *offset = 0;

    return 0;
}


static long put_array_info(DBADDR *paddr, long nNew)
{
    aSubRecord *prec = (aSubRecord *)paddr->precord;
    int fieldIndex = dbGetFieldIndex(paddr);

    if (fieldIndex >= aSubRecordA &&
        fieldIndex <= aSubRecordU) {
        (&prec->nea)[fieldIndex - aSubRecordA] = nNew;
    }
    else if (fieldIndex >= aSubRecordVALA &&
             fieldIndex <= aSubRecordVALU) {
        (&prec->neva)[fieldIndex - aSubRecordVALA] = nNew;
    }
    else {
        errlogPrintf("aSubRecord::put_array_info called for %s.%s\n",
            prec->name, paddr->pfldDes->name);
    }
    return 0;
}


static long special(DBADDR *paddr, int after)
{
    aSubRecord *prec = (aSubRecord *)paddr->precord;

    if (after &&
        prec->lflg == aSubLFLG_IGNORE) {
        if (prec->snam[0] == 0)
            return 0;

        prec->sadr = (GENFUNCPTR)registryFunctionFind(prec->snam);
        if (!prec->sadr) {
            recGblRecordError(S_db_BadSub, (void *)prec, prec->snam);
            return S_db_BadSub;
        }
    }
    return 0;
}
