package opus

/*
#cgo pkg-config: opus

#include <opus.h>

int get_bitrate(OpusEncoder *st, opus_int32 *bitrate) { return opus_encoder_ctl(st, OPUS_GET_BITRATE(bitrate)); }
int get_complexity(OpusEncoder *st, opus_int32 *complexity) { return opus_encoder_ctl(st, OPUS_GET_COMPLEXITY(complexity)); }
int get_dtx(OpusEncoder *st, opus_int32 *dtx) { return opus_encoder_ctl(st, OPUS_GET_DTX(dtx)); }
int get_inband_fec(OpusEncoder *st, opus_int32 *fec) { return opus_encoder_ctl(st, OPUS_GET_INBAND_FEC(fec)); }
int get_max_bandwidth(OpusEncoder *st, opus_int32 *max_bw) {	return opus_encoder_ctl(st, OPUS_GET_MAX_BANDWIDTH(max_bw)); }
int get_packet_loss_perc(OpusEncoder *st, opus_int32 *loss_perc) { return opus_encoder_ctl(st, OPUS_GET_PACKET_LOSS_PERC(loss_perc)); }
int get_sample_rate(OpusEncoder *st, opus_int32 *sample_rate) { return opus_encoder_ctl(st, OPUS_GET_SAMPLE_RATE(sample_rate)); }
int set_bitrate(OpusEncoder *st, opus_int32 bitrate) { return opus_encoder_ctl(st, OPUS_SET_BITRATE(bitrate)); }
int set_complexity(OpusEncoder *st, opus_int32 complexity) { return opus_encoder_ctl(st, OPUS_SET_COMPLEXITY(complexity)); }
int set_dtx(OpusEncoder *st, opus_int32 use_dtx) { return opus_encoder_ctl(st, OPUS_SET_DTX(use_dtx)); }
int set_inband_fec(OpusEncoder *st, opus_int32 fec) { return opus_encoder_ctl(st, OPUS_SET_INBAND_FEC(fec)); }
int set_max_bandwidth(OpusEncoder *st, opus_int32 max_bw) { return opus_encoder_ctl(st, OPUS_SET_MAX_BANDWIDTH(max_bw)); }
int set_packet_loss_perc(OpusEncoder *st, opus_int32 loss_perc) { return opus_encoder_ctl(st, OPUS_SET_PACKET_LOSS_PERC(loss_perc)); }
int reset_state(OpusEncoder *st) { return opus_encoder_ctl(st, OPUS_RESET_STATE); }
*/
import "C"
import (
	"fmt"
	"unsafe"
)

type (
	Application int
	Bandwidth   int
	Bitrate     int
	Error       int
)

const (
	// AppRestrictedLowDelay optimizes encoding for low latency applications
	AppRestrictedLowDelay = Application(C.OPUS_APPLICATION_RESTRICTED_LOWDELAY)
	// FullBand is 20 kHz bandpass
	FullBand = Bandwidth(C.OPUS_BANDWIDTH_FULLBAND)
)
const stereo = C.int(2)

type Encoder struct {
	mem []byte
	out []byte
	st  *C.struct_OpusEncoder
}

func NewEncoder(outFq int) (*Encoder, error) {
	mem := make([]byte, C.opus_encoder_get_size(stereo))
	out := make([]byte, 1024)
	enc := Encoder{
		mem: mem,
		st:  (*C.OpusEncoder)(unsafe.Pointer(&mem[0])),
		out: out,
	}
	err := unwrap(C.opus_encoder_init(enc.st, C.opus_int32(outFq), stereo, C.int(AppRestrictedLowDelay)))
	if err != nil {
		return nil, fmt.Errorf("opus: initializatoin error (%v)", err)
	}
	_ = enc.SetMaxBandwidth(FullBand)
	_ = enc.SetBitrate(96000)
	_ = enc.SetComplexity(5)

	return &enc, nil
}

func (e *Encoder) Reset() error { return e.ResetState() }

func (e *Encoder) Encode(pcm []int16) ([]byte, error) {
	if len(pcm) == 0 {
		return nil, nil
	}
	n := C.opus_encode(e.st, (*C.opus_int16)(&pcm[0]), C.int(len(pcm)>>1), (*C.uchar)(&e.out[0]), C.opus_int32(cap(pcm)))
	err := unwrap(n)
	// n = 1 is DTX
	if err != nil || n == 1 {
		return []byte{}, err
	}
	return e.out[:int(n)], nil
}

func (e *Encoder) GetInfo() string {
	bitrate, _ := e.Bitrate()
	complexity, _ := e.Complexity()
	dtx, _ := e.DTX()
	fec, _ := e.FEC()
	maxBandwidth, _ := e.MaxBandwidth()
	lossPercent, _ := e.PacketLossPerc()
	sampleRate, _ := e.SampleRate()
	return fmt.Sprintf(
		"%v, Bitrate: %v bps, Complexity: %v, DTX: %v, FEC: %v, Max bandwidth: *%v, Loss%%: %v, Rate: %v Hz",
		CodecVersion(), bitrate, complexity, dtx, fec, maxBandwidth, lossPercent, sampleRate,
	)
}

// SampleRate returns the sample rate of the encoder.
func (e *Encoder) SampleRate() (int, error) {
	var sampleRate C.opus_int32
	res := C.get_sample_rate(e.st, &sampleRate)
	return int(sampleRate), unwrap(res)
}

// Bitrate returns the bitrate of the encoder.
func (e *Encoder) Bitrate() (int, error) {
	var bitrate C.opus_int32
	res := C.get_bitrate(e.st, &bitrate)
	return int(bitrate), unwrap(res)
}

// SetBitrate sets the bitrate of the encoder.
// BitrateMax / BitrateAuto can be used here.
func (e *Encoder) SetBitrate(b Bitrate) error {
	return unwrap(C.set_bitrate(e.st, C.opus_int32(b)))
}

// Complexity returns the value of the complexity.
func (e *Encoder) Complexity() (int, error) {
	var complexity C.opus_int32
	res := C.get_complexity(e.st, &complexity)
	return int(complexity), unwrap(res)
}

// SetComplexity sets the complexity factor for the encoder.
// Complexity is a value from 1 to 10, where 1 is the lowest complexity and 10 is the highest.
func (e *Encoder) SetComplexity(complexity int) error {
	return unwrap(C.set_complexity(e.st, C.opus_int32(complexity)))
}

// DTX says if discontinuous transmission is enabled.
func (e *Encoder) DTX() (bool, error) {
	var dtx C.opus_int32
	res := C.get_dtx(e.st, &dtx)
	return dtx > 0, unwrap(res)
}

// SetDTX switches discontinuous transmission.
func (e *Encoder) SetDTX(dtx bool) error {
	var i int
	if dtx {
		i = 1
	}
	return unwrap(C.set_dtx(e.st, C.opus_int32(i)))
}

// MaxBandwidth returns the maximum allowed bandpass value.
func (e *Encoder) MaxBandwidth() (Bandwidth, error) {
	var b C.opus_int32
	res := C.get_max_bandwidth(e.st, &b)
	return Bandwidth(b), unwrap(res)
}

// SetMaxBandwidth sets the upper limit of the bandpass.
func (e *Encoder) SetMaxBandwidth(b Bandwidth) error {
	return unwrap(C.set_max_bandwidth(e.st, C.opus_int32(b)))
}

// FEC says if forward error correction (FEC) is enabled.
func (e *Encoder) FEC() (bool, error) {
	var fec C.opus_int32
	res := C.get_inband_fec(e.st, &fec)
	return fec > 0, unwrap(res)
}

// SetFEC switches the forward error correction (FEC).
func (e *Encoder) SetFEC(fec bool) error {
	var i int
	if fec {
		i = 1
	}
	return unwrap(C.set_inband_fec(e.st, C.opus_int32(i)))
}

// PacketLossPerc returns configured packet loss percentage.
func (e *Encoder) PacketLossPerc() (int, error) {
	var lossPerc C.opus_int32
	res := C.get_packet_loss_perc(e.st, &lossPerc)
	return int(lossPerc), unwrap(res)
}

// SetPacketLossPerc sets expected packet loss percentage.
func (e *Encoder) SetPacketLossPerc(lossPerc int) error {
	return unwrap(C.set_packet_loss_perc(e.st, C.opus_int32(lossPerc)))
}

func (e *Encoder) ResetState() error { return unwrap(C.reset_state(e.st)) }

func (e Error) Error() string { return fmt.Sprintf("opus: %v", C.GoString(C.opus_strerror(C.int(e)))) }

func unwrap(error C.int) (err error) {
	if error < C.OPUS_OK {
		err = Error(int(error))
	}
	return
}

func CodecVersion() string { return C.GoString(C.opus_get_version_string()) }
