//go:build epics

package epics

/*
#cgo CFLAGS: -Wno-unused-function
#include "ca_wrapper.h"
*/
import "C"

import (
	"time"
	"unsafe"

	"github.com/uopi/uopi/internal/datasource"
)

// goCAMonitorCallback is called from the C shim caMonitorCallbackShim on every
// CA monitor event.  It runs on CA's internal callback thread, so it must be
// short and must not block.  In particular it must not allocate heap memory
// that the Go GC would need to track across a CGo boundary.
//
//export goCAMonitorCallback
func goCAMonitorCallback(handle C.uintptr_t, dbrType C.int, count C.long,
	dbr unsafe.Pointer, severity C.int, status C.int, epicsTimeSecs C.double) {

	h := uintptr(handle)

	// Compute the Go timestamp.  EPICS timestamps are seconds since the EPICS
	// epoch (1 Jan 1990 UTC).
	const epicsEpochOffset = 631152000 // Unix seconds of 1990-01-01 00:00:00 UTC
	secs := float64(epicsTimeSecs)
	fullSecs := int64(secs)
	nsec := int64((secs - float64(fullSecs)) * 1e9)
	ts := time.Unix(fullSecs+epicsEpochOffset, nsec)

	// Map CA alarm severity to datasource quality.
	var q datasource.Quality
	switch int(severity) {
	case 0: // NO_ALARM
		q = datasource.QualityGood
	case 1, 2: // MINOR_ALARM, MAJOR_ALARM
		q = datasource.QualityUncertain
	default: // INVALID_ALARM (3)
		q = datasource.QualityBad
	}

	// Extract the value from the DBR buffer.
	var data any
	switch int(dbrType) {
	case int(C.DBR_TIME_DOUBLE):
		p := (*C.struct_dbr_time_double)(dbr)
		data = float64(p.value)

	case int(C.DBR_TIME_FLOAT):
		p := (*C.struct_dbr_time_float)(dbr)
		data = float64(p.value)

	case int(C.DBR_TIME_LONG):
		p := (*C.struct_dbr_time_long)(dbr)
		data = int64(p.value)

	case int(C.DBR_TIME_SHORT):
		p := (*C.struct_dbr_time_short)(dbr)
		data = int64(p.value)

	case int(C.DBR_TIME_ENUM):
		p := (*C.struct_dbr_time_enum)(dbr)
		data = int64(p.value)

	case int(C.DBR_TIME_STRING):
		p := (*C.struct_dbr_time_string)(dbr)
		// dbr_string is a fixed [MAX_STRING_SIZE]byte array
		data = C.GoString((*C.char)(unsafe.Pointer(&p.value[0])))

	default:
		// Unknown type; mark as bad quality and return early.
		q = datasource.QualityBad
		data = float64(0)
	}

	v := datasource.Value{
		Timestamp: ts,
		Data:      data,
		Quality:   q,
		Severity:  int(severity),
		Status:    int(status),
	}

	// Look up the subscription channel under the global handle table lock and
	// perform a non-blocking send so we never stall the CA callback thread.
	handleMu.Lock()
	ch, ok := handleTable[h]
	handleMu.Unlock()

	if ok {
		select {
		case ch <- v:
		default:
			// Subscriber is not reading fast enough; drop the update rather than
			// block the CA callback thread.
		}
	}
}

// goCAConnectionCallback is called from the C shim caConnectionCallbackShim
// whenever a channel connects or disconnects.
//
//export goCAConnectionCallback
func goCAConnectionCallback(handle C.uintptr_t, connected C.int) {
	h := uintptr(handle)

	handleMu.Lock()
	entry, ok := connTable[h]
	handleMu.Unlock()

	if !ok {
		return
	}

	if int(connected) == 1 {
		// Signal the one-shot "connected" channel if it has not been closed yet.
		select {
		case entry.connCh <- struct{}{}:
		default:
		}
	} else {
		// Channel disconnected; push a QualityBad sentinel to the subscriber.
		handleMu.Lock()
		ch, chOk := handleTable[h]
		handleMu.Unlock()

		if chOk {
			v := datasource.Value{
				Timestamp: time.Now(),
				Data:      float64(0),
				Quality:   datasource.QualityBad,
				Severity:  3, // INVALID
			}
			select {
			case ch <- v:
			default:
			}
		}
	}
}
