//go:build android

package teamspeak

/*
#cgo CFLAGS: -I${SRCDIR}/../../android/app/src/main/cpp/third_party/opus/include
#cgo android,arm64 LDFLAGS: -L${SRCDIR}/.opus/lib/arm64-v8a -l:libopus.a -lm
#cgo android,arm LDFLAGS: -L${SRCDIR}/.opus/lib/armeabi-v7a -l:libopus.a -lm
#cgo android,386 LDFLAGS: -L${SRCDIR}/.opus/lib/x86 -l:libopus.a -lm
#cgo android,amd64 LDFLAGS: -L${SRCDIR}/.opus/lib/x86_64 -l:libopus.a -lm
#include "opus.h"
#include <stdlib.h>
*/
import "C"

import (
	"errors"
	"fmt"
	"runtime"
	"sync"
	"unsafe"
)

var errOpusDecoderReleased = errors.New("opus decoder has been released")

type opusVoiceDecoderFactory struct{}

type opusVoiceDecoder struct {
	mu       sync.Mutex
	decoder  *C.OpusDecoder
	channels int
}

func newPlatformVoiceDecoderFactory() voiceDecoderFactory { return opusVoiceDecoderFactory{} }

func (opusVoiceDecoderFactory) NewvoiceDecoder(_ int, codec int) (voiceDecoder, error) {
	if codec != 4 && codec != 5 {
		return nil, fmt.Errorf("unsupported TeamSpeak voice codec %d", codec)
	}
	channels := voiceCodecChannels(codec)
	var opusErr C.int
	decoder := C.opus_decoder_create(C.opus_int32(receiveSampleRate), C.int(channels), &opusErr)
	if decoder == nil || opusErr != C.OPUS_OK {
		return nil, fmt.Errorf("opus_decoder_create: %d", int(opusErr))
	}
	return &opusVoiceDecoder{decoder: decoder, channels: channels}, nil
}

func (d *opusVoiceDecoder) Decode(packet []byte) ([]int16, error) {
	if len(packet) == 0 {
		return nil, errors.New("empty Opus packet")
	}
	d.mu.Lock()
	defer d.mu.Unlock()
	if d.decoder == nil {
		return nil, errOpusDecoderReleased
	}
	pcm := make([]int16, receiveMaxDecodeSamplesPerChannel*d.channels)
	decoded := C.opus_decode(
		d.decoder,
		(*C.uchar)(unsafe.Pointer(&packet[0])),
		C.opus_int32(len(packet)),
		(*C.opus_int16)(unsafe.Pointer(&pcm[0])),
		C.int(receiveMaxDecodeSamplesPerChannel),
		0,
	)
	runtime.KeepAlive(packet)
	return decodedPCM(pcm, decoded, d.channels)
}

func (d *opusVoiceDecoder) DecodeLost() ([]int16, error) {
	d.mu.Lock()
	defer d.mu.Unlock()
	if d.decoder == nil {
		return nil, errOpusDecoderReleased
	}
	pcm := make([]int16, receiveMaxDecodeSamplesPerChannel*d.channels)
	decoded := C.opus_decode(
		d.decoder,
		nil,
		0,
		(*C.opus_int16)(unsafe.Pointer(&pcm[0])),
		C.int(receiveMaxDecodeSamplesPerChannel),
		0,
	)
	return decodedPCM(pcm, decoded, d.channels)
}

func (d *opusVoiceDecoder) Release() {
	d.mu.Lock()
	decoder := d.decoder
	d.decoder = nil
	d.mu.Unlock()
	if decoder != nil {
		C.opus_decoder_destroy(decoder)
	}
}

func decodedPCM(pcm []int16, decoded C.int, channels int) ([]int16, error) {
	if decoded < 0 {
		return nil, fmt.Errorf("opus_decode: %d", int(decoded))
	}
	perChannel := int(decoded)
	decodedSamples := perChannel * channels
	if perChannel > receiveMaxDecodeSamplesPerChannel || decodedSamples > len(pcm) {
		return nil, fmt.Errorf("opus_decode returned %d samples/channel, buffer has %d samples for %d channels", perChannel, len(pcm), channels)
	}
	return normalizeDecodedPCM(pcm[:decodedSamples], channels), nil
}
