#ifndef CA_WRAPPER_H
#define CA_WRAPPER_H

#include <cadef.h>
#include <db_access.h>
#include <stdint.h>

/*
 * Go-exported callback declarations.
 * These are implemented in ca_callback.go (via //export directives).
 *
 * goCAMonitorCallback is called for each value update received from CA.
 * goCAConnectionCallback is called when a channel connects or disconnects.
 */
extern void goCAMonitorCallback(uintptr_t handle, int dbrType, long count,
                                void *dbr, int severity, int status,
                                double epicsTimeSecs);
extern void goCAConnectionCallback(uintptr_t handle, int connected);

/*
 * CA monitor callback shim.
 *
 * CA calls this function with the fixed evargs signature.  We extract the
 * relevant fields and forward them to the Go callback.
 */
static void caMonitorCallbackShim(struct event_handler_args args) {
    if (args.status != ECA_NORMAL) {
        return;
    }

    double timeSecs = 0.0;
    int    severity = 0;
    int    status   = 0;

    /*
     * Determine the timestamp and alarm severity/status from the DBR type.
     * We request DBR_TIME_* types so the timestamp is embedded in the value
     * buffer right after the alarm fields (struct dbr_time_double et al.).
     */
    switch (args.type) {
    case DBR_TIME_DOUBLE: {
        const struct dbr_time_double *p = (const struct dbr_time_double *)args.dbr;
        timeSecs = (double)p->stamp.secPastEpoch + (double)p->stamp.nsec / 1e9;
        severity = (int)p->severity;
        status   = (int)p->status;
        break;
    }
    case DBR_TIME_FLOAT: {
        const struct dbr_time_float *p = (const struct dbr_time_float *)args.dbr;
        timeSecs = (double)p->stamp.secPastEpoch + (double)p->stamp.nsec / 1e9;
        severity = (int)p->severity;
        status   = (int)p->status;
        break;
    }
    case DBR_TIME_LONG: {
        const struct dbr_time_long *p = (const struct dbr_time_long *)args.dbr;
        timeSecs = (double)p->stamp.secPastEpoch + (double)p->stamp.nsec / 1e9;
        severity = (int)p->severity;
        status   = (int)p->status;
        break;
    }
    case DBR_TIME_SHORT: {
        const struct dbr_time_short *p = (const struct dbr_time_short *)args.dbr;
        timeSecs = (double)p->stamp.secPastEpoch + (double)p->stamp.nsec / 1e9;
        severity = (int)p->severity;
        status   = (int)p->status;
        break;
    }
    case DBR_TIME_STRING: {
        const struct dbr_time_string *p = (const struct dbr_time_string *)args.dbr;
        timeSecs = (double)p->stamp.secPastEpoch + (double)p->stamp.nsec / 1e9;
        severity = (int)p->severity;
        status   = (int)p->status;
        break;
    }
    case DBR_TIME_ENUM: {
        const struct dbr_time_enum *p = (const struct dbr_time_enum *)args.dbr;
        timeSecs = (double)p->stamp.secPastEpoch + (double)p->stamp.nsec / 1e9;
        severity = (int)p->severity;
        status   = (int)p->status;
        break;
    }
    default:
        break;
    }

    goCAMonitorCallback((uintptr_t)args.usr, (int)args.type, (long)args.count,
                        (void *)args.dbr, severity, status, timeSecs);
}

/*
 * CA connection callback shim.
 *
 * CA calls this with a connection_handler_args struct.  We map the op field
 * to a simple boolean (1 = connected, 0 = disconnected) and forward.
 */
static void caConnectionCallbackShim(struct connection_handler_args args) {
    int connected = (args.op == CA_OP_CONN_UP) ? 1 : 0;
    goCAConnectionCallback((uintptr_t)ca_puser(args.chid), connected);
}

/*
 * Thin helpers so CGo callers do not need to deal with C function-pointer
 * syntax directly.  All CA API calls stay in C to avoid issues with CGo
 * passing Go pointers across the CA callback boundary.
 */

/* Create a channel with the connection shim as the connection callback.
 * user_handle is the Go uintptr_t that identifies this channel in the handle
 * table.  Returns the ECA_* status code. */
static int caCreateChannel(const char *pvName, uintptr_t userHandle,
                           chid *pChid) {
    return ca_create_channel(pvName, caConnectionCallbackShim,
                             (void *)userHandle, CA_PRIORITY_DEFAULT, pChid);
}

/* Add a value-monitor event on chid.  Returns the ECA_* status code and stores
 * the evid in *pEvid. */
static int caAddMonitor(chid ch, short dbrType, uintptr_t userHandle,
                        evid *pEvid) {
    return ca_add_event(dbrType, ch, caMonitorCallbackShim,
                        (void *)userHandle, pEvid);
}

/* Wrappers for ca_get / ca_put, which are macros in cadef.h that expand to
 * ca_array_get / ca_array_put with count=1.  CGo cannot call macros directly,
 * so we provide thin static-inline shims here. */

static int caGet(chtype type, chid chan, void *pValue) {
    return ca_array_get(type, 1u, chan, pValue);
}

static int caPut(chtype type, chid chan, const void *pValue) {
    return ca_array_put(type, 1u, chan, pValue);
}

/* Retrieve the current thread's CA context as an opaque pointer.
 * Store the result and pass it to caAttachContext() from other threads. */
static void *caCurrentContext(void) {
    return (void *)ca_current_context();
}

/* Attach an existing CA context to the calling thread.  Must be called at the
 * start of every goroutine (OS thread) that makes CA calls, if that thread did
 * not call ca_context_create() itself.  Returns ECA_NORMAL on success. */
static int caAttachContext(void *ctx) {
    return ca_attach_context((struct ca_client_context *)ctx);
}

#endif /* CA_WRAPPER_H */
