/*************************************************************************\
* Copyright (c) 2010 The UChicago Argonne LLC, as Operator of Argonne
*     National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
*     Operator of Los Alamos 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.
\*************************************************************************/
/* dbLink.h
 *
 *  Created on: Mar 21, 2010
 *      Author: Andrew Johnson
 */

#ifndef INC_dbLink_H
#define INC_dbLink_H

#include "link.h"
#include "dbCoreAPI.h"
#include "epicsTypes.h"
#include "epicsTime.h"
#include "dbAddr.h"
#include "dbChannel.h"

#ifdef __cplusplus
extern "C" {
#endif

struct dbLocker;

/** @file   dbLink.h
 * @brief   Link Support API
 *
 * Link support run-time API, all link types provide an lset which is used by
 * the IOC database to control and operate the link. This file also declares the
 * dbLink routines that IOC, record and device code can call to perform link
 * operations.
 */

/** @brief  callback routine for locked link operations
 *
 * Called by the lset::doLocked method to permit multiple link operations
 * while the link instance is locked.
 *
 * @param   plink   the link
 * @param   priv    context for the callback routine
 */
typedef long (*dbLinkUserCallback)(struct link *plink, void *priv);

/** @brief  Link Support Entry Table
 *
 * This structure provides information about and methods for an individual link
 * type. A pointer to this structure is included in every link's lset field, and
 * is used to perform operations on the link. For JSON links the pointer is
 * obtained by calling pjlink->pif->get_lset() at link initialization time,
 * immediately before calling dbLinkOpen() to activate the link.
 */
typedef struct lset {
    /* Characteristics of the link type */

    /** @brief  link constancy
     *
     * 1 means this is a constant link type whose value doesn't change.
     * The link's value will be obtained using one of the methods loadScalar,
     * loadLS or loadArray.
     */
    const unsigned isConstant:1;

    /** @brief  link volatility
     *
     * 0 means the link is always connected.
     */
    const unsigned isVolatile:1;

    /** @brief  activate link
     *
     * Optional, called whenever a JSON link is initialized or added at runtime.
     *
     * @param   plink   the link
     */
    void (*openLink)(struct link *plink);

    /** @brief  deactivate link
     *
     * Optional, called whenever a link address is changed at runtime, or the
     * IOC is shutting down.
     *
     * @param   locker
     * @param   plink   the link
     */
    void (*removeLink)(struct dbLocker *locker, struct link *plink);

    /* Constant link initialization and data type hinting */

    /** @brief  load constant scalar from link type
     *
     * Usually called during IOC initialization, constant link types must copy a
     * scalar value of the indicated data type to the buffer provided and return
     * 0. A non-constant link type can use this method call as an early hint
     * that subsequent calls to dbGetLink() will request scalar data of the
     * indicated type, although the type might change.
     *
     * @param   plink   the link
     * @param   dbrType data type code
     * @param   pbuffer where to put the value
     * @returns 0 if a value was loaded, non-zero otherwise
     */
    long (*loadScalar)(struct link *plink, short dbrType, void *pbuffer);

    /** @brief  load constant long string from link type
     *
     * Usually called during IOC initialization, constant link types must copy a
     * nil-terminated string up to size characters long to the buffer provided,
     * and write the length of that string to the plen location. A non-constant
     * link type can use this as an early hint that subsequent calls to
     * dbGetLink() will request long string data, although this might change.
     *
     * @param   plink   the link
     * @param   pbuffer where to put the string
     * @param   size    length of pbuffer in chars
     * @param   plen    set to number of chars written
     * @returns status value
     */
    long (*loadLS)(struct link *plink, char *pbuffer, epicsUInt32 size,
            epicsUInt32 *plen);

    /** @brief  load constant array from link type
     *
     * Usually called during IOC initialization, constant link types must copy
     * an array value of the indicated data type to the buffer provided, update
     * the pnRequest location to indicate how many elements were loaded, and
     * return 0. A non-constant link type can use this method call as an early
     * hint that subsequent calls to dbGetLink() will request array data of the
     * indicated type and max size, although the request might change.
     *
     * @param   plink       the link
     * @param   dbrType     data type code
     * @param   pbuffer     where to put the value
     * @param   pnRequest   Max elements on entry, actual on exit
     * @returns 0 if elements were loaded, non-zero otherwise
     */
    long (*loadArray)(struct link *plink, short dbrType, void *pbuffer,
            long *pnRequest);

    /* Metadata */

    /** @brief  return link connection status
     *
     * Return an indication whether this link is connected or not. This routine
     * is polled by the calcout and some external record types. Not required for
     * non-volatile link types, which are by definition always connected.
     *
     * @param   plink   the link
     * @returns 1 if connected, 0 if disconnected
     */
    int (*isConnected)(const struct link *plink);

    /** @brief  get data type of link destination
     *
     * Called on both input and output links by long string support code to
     * decide whether to use DBR_CHAR/DBR_UCHAR or DBR_STRING for a subsequent
     * dbPutLink() or dbGetLink() call. Optional, but if not provided long
     * strings cannot be transported over this link type, and no warning or
     * error will appear to explain why. Not required for constant link types.
     *
     * @param   plink   the link
     * @returns DBF_* type code, or -1 on error/disconnected link
     */
    int (*getDBFtype)(const struct link *plink);

    /* Get data */

    /** @brief  get array size of an input link
     *
     * Called on input links by the compress record type for memory allocation
     * purposes, before using the dbGetLink() routine to fetch the actual
     * array data.
     *
     * @param   plink       the link
     * @param   pnElements  where to put the answer
     * @returns status value
     */
    long (*getElements)(const struct link *plink, long *pnElements);

    /** @brief  get value from an input link
     *
     * Called to fetch data from the link, which must be converted into the
     * given data type and placed in the buffer indicated. The actual number of
     * elements retrieved should be updated in the pnRequest location. If this
     * method returns an error status value, the link's record will be placed
     * into an Invalid severity / Link Alarm state by the dbGetLink() routine
     * that calls this method.
     *
     * @param   plink       the link
     * @param   dbrType     data type code
     * @param   pbuffer     where to put the value
     * @param   pnRequest   max elements on entry, actual on exit
     * @returns status value
     */
    long (*getValue)(struct link *plink, short dbrType, void *pbuffer,
            long *pnRequest);

    /** @brief  get the control range for an output link
     *
     * Called to fetch the control range for the link target, as a pair of
     * double values for the lowest and highest values that the target will
     * accept. This method is not used at all by the IOC or built-in record
     * types, although external record types may require it.
     *
     * @param   plink   the link
     * @param   lo      lowest accepted value
     * @param   hi      highest accepted value
     * @returns status value
     */
    long (*getControlLimits)(const struct link *plink, double *lo, double *hi);

    /** @brief  get the display range from an input link
     *
     * Called to fetch the display range for an input link target, as a pair of
     * double values for the lowest and highest values that the PV expects to
     * return. This method is used by several built-in record types to obtain
     * the display range for their generic input links.
     *
     * @param   plink   the link
     * @param   lo      lowest accepted value
     * @param   hi      highest accepted value
     * @returns status value
     */
    long (*getGraphicLimits)(const struct link *plink, double *lo, double *hi);

    /** @brief  get the alarm limits from an input link
     *
     * Called to fetch the alarm limits for an input link target, as four
     * double values for the warning and alarm levels that the PV checks its
     * value against. This method is used by several built-in record types to
     * obtain the alarm limits for their generic input links.
     *
     * @param   plink   the link
     * @param   lolo    low alarm value
     * @param   lo      low warning value
     * @param   hi      high warning value
     * @param   hihi    high alarm value
     * @returns status value
     */
    long (*getAlarmLimits)(const struct link *plink, double *lolo, double *lo,
            double *hi, double *hihi);

    /** @brief  get the precision from an input link
     *
     * Called to fetch the precision for an input link target. This method is
     * used by several built-in record types to obtain the precision for their
     * generic input links.
     *
     * @param   plink       the link
     * @param   precision   where to put the answer
     * @returns status value
     */
    long (*getPrecision)(const struct link *plink, short *precision);

    /** @brief  get the units string from an input link
     *
     * Called to fetch the units string for an input link target. This method is
     * used by several built-in record types to obtain the units string for
     * their generic input links.
     *
     * @param   plink       the link
     * @param   units       where to put the answer
     * @param   unitsSize   buffer size for the answer
     * @returns status value
     */
    long (*getUnits)(const struct link *plink, char *units, int unitsSize);

    /** @brief  get the alarm condition from an input link
     *
     * Called to fetch the alarm status and severity for an input link target.
     * Either status or severity pointers may be NULL when that value is not
     * needed by the calling code. This method is used by several built-in
     * record types to obtain the alarm condition for their generic input links.
     *
     * @param   plink       the link
     * @param   status      where to put the alarm status (or NULL)
     * @param   severity    where to put the severity (or NULL)
     * @returns status value
     */
    long (*getAlarm)(const struct link *plink, epicsEnum16 *status,
            epicsEnum16 *severity);

    /** @brief  get the time-stamp from an input link
     *
     * Called to fetch the time-stamp for an input link target. This method is
     * used by many built-in device supports to obtain the precision for their
     * generic input links.
     *
     * @param   plink   the link
     * @param   pstamp  where to put the answer
     * @returns status value
     */
    long (*getTimeStamp)(const struct link *plink, epicsTimeStamp *pstamp);

    /* Put data */

    /** @brief  put a value to an output link
     *
     * Called to send nRequest elements of type dbrType found at pbuffer to an
     * output link target.
     *
     * @param   plink       the link
     * @param   dbrType     data type code
     * @param   pbuffer     where to put the value
     * @param   nRequest    number of elements to send
     * @returns status value
     */
    long (*putValue)(struct link *plink, short dbrType,
            const void *pbuffer, long nRequest);

    /** @brief  put a value to an output link with asynchronous completion
     *
     * Called to send nRequest elements of type dbrType found at pbuffer to an
     * output link target. If the return status is zero, the link type will
     * later indicate the put has completed by calling dbLinkAsyncComplete()
     * from a background thread, which will be used to continue the record
     * process operation from where it left off.
     *
     * @param   plink       the link
     * @param   dbrType     data type code
     * @param   pbuffer     where to put the value
     * @param   nRequest    number of elements to send
     * @returns status value
     */
    long (*putAsync)(struct link *plink, short dbrType,
            const void *pbuffer, long nRequest);

    /* Process */

    /** @brief  trigger processing of a forward link
     *
     * Called to trigger processing of the record pointed to by a forward link.
     * This routine is optional, but if not provided no warning message will be
     * shown when called by dbScanFwdLink(). JSON link types that do not support
     * this operation should return NULL from their jlif::alloc_jlink() method
     * if it gets called with a dbfType of DBF_FWDLINK.
     *
     * @param   plink       the link
     */
    void (*scanForward)(struct link *plink);

    /* Atomicity */

    /** @brief  execute a callback routine with link locked
     *
     * Called on an input link when multiple link attributes need to be fetched
     * in an atomic fashion. The link type must call the callback routine and
     * prevent any background I/O from updating any cached link data until that
     * routine returns. This method is used by most input device support to
     * fetch the timestamp along with the value when the record's TSE field is
     * set to epicsTimeEventDeviceTime.
     *
     * @param   plink   the link
     * @param   rtn     routine to execute
     * @returns status value
     */
    long (*doLocked)(struct link *plink, dbLinkUserCallback rtn, void *priv);

    /** @brief Extended version of getAlarm
     *
     * Equivalent of getAlarm() and also copy out alarm message string.
     * The msgbuf argument may be NULL and/or msgbuflen==0, in which case
     * the effect must be the same as a call to getAlarm().
     *
     * Implementations must write a trailing nil to msgbuf whenever
     * @code msgbuf!=NULL && msgbuflen>0 @endcode .
     *
     * @since UNRELEASED
     */
    long (*getAlarmMsg)(const struct link *plink, epicsEnum16 *status,
                        epicsEnum16 *severity, char *msgbuf, size_t msgbuflen);

    /** @brief Extended version of getTimeStamp
     *
     * Equivalent of getTimeStamp() and also copy out time tag.
     * ptag may be NULL.
     *
     * @since Added after UNRELEASED
     */
    long (*getTimeStampTag)(const struct link *plink, epicsTimeStamp *pstamp, epicsUTag *ptag);
} lset;

#define dbGetSevr(link, sevr) \
    dbGetAlarm(link, NULL, sevr)

DBCORE_API const char * dbLinkFieldName(const struct link *plink);

DBCORE_API void dbInitLink(struct link *plink, short dbfType);
DBCORE_API void dbAddLink(struct dbLocker *locker, struct link *plink,
        short dbfType, dbChannel *ptarget);

DBCORE_API void dbLinkOpen(struct link *plink);
DBCORE_API void dbRemoveLink(struct dbLocker *locker, struct link *plink);

DBCORE_API int dbLinkIsDefined(const struct link *plink);  /* 0 or 1 */
DBCORE_API int dbLinkIsConstant(const struct link *plink); /* 0 or 1 */
DBCORE_API int dbLinkIsVolatile(const struct link *plink); /* 0 or 1 */

DBCORE_API long dbLoadLink(struct link *plink, short dbrType,
        void *pbuffer);
DBCORE_API long dbLoadLinkArray(struct link *, short dbrType, void *pbuffer,
        long *pnRequest);

DBCORE_API long dbGetNelements(const struct link *plink, long *pnElements);
DBCORE_API int dbIsLinkConnected(const struct link *plink); /* 0 or 1 */
DBCORE_API int dbGetLinkDBFtype(const struct link *plink);
DBCORE_API long dbTryGetLink(struct link *, short dbrType, void *pbuffer,
        long *nRequest);
DBCORE_API long dbGetLink(struct link *, short dbrType, void *pbuffer,
        long *options, long *nRequest);
DBCORE_API long dbGetControlLimits(const struct link *plink, double *low,
        double *high);
DBCORE_API long dbGetGraphicLimits(const struct link *plink, double *low,
        double *high);
DBCORE_API long dbGetAlarmLimits(const struct link *plink, double *lolo,
        double *low, double *high, double *hihi);
DBCORE_API long dbGetPrecision(const struct link *plink, short *precision);
DBCORE_API long dbGetUnits(const struct link *plink, char *units,
        int unitsSize);
DBCORE_API long dbGetAlarm(const struct link *plink, epicsEnum16 *status,
        epicsEnum16 *severity);
/** Get link alarm and message string.
 * To ensure the complete message string is copied, ensure @code msgbuflen >= sizeof (dbCommon::amsg) @endcode .
 * A trailing nil will be added whenever @code msgbuflen > 0 @endcode .
 * @since UNRELEASED
 */
DBCORE_API long dbGetAlarmMsg(const struct link *plink, epicsEnum16 *status,
        epicsEnum16 *severity, char *msgbuf, size_t msgbuflen);
#define dbGetAlarmMsg(LINK, STAT, SEVR, BUF, BUFLEN) dbGetAlarmMsg(LINK, STAT, SEVR, BUF, BUFLEN)
DBCORE_API long dbGetTimeStamp(const struct link *plink,
        epicsTimeStamp *pstamp);
/** @since UNRELEASED */
DBCORE_API long dbGetTimeStampTag(const struct link *plink,
        epicsTimeStamp *pstamp, epicsUTag *ptag);
#define dbGetTimeStampTag(LINK, STAMP, TAG) dbGetTimeStampTag(LINK, STAMP, TAG)
DBCORE_API long dbPutLink(struct link *plink, short dbrType,
        const void *pbuffer, long nRequest);
DBCORE_API void dbLinkAsyncComplete(struct link *plink);
DBCORE_API long dbPutLinkAsync(struct link *plink, short dbrType,
        const void *pbuffer, long nRequest);
DBCORE_API void dbScanFwdLink(struct link *plink);

DBCORE_API long dbLinkDoLocked(struct link *plink, dbLinkUserCallback rtn,
        void *priv);

DBCORE_API long dbLoadLinkLS(struct link *plink, char *pbuffer,
        epicsUInt32 size, epicsUInt32 *plen);
DBCORE_API long dbGetLinkLS(struct link *plink, char *pbuffer,
        epicsUInt32 buffer_size, epicsUInt32 *plen);
DBCORE_API long dbPutLinkLS(struct link *plink, char *pbuffer,
        epicsUInt32 len);

#ifdef __cplusplus
}
#endif

#endif /* INC_dbLink_H */
