package goborator

/*
#cgo pkg-config: cgaborator
#include <cgaborator.h>

void cgoCallback(uintptr_t callback_data, float* data, int size);

typedef void (*gaborator_transform_callback)(uintptr_t callback_data, float* data, int size);
*/
import "C"
import (
	"fmt"
	"log"
	"runtime/cgo"
	"unsafe"
)

type Gaborator struct {
	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) {
	g.analyze(audioData)
}

func (g *Gaborator) GetChannel() chan []float32 {

	if g.coefficientOutputChannel == nil {
		g.coefficientOutputChannel = make(chan []float32, 1024)
	}

	return g.coefficientOutputChannel
}

func (g *Gaborator) GaborBlockTransform(source chan []float32) (channel chan []float32) {

	channel = g.GetChannel()
	go func() {
		defer g.ProcessingFinished()

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

		//finish
		err := g.Process(nil)
		if err != nil {
			log.Panic(err)
		}
	}()

	return channel
}

func (g *Gaborator) GaborTransform(source chan float32) (channel chan []float32) {
	channel = g.GetChannel()

	go func() {
		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])
				audioData = audioData[:0]
			}
		}
		//finish
		err := g.Process(nil)
		if err != nil {
			log.Panic(err)
		}
	}()

	return channel
}
func (g *Gaborator) Process(block []float32) error {

	if len(block) > g.audioBlockSize {
		return fmt.Errorf("invalid block size %d > %d", len(block), g.audioBlockSize)
	}
	g.gaborTransform(block)
	return nil
}
func (g *Gaborator) ProcessingFinished() {
	if g.pointer != 0 {
		C.gaborator_release(g.pointer)
		g.pointer = 0
	}
	if g.coefficientOutputChannel != nil {
		close(g.coefficientOutputChannel)
		g.coefficientOutputChannel = nil
	}
}

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(block []float32) {
	handle := cgo.NewHandle(g.coefficientOutputChannel)
	defer handle.Delete()

	if len(block) == 0 {
		C.gaborator_transform(g.pointer, (*C.float)(nil), C.int(0), C.gaborator_transform_callback(C.cgoCallback), C.uintptr_t(handle))
	} else {
		C.gaborator_transform(g.pointer, (*C.float)(&block[0]), C.int(len(block)), C.gaborator_transform_callback(C.cgoCallback), C.uintptr_t(handle))
	}
}

//export cgoCallback
func cgoCallback(ptr C.uintptr_t, data *C.float, size C.int) {
	buf := make([]float32, size)
	copy(buf, unsafe.Slice((*float32)(data), int(size)))
	cgo.Handle(ptr).Value().(chan []float32) <- buf
}
