//go:build soapy && windows

package soapysdr

import (
	"fmt"
	"math"
	"syscall"
	"unsafe"
)

type soapyLib struct {
	dll                    *syscall.DLL
	pEnumerate             *syscall.Proc
	pKwargsListClear       *syscall.Proc
	pMake                  *syscall.Proc
	pUnmake                *syscall.Proc
	pSetSampleRate         *syscall.Proc
	pSetFrequency          *syscall.Proc
	pSetGain               *syscall.Proc
	pGetGainRange          *syscall.Proc
	pSetupStream           *syscall.Proc
	pCloseStream           *syscall.Proc
	pGetStreamMTU          *syscall.Proc
	pActivateStream        *syscall.Proc
	pDeactivateStream      *syscall.Proc
	pWriteStream           *syscall.Proc
	pKwargsSet             *syscall.Proc
}

var searchPaths = []string{
	"SoapySDR",
	`C:\Program Files\PothosSDR\bin\SoapySDR.dll`,
	`C:\PothosSDR\bin\SoapySDR.dll`,
}

func loadSoapyLib() (*soapyLib, error) {
	var dll *syscall.DLL
	var lastErr error
	for _, path := range searchPaths {
		dll, lastErr = syscall.LoadDLL(path)
		if dll != nil {
			break
		}
	}
	if dll == nil {
		return nil, fmt.Errorf("cannot load SoapySDR.dll: %v (searched: %v)", lastErr, searchPaths)
	}

	mustProc := func(name string) *syscall.Proc {
		p, _ := dll.FindProc(name)
		return p
	}

	return &soapyLib{
		dll:               dll,
		pEnumerate:        mustProc("SoapySDRDevice_enumerate"),
		pKwargsListClear:  mustProc("SoapySDRKwargsList_clear"),
		pMake:             mustProc("SoapySDRDevice_make"),
		pUnmake:           mustProc("SoapySDRDevice_unmake"),
		pSetSampleRate:    mustProc("SoapySDRDevice_setSampleRate"),
		pSetFrequency:     mustProc("SoapySDRDevice_setFrequency"),
		pSetGain:          mustProc("SoapySDRDevice_setGain"),
		pGetGainRange:     mustProc("SoapySDRDevice_getGainRange"),
		pSetupStream:      mustProc("SoapySDRDevice_setupStream"),
		pCloseStream:      mustProc("SoapySDRDevice_closeStream"),
		pGetStreamMTU:     mustProc("SoapySDRDevice_getStreamMTU"),
		pActivateStream:   mustProc("SoapySDRDevice_activateStream"),
		pDeactivateStream: mustProc("SoapySDRDevice_deactivateStream"),
		pWriteStream:      mustProc("SoapySDRDevice_writeStream"),
		pKwargsSet:        mustProc("SoapySDRKwargs_set"),
	}, nil
}

// --- KWArgs helper (stack-allocated 64-byte struct matching SoapySDRKwargs) ---
// SoapySDRKwargs = { size_t size; char** keys; char** vals; }
// On 64-bit: 8 + 8 + 8 = 24 bytes

type kwargs struct {
	size uintptr
	keys uintptr
	vals uintptr
}

func (lib *soapyLib) kwargsSet(kw *kwargs, key, val string) {
	if lib.pKwargsSet == nil {
		return
	}
	cKey, _ := syscall.BytePtrFromString(key)
	cVal, _ := syscall.BytePtrFromString(val)
	lib.pKwargsSet.Call(uintptr(unsafe.Pointer(kw)), uintptr(unsafe.Pointer(cKey)), uintptr(unsafe.Pointer(cVal)))
}

// --- Enumerate ---

func (lib *soapyLib) enumerate() ([]map[string]string, error) {
	if lib.pEnumerate == nil {
		return nil, fmt.Errorf("SoapySDRDevice_enumerate not found")
	}
	var kw kwargs
	var length uintptr
	ret, _, _ := lib.pEnumerate.Call(uintptr(unsafe.Pointer(&kw)), uintptr(unsafe.Pointer(&length)))
	if ret == 0 || length == 0 {
		return nil, nil
	}
	defer func() {
		if lib.pKwargsListClear != nil {
			lib.pKwargsListClear.Call(ret, length)
		}
	}()

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

// --- Device lifecycle ---

func (lib *soapyLib) makeDevice(driver, device string, args map[string]string) (uintptr, error) {
	if lib.pMake == nil {
		return 0, fmt.Errorf("SoapySDRDevice_make not found")
	}
	var kw kwargs
	if driver != "" {
		lib.kwargsSet(&kw, "driver", driver)
	}
	if device != "" {
		lib.kwargsSet(&kw, "device", device)
	}
	for k, v := range args {
		lib.kwargsSet(&kw, k, v)
	}
	ret, _, _ := lib.pMake.Call(uintptr(unsafe.Pointer(&kw)))
	if ret == 0 {
		return 0, fmt.Errorf("soapy: failed to open device (driver=%q device=%q)", driver, device)
	}
	return ret, nil
}

func (lib *soapyLib) unmakeDevice(dev uintptr) {
	if lib.pUnmake != nil && dev != 0 {
		lib.pUnmake.Call(dev)
	}
}

// --- Configuration ---

func (lib *soapyLib) setSampleRate(dev uintptr, dir, ch int, rate float64) error {
	if lib.pSetSampleRate == nil {
		return fmt.Errorf("setSampleRate not available")
	}
	bits := math.Float64bits(rate)
	rc, _, _ := lib.pSetSampleRate.Call(dev, uintptr(dir), uintptr(ch), uintptr(bits))
	if int32(rc) != 0 {
		return fmt.Errorf("soapy: setSampleRate(%.0f) failed: %d", rate, int32(rc))
	}
	return nil
}

func (lib *soapyLib) setFrequency(dev uintptr, dir, ch int, freq float64) error {
	if lib.pSetFrequency == nil {
		return fmt.Errorf("setFrequency not available")
	}
	bits := math.Float64bits(freq)
	var kw kwargs
	rc, _, _ := lib.pSetFrequency.Call(dev, uintptr(dir), uintptr(ch), uintptr(bits), uintptr(unsafe.Pointer(&kw)))
	if int32(rc) != 0 {
		return fmt.Errorf("soapy: setFrequency(%.0f) failed: %d", freq, int32(rc))
	}
	return nil
}

func (lib *soapyLib) setGain(dev uintptr, dir, ch int, gain float64) error {
	if lib.pSetGain == nil {
		return nil
	}
	bits := math.Float64bits(gain)
	lib.pSetGain.Call(dev, uintptr(dir), uintptr(ch), uintptr(bits))
	return nil
}

func (lib *soapyLib) getGainRange(dev uintptr, dir, ch int) (float64, float64) {
	if lib.pGetGainRange == nil {
		return 0, 89
	}
	// SoapySDRRange is { double minimum; double maximum; double step; } = 24 bytes
	var buf [3]float64
	lib.pGetGainRange.Call(uintptr(unsafe.Pointer(&buf)), dev, uintptr(dir), uintptr(ch))
	return buf[0], buf[1]
}

// --- Stream ---

func (lib *soapyLib) setupStream(dev uintptr, dir int, format string, channels []uint) (uintptr, error) {
	if lib.pSetupStream == nil {
		return 0, fmt.Errorf("setupStream not available")
	}
	cFmt, _ := syscall.BytePtrFromString(format)
	var chPtr uintptr
	if len(channels) > 0 {
		chPtr = uintptr(unsafe.Pointer(&channels[0]))
	}
	var kw kwargs
	ret, _, _ := lib.pSetupStream.Call(dev, uintptr(dir), uintptr(unsafe.Pointer(cFmt)),
		chPtr, uintptr(len(channels)), uintptr(unsafe.Pointer(&kw)))
	if ret == 0 {
		return 0, fmt.Errorf("soapy: setupStream failed")
	}
	return ret, nil
}

func (lib *soapyLib) closeStream(dev, stream uintptr) {
	if lib.pCloseStream != nil {
		lib.pCloseStream.Call(dev, stream)
	}
}

func (lib *soapyLib) getStreamMTU(dev, stream uintptr) int {
	if lib.pGetStreamMTU == nil {
		return 4096
	}
	ret, _, _ := lib.pGetStreamMTU.Call(dev, stream)
	if ret == 0 {
		return 4096
	}
	return int(ret)
}

func (lib *soapyLib) activateStream(dev, stream uintptr) error {
	if lib.pActivateStream == nil {
		return fmt.Errorf("activateStream not available")
	}
	rc, _, _ := lib.pActivateStream.Call(dev, stream, 0, 0, 0)
	if int32(rc) != 0 {
		return fmt.Errorf("soapy: activateStream failed: %d", int32(rc))
	}
	return nil
}

func (lib *soapyLib) deactivateStream(dev, stream uintptr) {
	if lib.pDeactivateStream != nil {
		lib.pDeactivateStream.Call(dev, stream, 0, 0)
	}
}

func (lib *soapyLib) writeStream(dev, stream uintptr, buf unsafe.Pointer, numElems int) (int, error) {
	if lib.pWriteStream == nil {
		return 0, fmt.Errorf("writeStream not available")
	}
	buffs := [1]uintptr{uintptr(buf)}
	var flags int32
	rc, _, _ := lib.pWriteStream.Call(dev, stream,
		uintptr(unsafe.Pointer(&buffs[0])),
		uintptr(numElems),
		uintptr(unsafe.Pointer(&flags)),
		0,        // timeNs
		100000,   // timeoutUs = 100ms
	)
	n := int32(rc)
	if n < 0 {
		return 0, fmt.Errorf("soapy: writeStream returned %d", n)
	}
	return int(n), nil
}

// --- C string helper ---

func goString(p uintptr) string {
	if p == 0 {
		return ""
	}
	buf := make([]byte, 0, 256)
	for i := uintptr(0); ; i++ {
		b := *(*byte)(unsafe.Pointer(p + i))
		if b == 0 {
			break
		}
		buf = append(buf, b)
	}
	return string(buf)
}
