/*
 * Copyright (c) 2010-2022 Belledonne Communications SARL.
 *
 * This file is part of mediastreamer2
 * (see https://gitlab.linphone.org/BC/public/mediastreamer2).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include <bctoolbox/defs.h>

#include "mediastreamer2/msfilter.h"
#ifdef GSM_USE_BUILD_INTERFACE
#include <gsm.h>
#else
#include <gsm/gsm.h>
#endif

#ifdef _MSC_VER
#include <malloc.h>
#define alloca _alloca
#endif

typedef struct EncState {
	gsm state;
	uint32_t ts;
	int ptime;
	int maxptime;
	MSBufferizer *bufferizer;
} EncState;

static int set_ptime(MSFilter *f, int ptime) {
	EncState *s = (EncState *)f->data;
	ms_message("%s want ptime=%i", f->desc->name, ptime);
	ptime = (ptime / 20) * 20;
	s->ptime = MIN(ptime, s->maxptime);
	if (s->ptime == s->maxptime) ms_message("%s ptime set to maxptime=%i", f->desc->name, s->maxptime);
	return 0;
}

static int enc_add_fmtp(MSFilter *f, void *arg) {
	const char *fmtp = (const char *)arg;
	EncState *s = (EncState *)f->data;
	char tmp[30];
	if (fmtp_get_value(fmtp, "maxptime", tmp, sizeof(tmp))) {
		s->maxptime = MIN(atoi(tmp), MS_DEFAULT_MAX_PTIME);
	}
	if (fmtp_get_value(fmtp, "ptime", tmp, sizeof(tmp))) {
		return set_ptime(f, atoi(tmp));
	}
	return 0;
}

static int enc_add_attr(MSFilter *f, void *arg) {
	const char *attr = (const char *)arg;
	if (strstr(attr, "ptime:") != NULL) {
		int ptime = atoi(attr + 6);
		return set_ptime(f, ptime);
	}
	return 0;
}

static int enc_get_sample_rate(BCTBX_UNUSED(MSFilter *f), void *arg) {
	*((int *)arg) = 8000;
	return 0;
}

static void enc_init(MSFilter *f) {
	EncState *s = (EncState *)ms_new(EncState, 1);
	s->state = gsm_create();
	s->ts = 0;
	s->ptime = 20;
	s->maxptime = MS_DEFAULT_MAX_PTIME;
	s->bufferizer = ms_bufferizer_new();
	f->data = s;
}

static void enc_uninit(MSFilter *f) {
	EncState *s = (EncState *)f->data;
	gsm_destroy(s->state);
	ms_bufferizer_destroy(s->bufferizer);
	ms_free(s);
}

static void enc_process(MSFilter *f) {
	EncState *s = (EncState *)f->data;
	mblk_t *im;
	unsigned int unitary_buff_size = sizeof(int16_t) * 160;
	unsigned int buff_size = unitary_buff_size * s->ptime / 20;
	int16_t *buff;
	unsigned int offset;

	while ((im = ms_queue_get(f->inputs[0])) != NULL) {
		ms_bufferizer_put(s->bufferizer, im);
	}
	while (ms_bufferizer_get_avail(s->bufferizer) >= buff_size) {
		mblk_t *om = allocb((33 * s->ptime) / 20, 0);
		buff = (int16_t *)alloca(buff_size);
		ms_bufferizer_read(s->bufferizer, (uint8_t *)buff, buff_size);

		for (offset = 0; offset < buff_size; offset += unitary_buff_size) {
			gsm_encode(s->state, (gsm_signal *)&buff[offset / sizeof(int16_t)], (gsm_byte *)om->b_wptr);
			om->b_wptr += 33;
		}
		ms_bufferizer_fill_current_metas(s->bufferizer, om);
		mblk_set_timestamp_info(om, s->ts);
		ms_queue_put(f->outputs[0], om);
		s->ts += buff_size / sizeof(int16_t) /*sizeof(buf)/2*/;
	}
}

static int get_ptime(MSFilter *f, void *arg) {
	struct EncState *s = (struct EncState *)f->data;
	*((int *)arg) = s->ptime;
	return 0;
}

static MSFilterMethod enc_methods[] = {{MS_FILTER_ADD_FMTP, enc_add_fmtp},
                                       {MS_FILTER_ADD_ATTR, enc_add_attr},
                                       {MS_FILTER_GET_SAMPLE_RATE, enc_get_sample_rate},
                                       {MS_AUDIO_ENCODER_GET_PTIME, get_ptime},
                                       {0, NULL}};

#ifdef _MSC_VER

MSFilterDesc ms_gsm_enc_desc = {MS_GSM_ENC_ID,
                                "MSGsmEnc",
                                N_("The GSM full-rate codec"),
                                MS_FILTER_ENCODER,
                                "gsm",
                                1,
                                1,
                                enc_init,
                                NULL,
                                enc_process,
                                NULL,
                                enc_uninit,
                                enc_methods};

#else

MSFilterDesc ms_gsm_enc_desc = {.id = MS_GSM_ENC_ID,
                                .name = "MSGsmEnc",
                                .text = N_("The GSM full-rate codec"),
                                .category = MS_FILTER_ENCODER,
                                .enc_fmt = "gsm",
                                .ninputs = 1,
                                .noutputs = 1,
                                .init = enc_init,
                                .process = enc_process,
                                .uninit = enc_uninit,
                                .methods = enc_methods};

#endif

static void dec_init(MSFilter *f) {
	f->data = gsm_create();
}

static void dec_uninit(MSFilter *f) {
	gsm s = (gsm)f->data;
	gsm_destroy(s);
}

static void dec_process(MSFilter *f) {
	gsm s = (gsm)f->data;
	mblk_t *im;
	mblk_t *om;
	const int frsz = 160 * 2;

	while ((im = ms_queue_get(f->inputs[0])) != NULL) {
		for (; (im->b_wptr - im->b_rptr) >= 33; im->b_rptr += 33) {
			om = allocb(frsz, 0);
			mblk_meta_copy(im, om);
			if (gsm_decode(s, (gsm_byte *)im->b_rptr, (gsm_signal *)om->b_wptr) < 0) {
				ms_warning("gsm_decode error!");
				freemsg(om);
			} else {
				om->b_wptr += frsz;
				ms_queue_put(f->outputs[0], om);
			}
		}
		freemsg(im);
	}
}

static int dec_get_sample_rate(BCTBX_UNUSED(MSFilter *f), void *arg) {
	*((int *)arg) = 8000;
	return 0;
}

static MSFilterMethod dec_methods[] = {{MS_FILTER_GET_SAMPLE_RATE, dec_get_sample_rate}, {0, NULL}};

#ifdef _MSC_VER

MSFilterDesc ms_gsm_dec_desc = {
    MS_GSM_DEC_ID, "MSGsmDec", N_("The GSM codec"), MS_FILTER_DECODER, "gsm", 1, 1, dec_init, NULL,
    dec_process,   NULL,       dec_uninit,          dec_methods};

#else

MSFilterDesc ms_gsm_dec_desc = {.id = MS_GSM_DEC_ID,
                                .name = "MSGsmDec",
                                .text = N_("The GSM codec"),
                                .category = MS_FILTER_DECODER,
                                .enc_fmt = "gsm",
                                .ninputs = 1,
                                .noutputs = 1,
                                .init = dec_init,
                                .process = dec_process,
                                .uninit = dec_uninit,
                                .methods = dec_methods};

#endif

MS_FILTER_DESC_EXPORT(ms_gsm_dec_desc)
MS_FILTER_DESC_EXPORT(ms_gsm_enc_desc)
