package goborator

// #cgo CFLAGS: -I${SRCDIR}/c-gaborator
// #cgo LDFLAGS: -L${SRCDIR}/c-gaborator/build -L${SRCDIR} -lcgaborator -lm -lstdc++
// #include "cgaborator.h"
import "C"
import (
	"encoding/binary"
	"fmt"
	"io"
	"unsafe"
)

type Gaborator struct {
	pointer                   unsafe.Pointer
	latency                   int64
	sampleRate                float64
	audioBlockSize            int
	bandcenterCache           []float32
	firstBandCache            int
	audioDataToTransform      []float32
	fixedCoefficents          [][]float32
	frequencyBinTimeStepSize  int
	bandsPerOctave            int
	coefficients              [][]float32
	coefficientIndexOffset    int
	mostRecentCoefficentIndex int
}

func NewGaborator(blockSize int, sampleRate float64, bandsPerOctave int, minimumFrequency, maximumFrequency, referenceFrequency float64, stepSize int) *Gaborator {
	g := &Gaborator{
		pointer:                   unsafe.Pointer(C.gaborator_initialize(C.int(blockSize), C.double(sampleRate), C.int(bandsPerOctave), C.double(minimumFrequency), C.double(maximumFrequency), C.double(referenceFrequency))),
		sampleRate:                sampleRate,
		audioBlockSize:            blockSize,
		audioDataToTransform:      make([]float32, blockSize),
		frequencyBinTimeStepSize:  stepSize,
		bandsPerOctave:            bandsPerOctave,
		mostRecentCoefficentIndex: 0,
	}

	g.latency = int64(C.gaborator_get_anal_support(g.pointer))
	g.bandcenterCache = g.getBandcenters()

	g.firstBandCache = g.firstBand()

	coefficientSize := (g.latency + 2*int64(blockSize)) / int64(stepSize)
	g.coefficients = make([][]float32, coefficientSize)
	for i := range g.coefficients {
		g.coefficients[i] = make([]float32, g.numberOfBands())
	}
	g.coefficientIndexOffset = 0

	return g
}

func (g *Gaborator) numberOfBands() int {
	numberOfBands := 0
	for _, e := range g.bandcenterCache {
		if e > 0 {
			numberOfBands++
		}
	}

	return numberOfBands
}

func (g *Gaborator) firstBand() int {

	for i, e := range g.bandcenterCache {
		if e > 0 {
			return i
		}
	}
	return -1
}

func (g *Gaborator) bandCenters(bandIndex int) float32 {
	return g.bandcenterCache[bandIndex+g.firstBandCache]
}

func (g *Gaborator) getBandcenters() []float32 {
	result := make([]float32, int(C.gaborator_bandcenters_array_length(g.pointer)))
	C.gaborator_bandcenters(g.pointer, (*C.float)(&result[0]))

	return result
}

func float32Max(a, b float32) float32 {
	if a > b {
		return a
	}
	return b
}

func (g *Gaborator) gaborTransform(audioData []float32) {

	analysysResult := g.analyze(audioData)

	for i := 0; i < len(analysysResult); i += 3 {
		band := int(analysysResult[i])
		audioSample := int(analysysResult[i+1])
		coefficient := analysysResult[i+2]

		coefficientIndex := audioSample/g.frequencyBinTimeStepSize - g.coefficientIndexOffset
		bandIndex := band - g.firstBandCache

		circularIndex := coefficientIndex % len(g.coefficients)

		// The first results have a negative audio sample index
		// ignore these
		if coefficientIndex > 0 && bandIndex < len(g.coefficients[circularIndex]) {

			// If a new index is reached, save the old (fixed) coefficents in the history
			// Fill the array with zeros to get the max
			if coefficientIndex > g.mostRecentCoefficentIndex && coefficientIndex > len(g.coefficients) {
				// keep the new maximum
				g.mostRecentCoefficentIndex = coefficientIndex
				// copy the oldest data to the history
				g.fixedCoefficents = append(g.fixedCoefficents, g.coefficients[circularIndex])

				// fill the oldest with zeros
				for j := range g.coefficients[circularIndex] {
					g.coefficients[circularIndex][j] = 0.
				}
			}
			// due to reduction in precision (from audio sample accuracy to steps) multiple
			// magnitudes could be placed in the same stepIndex, bandIndex pair.
			// We take the maximum magnitudes value.
			g.coefficients[circularIndex][bandIndex] = float32Max(g.coefficients[circularIndex][bandIndex], coefficient)
		}
	}
}
func (g *Gaborator) GaborTransform(reader io.Reader) [][]float32 {
	var err error
	var f float32

	var audioData []float32

	for {
		err = binary.Read(reader, binary.LittleEndian, &f)
		if err != nil {
			break
		}
		audioData = append(audioData, f)
	}
	//log.Printf("length file in float %d / blocks %d", len(audioData), len(audioData)/g.audioBlockSize)
	//TODO: this seems to skip last block?
	for floatIndex := 0; floatIndex < len(audioData); floatIndex += g.audioBlockSize {
		g.gaborTransform(audioData[floatIndex : floatIndex+g.audioBlockSize])
	}

	g.ProcessingFinished()

	return g.coefficients
}
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 != nil {
		C.gaborator_release(g.pointer)
		g.pointer = nil
	}
}

func (g *Gaborator) GetStepSize() int {
	return g.frequencyBinTimeStepSize
}

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

func (g *Gaborator) GetSampleRate() float64 {
	return g.sampleRate
}

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

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

func (g *Gaborator) analyze(block []float32) []float32 {
	//log.Printf("analyze block len %d", len(block))
	C.gaborator_analyze(g.pointer, (*C.float)(&block[0]), C.int(len(block)))
	cSize := uintptr(C.gaborator_get_array_length(g.pointer))

	//log.Print(cSize)
	ptr := (*C.float)(C.gaborator_get_array(g.pointer))

	result := make([]float32, cSize)
	for i, v := range unsafe.Slice(ptr, cSize) {
		result[i] = float32(v)
	}

	return result
}
