//go:build soapy && !windows

package soapysdr

import (
	"fmt"
	"log"
	"math"
	"sort"
	"unsafe"
)

/*
#include <dlfcn.h>
#include <stdlib.h>
#include <stdint.h>

// Minimal dlopen wrapper — this is the ONLY cgo usage and it's
// just for dlopen/dlsym which are part of libc, not SoapySDR.
// No SoapySDR headers needed at compile time.

static void* soapy_dlopen(const char* path) {
    return dlopen(path, 2); // RTLD_NOW
}

static void* soapy_dlsym(void* handle, const char* name) {
    return dlsym(handle, name);
}

static const char* soapy_dlerror() {
    return dlerror();
}

// Try to resolve SoapySDR_getLastError dynamically when available.
typedef const char* (*last_error_fn)(void);
static const char* call_last_error(void* fn) {
    if (fn == NULL) return NULL;
    return ((last_error_fn)fn)();
}

// Function call trampolines — we call function pointers loaded via dlsym.
// These avoid the complexity of calling C function pointers from Go directly.

typedef void* (*make_fn)(void*);
typedef int (*unmake_fn)(void*);
typedef int (*set_double_fn)(void*, int, size_t, double);
typedef int (*set_freq_fn)(void*, int, size_t, double, void*);
typedef void* (*setup_stream_fn)(void*, int, const char*, size_t*, size_t, void*);
typedef int (*close_stream_fn)(void*, void*);
typedef size_t (*mtu_fn)(void*, void*);
typedef int (*activate_fn)(void*, void*, int, long long, size_t);
typedef int (*deactivate_fn)(void*, void*, int, long long);
typedef int (*write_fn)(void*, void*, const void**, size_t, int*, long long, long);
typedef void* (*enumerate_fn)(void*, size_t*);
typedef void (*kwargs_clear_fn)(void*, size_t);
typedef void (*kwargs_set_fn)(void*, const char*, const char*);

// --- KWArgs struct matching SoapySDRKwargs ---
typedef struct {
    size_t size;
    char** keys;
    char** vals;
} GoKwargs;

static void* call_make(void* fn, void* args) {
    return ((make_fn)fn)(args);
}
static int call_unmake(void* fn, void* dev) {
    return ((unmake_fn)fn)(dev);
}
static int call_set_sample_rate(void* fn, void* dev, int dir, size_t ch, double rate) {
    return ((set_double_fn)fn)(dev, dir, ch, rate);
}
static int call_set_frequency(void* fn, void* dev, int dir, size_t ch, double freq, void* kw) {
    return ((set_freq_fn)fn)(dev, dir, ch, freq, kw);
}
static int call_set_gain(void* fn, void* dev, int dir, size_t ch, double gain) {
    return ((set_double_fn)fn)(dev, dir, ch, gain);
}
static void* call_setup_stream(void* fn, void* dev, int dir, const char* fmt, size_t* chs, size_t nch, void* kw) {
    return ((setup_stream_fn)fn)(dev, dir, fmt, chs, nch, kw);
}
static int call_close_stream(void* fn, void* dev, void* stream) {
    return ((close_stream_fn)fn)(dev, stream);
}
static size_t call_mtu(void* fn, void* dev, void* stream) {
    return ((mtu_fn)fn)(dev, stream);
}
static int call_activate(void* fn, void* dev, void* stream) {
    return ((activate_fn)fn)(dev, stream, 0, 0, 0);
}
static int call_deactivate(void* fn, void* dev, void* stream) {
    return ((deactivate_fn)fn)(dev, stream, 0, 0);
}
static int call_write(void* fn, void* dev, void* stream, const void* buf, size_t n, int* flags, long timeout) {
    const void* buffs[1];
    buffs[0] = buf;
    *flags = 0;
    return ((write_fn)fn)(dev, stream, buffs, n, flags, 0, timeout);
}
static void* call_enumerate(void* fn, void* kw, size_t* length) {
    return ((enumerate_fn)fn)(kw, length);
}
static void call_kwargs_clear(void* fn, void* list, size_t length) {
    ((kwargs_clear_fn)fn)(list, length);
}
static void call_kwargs_set(void* fn, void* kw, const char* key, const char* val) {
    ((kwargs_set_fn)fn)(kw, key, val);
}
*/
import "C"

type soapyLib struct {
	handle              unsafe.Pointer
	fnEnumerate         unsafe.Pointer
	fnKwargsListClear   unsafe.Pointer
	fnKwargsSet         unsafe.Pointer
	fnMake              unsafe.Pointer
	fnUnmake            unsafe.Pointer
	fnSetSampleRate     unsafe.Pointer
	fnSetFrequency      unsafe.Pointer
	fnSetGain           unsafe.Pointer
	fnGetGainRange      unsafe.Pointer
	fnSetupStream       unsafe.Pointer
	fnCloseStream       unsafe.Pointer
	fnGetStreamMTU      unsafe.Pointer
	fnActivateStream    unsafe.Pointer
	fnDeactivateStream  unsafe.Pointer
	fnWriteStream       unsafe.Pointer
	fnGetLastError      unsafe.Pointer
}

var libNames = []string{
	"libSoapySDR.so.0.8",
	"libSoapySDR.so",
	"libSoapySDR.dylib",
}

func loadSoapyLib() (*soapyLib, error) {
	var handle unsafe.Pointer
	for _, name := range libNames {
		cName := C.CString(name)
		handle = C.soapy_dlopen(cName)
		C.free(unsafe.Pointer(cName))
		if handle != nil {
			break
		}
	}
	if handle == nil {
		errMsg := C.GoString(C.soapy_dlerror())
		return nil, fmt.Errorf("cannot load SoapySDR: %s", errMsg)
	}

	sym := func(name string) unsafe.Pointer {
		cName := C.CString(name)
		defer C.free(unsafe.Pointer(cName))
		return C.soapy_dlsym(handle, cName)
	}

	return &soapyLib{
		handle:             handle,
		fnEnumerate:        sym("SoapySDRDevice_enumerate"),
		fnKwargsListClear:  sym("SoapySDRKwargsList_clear"),
		fnKwargsSet:        sym("SoapySDRKwargs_set"),
		fnMake:             sym("SoapySDRDevice_make"),
		fnUnmake:           sym("SoapySDRDevice_unmake"),
		fnSetSampleRate:    sym("SoapySDRDevice_setSampleRate"),
		fnSetFrequency:     sym("SoapySDRDevice_setFrequency"),
		fnSetGain:          sym("SoapySDRDevice_setGain"),
		fnGetGainRange:     sym("SoapySDRDevice_getGainRange"),
		fnSetupStream:      sym("SoapySDRDevice_setupStream"),
		fnCloseStream:      sym("SoapySDRDevice_closeStream"),
		fnGetStreamMTU:     sym("SoapySDRDevice_getStreamMTU"),
		fnActivateStream:   sym("SoapySDRDevice_activateStream"),
		fnDeactivateStream: sym("SoapySDRDevice_deactivateStream"),
		fnWriteStream:      sym("SoapySDRDevice_writeStream"),
		fnGetLastError:     sym("SoapySDR_getLastError"),
	}, nil
}

// --- kwargs helper ---

type kwargs = C.GoKwargs

func (lib *soapyLib) kwargsSet(kw *kwargs, key, val string) {
	if lib.fnKwargsSet == nil { return }
	cK := C.CString(key); cV := C.CString(val)
	defer C.free(unsafe.Pointer(cK)); defer C.free(unsafe.Pointer(cV))
	C.call_kwargs_set(lib.fnKwargsSet, unsafe.Pointer(kw), cK, cV)
}

// --- Enumerate ---

func (lib *soapyLib) enumerate() ([]map[string]string, error) {
	if lib.fnEnumerate == nil { return nil, fmt.Errorf("enumerate not available") }
	var kw kwargs
	var length C.size_t
	ret := C.call_enumerate(lib.fnEnumerate, unsafe.Pointer(&kw), &length)
	if ret == nil || length == 0 { return nil, nil }
	defer func() {
		if lib.fnKwargsListClear != nil {
			C.call_kwargs_clear(lib.fnKwargsListClear, ret, length)
		}
	}()

	devices := make([]map[string]string, 0, int(length))
	kwSize := unsafe.Sizeof(C.GoKwargs{})
	base := uintptr(ret)
	for i := 0; i < int(length); i++ {
		entry := (*C.GoKwargs)(unsafe.Pointer(base + uintptr(i)*kwSize))
		m := make(map[string]string)
		for j := 0; j < int(entry.size); j++ {
			keyPtr := *(**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(entry.keys)) + uintptr(j)*unsafe.Sizeof(uintptr(0))))
			valPtr := *(**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(entry.vals)) + uintptr(j)*unsafe.Sizeof(uintptr(0))))
			if keyPtr != nil && valPtr != nil {
				m[C.GoString(keyPtr)] = C.GoString(valPtr)
			}
		}
		devices = append(devices, m)
	}
	return devices, nil
}

// --- Device ---

func (lib *soapyLib) makeDevice(driver, device string, args map[string]string) (uintptr, error) {
	if lib.fnMake == nil { return 0, fmt.Errorf("make not available") }
	var kw kwargs
	if driver != "" { lib.kwargsSet(&kw, "driver", driver) }
	if device != "" { lib.kwargsSet(&kw, "device", device) }

	keys := make([]string, 0, len(args))
	for k := range args {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		lib.kwargsSet(&kw, k, args[k])
	}

	log.Printf("soapy: makeDevice driver=%q device=%q args=%v", driver, device, args)
	ret := C.call_make(lib.fnMake, unsafe.Pointer(&kw))
	if ret == nil {
		msg := ""
		if lib.fnGetLastError != nil {
			if p := C.call_last_error(lib.fnGetLastError); p != nil {
				msg = C.GoString(p)
			}
		}
		if msg != "" {
			return 0, fmt.Errorf("soapy: failed to open device: %s", msg)
		}
		return 0, fmt.Errorf("soapy: failed to open device")
	}
	return uintptr(ret), nil
}

func (lib *soapyLib) unmakeDevice(dev uintptr) {
	if lib.fnUnmake != nil { C.call_unmake(lib.fnUnmake, unsafe.Pointer(dev)) }
}

// --- Config ---

func (lib *soapyLib) setSampleRate(dev uintptr, dir, ch int, rate float64) error {
	if lib.fnSetSampleRate == nil { return fmt.Errorf("not available") }
	rc := C.call_set_sample_rate(lib.fnSetSampleRate, unsafe.Pointer(dev), C.int(dir), C.size_t(ch), C.double(rate))
	if rc != 0 { return fmt.Errorf("soapy: setSampleRate(%.0f) failed: %d", rate, rc) }
	return nil
}

func (lib *soapyLib) setFrequency(dev uintptr, dir, ch int, freq float64) error {
	if lib.fnSetFrequency == nil { return fmt.Errorf("not available") }
	var kw kwargs
	rc := C.call_set_frequency(lib.fnSetFrequency, unsafe.Pointer(dev), C.int(dir), C.size_t(ch), C.double(freq), unsafe.Pointer(&kw))
	if rc != 0 { return fmt.Errorf("soapy: setFrequency(%.0f) failed: %d", freq, rc) }
	return nil
}

func (lib *soapyLib) setGain(dev uintptr, dir, ch int, gain float64) error {
	if lib.fnSetGain == nil { return nil }
	C.call_set_gain(lib.fnSetGain, unsafe.Pointer(dev), C.int(dir), C.size_t(ch), C.double(gain))
	return nil
}

func (lib *soapyLib) getGainRange(dev uintptr, dir, ch int) (float64, float64) {
	_ = math.Float64bits // keep import
	// Fallback if function not available
	return 0, 89
}

// --- Stream ---

func (lib *soapyLib) setupStream(dev uintptr, dir int, format string, channels []uint) (uintptr, error) {
	if lib.fnSetupStream == nil { return 0, fmt.Errorf("not available") }
	cFmt := C.CString(format); defer C.free(unsafe.Pointer(cFmt))
	chs := make([]C.size_t, len(channels))
	for i, c := range channels { chs[i] = C.size_t(c) }
	var chPtr *C.size_t
	if len(chs) > 0 { chPtr = &chs[0] }
	var kw kwargs
	ret := C.call_setup_stream(lib.fnSetupStream, unsafe.Pointer(dev), C.int(dir), cFmt, chPtr, C.size_t(len(channels)), unsafe.Pointer(&kw))
	if ret == nil { return 0, fmt.Errorf("soapy: setupStream failed") }
	return uintptr(ret), nil
}

func (lib *soapyLib) closeStream(dev, stream uintptr) {
	if lib.fnCloseStream != nil {
		C.call_close_stream(lib.fnCloseStream, unsafe.Pointer(dev), unsafe.Pointer(stream))
	}
}

func (lib *soapyLib) getStreamMTU(dev, stream uintptr) int {
	if lib.fnGetStreamMTU == nil { return 4096 }
	ret := C.call_mtu(lib.fnGetStreamMTU, unsafe.Pointer(dev), unsafe.Pointer(stream))
	if ret == 0 { return 4096 }
	return int(ret)
}

func (lib *soapyLib) activateStream(dev, stream uintptr) error {
	if lib.fnActivateStream == nil { return fmt.Errorf("not available") }
	rc := C.call_activate(lib.fnActivateStream, unsafe.Pointer(dev), unsafe.Pointer(stream))
	if rc != 0 { return fmt.Errorf("soapy: activateStream failed: %d", rc) }
	return nil
}

func (lib *soapyLib) deactivateStream(dev, stream uintptr) {
	if lib.fnDeactivateStream != nil {
		C.call_deactivate(lib.fnDeactivateStream, unsafe.Pointer(dev), unsafe.Pointer(stream))
	}
}

func (lib *soapyLib) writeStream(dev, stream uintptr, buf unsafe.Pointer, numElems int) (int, error) {
	if lib.fnWriteStream == nil { return 0, fmt.Errorf("not available") }
	var flags C.int
	rc := C.call_write(lib.fnWriteStream, unsafe.Pointer(dev), unsafe.Pointer(stream), buf, C.size_t(numElems), &flags, 100000)
	if rc < 0 { return 0, fmt.Errorf("soapy: writeStream returned %d", rc) }
	return int(rc), nil
}
