package goborator

/*
#cgo pkg-config: cgaborator
#include <cgaborator.h>
*/
import "C"
import (
	"fmt"
	"runtime"
	"slices"
	"sync"
	"unsafe"
)

type Gaborator struct {
	closeOnce                sync.Once
	pointer                  C.uintptr_t
	audioBlockSize           int
	coefficientOutputChannel chan []float32
	bandsPerOctave           int
	bandNumberCache          int
	latency                  int64
}

func NewGaborator(blockSize int, sampleRate float64, bandsPerOctave int, minimumFrequency, maximumFrequency, referenceFrequency float64, stepSize int) *Gaborator {
	ob := &Gaborator{
		pointer:        C.gaborator_initialize(C.int(blockSize), C.double(sampleRate), C.int(bandsPerOctave), C.double(minimumFrequency), C.double(referenceFrequency), C.double(maximumFrequency), C.int(stepSize)),
		audioBlockSize: blockSize,
		bandsPerOctave: bandsPerOctave,
	}
	ob.bandNumberCache = ob.GetNumberOfBands()
	ob.latency = int64(C.gaborator_analysis_support(ob.pointer))
	return ob
}

func (g *Gaborator) GetNumberOfBands() int {
	return int(C.gaborator_number_of_bands(g.pointer))
}

func (g *Gaborator) gaborTransform(audioData []float32, outputCallback func(output []float32)) {
	g.analyze(audioData, outputCallback)
}

func (g *Gaborator) GaborBlockTransform(source chan []float32, outputCallback func(output []float32)) error {
	defer g.ProcessingFinished()

	for {
		block, more := <-source
		if !more {
			break
		}
		err := g.Process(block, outputCallback)
		if err != nil {
			return err
		}
	}

	//finish
	err := g.Process(nil, outputCallback)
	if err != nil {
		return err
	}
	return nil
}

func (g *Gaborator) GaborTransform(source chan float32, outputCallback func(output []float32)) error {
	defer g.ProcessingFinished()
	audioData := make([]float32, 0, g.audioBlockSize)

	for {
		f, more := <-source
		if !more {
			break
		}
		audioData = append(audioData, f)

		for len(audioData) >= g.audioBlockSize {
			g.gaborTransform(audioData[0:g.audioBlockSize], outputCallback)
			audioData = audioData[:0]
		}
	}
	//finish
	if len(audioData) > 0 {
		err := g.Process(audioData, outputCallback)
		if err != nil {
			return err
		}
	}
	err := g.Process(nil, outputCallback)
	if err != nil {
		return err
	}

	return nil
}

func (g *Gaborator) Process(block []float32, outputCallback func(output []float32)) error {

	if len(block) > g.audioBlockSize {
		return fmt.Errorf("invalid block size %d > %d", len(block), g.audioBlockSize)
	}
	g.gaborTransform(block, outputCallback)
	return nil
}

func (g *Gaborator) ProcessingFinished() {
	g.closeOnce.Do(func() {
		if g.pointer != 0 {
			C.gaborator_release(g.pointer)
			g.pointer = 0
		}
	})
}

func (g *Gaborator) GetBlockSize() int {
	return g.audioBlockSize
}

func (g *Gaborator) GetLatency() int64 {
	return g.latency
}

func (g *Gaborator) GetBandwidth() float64 {
	return 1200. / float64(g.bandsPerOctave)
}

func (g *Gaborator) analyze(input []float32, outputCallback func(output []float32)) {
	var returnSize C.size_t
	var sliceSize C.size_t
	var returnData *C.float

	if len(input) == 0 {
		returnData = C.gaborator_transform(g.pointer, (*C.float)(nil), C.int64_t(0), &returnSize, &sliceSize)
	} else {
		returnData = C.gaborator_transform(g.pointer, (*C.float)(unsafe.SliceData(input)), C.int64_t(len(input)), &returnSize, &sliceSize)
		runtime.KeepAlive(input)
	}

	if returnData != nil && returnSize > 0 {
		block := unsafe.Slice((*float32)(returnData), uint64(returnSize))

		for i := 0; i < len(block); i += int(sliceSize) {
			outputCallback(slices.Clone(block[i : i+int(sliceSize)]))
		}
	}
}
