/**
 * @file speex.c  Speex audio codec
 *
 * Copyright (C) 2010 Creytiv.com
 */
#include <stdlib.h>
#include <speex/speex.h>
#include <speex/speex_stereo.h>
#include <speex/speex_callbacks.h>
#include <re.h>
#include <baresip.h>


#define DEBUG_MODULE "speex"
#define DEBUG_LEVEL 5
#include <re_dbg.h>


enum {
	MIN_FRAME_SIZE = 43,
	SPEEX_PTIME    = 20,
};


struct auenc_state {
	void *enc;
	SpeexBits bits;

	uint32_t frame_size;  /* Number of samples */
	uint8_t channels;
};


struct audec_state {
	void *dec;
	SpeexBits bits;
	SpeexStereoState stereo;
	SpeexCallback callback;

	uint32_t frame_size;  /* Number of samples */
	uint8_t channels;
};


static char speex_fmtp[128];


/** Speex configuration */
static struct {
	int quality;
	int complexity;
	int enhancement;
	int vbr;
	int vad;
} sconf = {
	3,  /* 0-10   */
	2,  /* 0-10   */
	0,  /* 0 or 1 */
	0,  /* 0 or 1 */
	0   /* 0 or 1 */
};


static void encode_destructor(void *arg)
{
	struct auenc_state *st = arg;

	speex_bits_destroy(&st->bits);
	speex_encoder_destroy(st->enc);
}


static void decode_destructor(void *arg)
{
	struct audec_state *st = arg;

	speex_bits_destroy(&st->bits);
	speex_decoder_destroy(st->dec);
}


static void encoder_config(void *st)
{
	int ret;

	ret = speex_encoder_ctl(st, SPEEX_SET_QUALITY, &sconf.quality);
	if (ret) {
		DEBUG_WARNING("SPEEX_SET_QUALITY: %d\n", ret);
	}

	ret = speex_encoder_ctl(st, SPEEX_SET_COMPLEXITY, &sconf.complexity);
	if (ret) {
		DEBUG_WARNING("SPEEX_SET_COMPLEXITY: %d\n", ret);
	}

	ret = speex_encoder_ctl(st, SPEEX_SET_VBR, &sconf.vbr);
	if (ret) {
		DEBUG_WARNING("SPEEX_SET_VBR: %d\n", ret);
	}

	ret = speex_encoder_ctl(st, SPEEX_SET_VAD, &sconf.vad);
	if (ret) {
		DEBUG_WARNING("SPEEX_SET_VAD: %d\n", ret);
	}
}


static void decoder_config(void *st)
{
	int ret;

	ret = speex_decoder_ctl(st, SPEEX_SET_ENH, &sconf.enhancement);
	if (ret) {
		DEBUG_WARNING("SPEEX_SET_ENH: %d\n", ret);
	}
}


static int decode_param(struct auenc_state *st, const struct pl *name,
			const struct pl *val)
{
	int ret;

	DEBUG_INFO("speex param: \"%r\" = \"%r\"\n", name, val);

	/* mode: List supported Speex decoding modes.  The valid modes are
	   different for narrowband and wideband, and are defined as follows:

	   {1,2,3,4,5,6,7,8,any} for narrowband
	   {0,1,2,3,4,5,6,7,8,9,10,any} for wideband and ultra-wideband
	 */
	if (0 == pl_strcasecmp(name, "mode")) {
		struct pl v;
		int mode;

		/* parameter is quoted */
		if (re_regex(val->p, val->l, "\"[^\"]+\"", &v))
			v = *val;

		if (0 == pl_strcasecmp(&v, "any"))
			return 0;

		mode = pl_u32(&v);

		DEBUG_NOTICE("SPEEX_SET_MODE: mode=%d\n", mode);
		ret = speex_encoder_ctl(st->enc, SPEEX_SET_MODE, &mode);
		if (ret) {
			DEBUG_WARNING("SPEEX_SET_MODE: ret=%d\n", ret);
		}
	}
	/* vbr: variable bit rate - either 'on' 'off' or 'vad' */
	else if (0 == pl_strcasecmp(name, "vbr")) {
		int vbr = 0, vad = 0;

		if (0 == pl_strcasecmp(val, "on"))
			vbr = 1;
		else if (0 == pl_strcasecmp(val, "off"))
			vbr = 0;
		else if (0 == pl_strcasecmp(val, "vad"))
			vad = 1;
		else {
			DEBUG_WARNING("invalid vbr value %r\n", val);
		}

		DEBUG_NOTICE("Setting VBR=%d VAD=%d\n", vbr, vad);
		ret = speex_encoder_ctl(st->enc, SPEEX_SET_VBR, &vbr);
		if (ret) {
			DEBUG_WARNING("SPEEX_SET_VBR: ret=%d\n", ret);
		}
		ret = speex_encoder_ctl(st->enc, SPEEX_SET_VAD, &vad);
		if (ret) {
			DEBUG_WARNING("SPEEX_SET_VAD: ret=%d\n", ret);
		}
	}
	else if (0 == pl_strcasecmp(name, "cng")) {
		int dtx = 0;

		if (0 == pl_strcasecmp(val, "on"))
			dtx = 0;
		else if (0 == pl_strcasecmp(val, "off"))
			dtx = 1;

		ret = speex_encoder_ctl(st->enc, SPEEX_SET_DTX, &dtx);
		if (ret) {
			DEBUG_WARNING("SPEEX_SET_DTX: ret=%d\n", ret);
		}
	}
	else {
		DEBUG_NOTICE("unknown Speex param: %r=%r\n", name, val);
	}

	return 0;
}


static void param_handler(const struct pl *name, const struct pl *val,
			  void *arg)
{
	struct auenc_state *st = arg;

	decode_param(st, name, val);
}


static const SpeexMode *resolve_mode(uint32_t srate)
{
	switch (srate) {

	default:
	case 8000:  return &speex_nb_mode;
	case 16000: return &speex_wb_mode;
	case 32000: return &speex_uwb_mode;
	}
}


static int encode_update(struct auenc_state **aesp, const struct aucodec *ac,
			 struct auenc_param *prm, const char *fmtp)
{
	struct auenc_state *st;
	int ret, err = 0;

	if (!aesp || !ac || !prm)
		return EINVAL;
	if (prm->ptime != SPEEX_PTIME)
		return EPROTO;
	if (*aesp)
		return 0;

	st = mem_zalloc(sizeof(*st), encode_destructor);
	if (!st)
		return ENOMEM;

	st->frame_size = 160 * ac->srate/8000;
	st->channels = ac->ch;

	/* Encoder */
	st->enc = speex_encoder_init(resolve_mode(ac->srate));
	if (!st->enc) {
		err = ENOMEM;
		goto out;
	}

	speex_bits_init(&st->bits);

	encoder_config(st->enc);

	ret = speex_encoder_ctl(st->enc, SPEEX_GET_FRAME_SIZE,
				&st->frame_size);
	if (ret) {
		DEBUG_WARNING("SPEEX_GET_FRAME_SIZE: %d\n", ret);
	}

	if (str_isset(fmtp)) {
		struct pl params;

		pl_set_str(&params, fmtp);

		fmt_param_apply(&params, param_handler, st);
	}

 out:
	if (err)
		mem_deref(st);
	else
		*aesp = st;

	return err;
}


static int decode_update(struct audec_state **adsp,
			 const struct aucodec *ac, const char *fmtp)
{
	struct audec_state *st;
	int err = 0;
	(void)fmtp;

	if (!adsp || !ac)
		return EINVAL;
	if (*adsp)
		return 0;

	st = mem_zalloc(sizeof(*st), decode_destructor);
	if (!st)
		return ENOMEM;

	st->frame_size = 160 * ac->srate/8000;
	st->channels = ac->ch;

	/* Decoder */
	st->dec = speex_decoder_init(resolve_mode(ac->srate));
	if (!st->dec) {
		err = ENOMEM;
		goto out;
	}

	speex_bits_init(&st->bits);

	if (2 == st->channels) {
		DEBUG_NOTICE("decoder: Stereo enabled\n");

		/* Stereo. */
		st->stereo.balance = 1;
		st->stereo.e_ratio = .5f;
		st->stereo.smooth_left = 1;
		st->stereo.smooth_right = 1;

		st->callback.callback_id = SPEEX_INBAND_STEREO;
		st->callback.func = speex_std_stereo_request_handler;
		st->callback.data = &st->stereo;
		speex_decoder_ctl(st->dec, SPEEX_SET_HANDLER,
				  &st->callback);
	}

	decoder_config(st->dec);

 out:
	if (err)
		mem_deref(st);
	else
		*adsp = st;

	return err;
}


static int encode(struct auenc_state *st, uint8_t *buf,
		  size_t *len, const int16_t *sampv, size_t sampc)
{
	const size_t n = st->channels * st->frame_size;
	int ret, r;

	if (*len < 128)
		return ENOMEM;

	/* VAD */
	if (!sampv || !sampc) {
		/* 5 zeros interpreted by Speex as silence (submode 0) */
		speex_bits_pack(&st->bits, 0, 5);
		goto out;
	}

	/* Handle multiple Speex frames in one RTP packet */
	while (sampc > 0) {

		/* Assume stereo */
		if (2 == st->channels) {
			speex_encode_stereo_int((int16_t *)sampv,
						st->frame_size, &st->bits);
		}

		ret = speex_encode_int(st->enc, (int16_t *)sampv, &st->bits);
		if (1 != ret) {
			DEBUG_WARNING("speex_encode_int: ret=%d\n", ret);
		}

		sampc -= n;
		sampv += n;
	}

 out:
	/* Terminate bit stream */
	speex_bits_pack(&st->bits, 15, 5);

	r = speex_bits_write(&st->bits, (char *)buf, (int)*len);
	*len = r;

	speex_bits_reset(&st->bits);

	return 0;
}


static int decode(struct audec_state *st, int16_t *sampv,
		  size_t *sampc, const uint8_t *buf, size_t len)
{
	const size_t n = st->channels * st->frame_size;
	size_t i = 0;

	/* Read into bit-stream */
	speex_bits_read_from(&st->bits, (char *)buf, (int)len);

	/* Handle multiple Speex frames in one RTP packet */
	while (speex_bits_remaining(&st->bits) >= MIN_FRAME_SIZE) {
		int ret;

		if (*sampc < n)
			return ENOMEM;

		ret = speex_decode_int(st->dec, &st->bits,
				       (int16_t *)&sampv[i]);
		if (ret < 0) {
			if (-1 == ret) {
			}
			else if (-2 == ret) {
				DEBUG_WARNING("decode: corrupt stream\n");
			}
			else {
				DEBUG_WARNING("decode: speex_decode_int:"
					      " ret=%d\n", ret);
			}
			break;
		}

		/* Transforms a mono frame into a stereo frame
		   using intensity stereo info */
		if (2 == st->channels) {
			speex_decode_stereo_int((int16_t *)&sampv[i],
						st->frame_size,
						&st->stereo);
		}

		i      += n;
		*sampc -= n;
	}

	*sampc = i;

	return 0;
}


static int pkloss(struct audec_state *st, int16_t *sampv, size_t *sampc)
{
	const size_t n = st->channels * st->frame_size;

	if (*sampc < n)
		return ENOMEM;

	/* Silence */
	speex_decode_int(st->dec, NULL, sampv);
	*sampc = n;

	return 0;
}


static void config_parse(struct conf *conf)
{
	uint32_t v;

	if (0 == conf_get_u32(conf, "speex_quality", &v))
		sconf.quality = v;
	if (0 == conf_get_u32(conf, "speex_complexity", &v))
		sconf.complexity = v;
	if (0 == conf_get_u32(conf, "speex_enhancement", &v))
		sconf.enhancement = v;
	if (0 == conf_get_u32(conf, "speex_vbr", &v))
		sconf.vbr = v;
	if (0 == conf_get_u32(conf, "speex_vad", &v))
		sconf.vad = v;
}


static struct aucodec speexv[] = {

	/* Stereo Speex */
	{LE_INIT, 0, "speex", 32000, 2, speex_fmtp,
	 encode_update, encode, decode_update, decode, pkloss, 0, 0},
	{LE_INIT, 0, "speex", 16000, 2, speex_fmtp,
	 encode_update, encode, decode_update, decode, pkloss, 0, 0},
	{LE_INIT, 0, "speex",  8000, 2, speex_fmtp,
	 encode_update, encode, decode_update, decode, pkloss, 0, 0},

	/* Standard Speex */
	{LE_INIT, 0, "speex", 32000, 1, speex_fmtp,
	 encode_update, encode, decode_update, decode, pkloss, 0, 0},
	{LE_INIT, 0, "speex", 16000, 1, speex_fmtp,
	 encode_update, encode, decode_update, decode, pkloss, 0, 0},
	{LE_INIT, 0, "speex",  8000, 1, speex_fmtp,
	 encode_update, encode, decode_update, decode, pkloss, 0, 0},
};


static int speex_init(void)
{
	size_t i;

	config_parse(conf_cur());

	(void)re_snprintf(speex_fmtp, sizeof(speex_fmtp),
			  "mode=\"7\";vbr=%s;cng=on",
			  sconf.vad ? "vad" : (sconf.vbr ? "on" : "off"));

	for (i=0; i<ARRAY_SIZE(speexv); i++)
		aucodec_register(&speexv[i]);

	return 0;
}


static int speex_close(void)
{
	size_t i;
	for (i=0; i<ARRAY_SIZE(speexv); i++)
		aucodec_unregister(&speexv[i]);
	return 0;
}


EXPORT_SYM const struct mod_export DECL_EXPORTS(speex) = {
	"speex",
	"codec",
	speex_init,
	speex_close
};
