/*
 * Copyright (c) 2010-2019 Belledonne Communications SARL.
 *
 * This file is part of mediastreamer2.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "mediastreamer2/msfilter.h"
#include "mediastreamer2/mschanadapter.h"
#include "mediastreamer2/msticker.h"

/*
 This filter transforms stereo buffers to mono and vice versa.
*/

typedef struct AdapterState {
	int inputchans; // means the number of channels but not the number of filter input
	int outputchans; // means the number of channels but not the number of filter output
	int sample_rate;
	size_t buffer_size;
	uint8_t *buffer1;
	uint8_t *buffer2;
	MSBufferizer input_buffer1;
	MSBufferizer input_buffer2;
}AdapterState;

static void adapter_init(MSFilter *f) {
	AdapterState *s = ms_new0(AdapterState, 1);
	s->inputchans = 1;
	s->outputchans = 1;
	s->sample_rate = 8000;
	f->data = s;
}

static void adapter_uninit(MSFilter *f) {
	AdapterState *s = (AdapterState*)f->data;
	ms_free(s);
}

static void adapter_preprocess(MSFilter *f) {
	AdapterState *s = (AdapterState*)f->data;
	if (s->inputchans == 2 && s->outputchans == 1) {
		s->buffer_size = ((f->ticker->interval * s->sample_rate) / 1000) * 2;
		s->buffer1 = ms_new(uint8_t, s->buffer_size);
		s->buffer2 = ms_new(uint8_t, s->buffer_size);
		ms_bufferizer_init(&s->input_buffer1);
		ms_bufferizer_init(&s->input_buffer2);
	}
}

static void adapter_process(MSFilter *f) {
	AdapterState *s = (AdapterState*)f->data;

	// Two mono input to stereo output
	if (f->inputs[0] != NULL && f->inputs[1] != NULL) {
		size_t buffer_size1, buffer_size2;

		ms_bufferizer_put_from_queue(&s->input_buffer1, f->inputs[0]);
		ms_bufferizer_put_from_queue(&s->input_buffer2, f->inputs[1]);

		buffer_size1 = ms_bufferizer_get_avail(&s->input_buffer1);
		buffer_size2 = ms_bufferizer_get_avail(&s->input_buffer2);

		if (buffer_size1 >= s->buffer_size || buffer_size2 >= s->buffer_size) {
			mblk_t *om;
			unsigned int i;

			if (buffer_size1 < s->buffer_size) memset(s->buffer1, 0, s->buffer_size);
			if (buffer_size2 < s->buffer_size) memset(s->buffer2, 0, s->buffer_size);

			ms_bufferizer_read(&s->input_buffer1, s->buffer1, s->buffer_size);
			ms_bufferizer_read(&s->input_buffer2, s->buffer2, s->buffer_size);

			om = allocb(s->buffer_size * 2, 0);

			for (i = 0 ; i < s->buffer_size / sizeof(int16_t) ; i++ , om->b_wptr += 4) {
				((int16_t*)om->b_wptr)[0] = ((int16_t*)s->buffer1)[i];
				((int16_t*)om->b_wptr)[1] = ((int16_t*)s->buffer2)[i];
			}

			ms_queue_put(f->outputs[0], om);

			buffer_size1 = ms_bufferizer_get_avail(&s->input_buffer1);
			buffer_size2 = ms_bufferizer_get_avail(&s->input_buffer2);
		}
	} else {
		mblk_t *im, *om;
		size_t msgsize;

		while ((im = ms_queue_get(f->inputs[0])) != NULL) {
			if (s->inputchans == s->outputchans) {
				ms_queue_put(f->outputs[0], im);
			} else if (s->outputchans == 2) {
				msgsize = msgdsize(im) * 2;
				om = allocb(msgsize, 0);
				for (;im->b_rptr < im->b_wptr ; im->b_rptr += 2 , om->b_wptr += 4) {
					((int16_t*)om->b_wptr)[0] = *(int16_t*)im->b_rptr;
					((int16_t*)om->b_wptr)[1] = *(int16_t*)im->b_rptr;
				}
				ms_queue_put(f->outputs[0], om);
				freemsg(im);
			} else if (s->inputchans == 2) {
				msgsize = msgdsize(im)/2;
				om = allocb(msgsize,0);
				for (;im->b_rptr < im->b_wptr ; im->b_rptr += 4 , om->b_wptr += 2) {
					*(int16_t*)om->b_wptr = *(int16_t*)im->b_rptr;
				}
				ms_queue_put(f->outputs[0], om);
				freemsg(im);
			}
		}
	}
}

static void adapter_postprocess(MSFilter *f) {
	AdapterState *s = (AdapterState*)f->data;
	ms_bufferizer_uninit(&s->input_buffer1);
	ms_bufferizer_uninit(&s->input_buffer2);
	if (s->buffer1) ms_free(s->buffer1);
	if (s->buffer2) ms_free(s->buffer2);
	s->buffer1 = NULL;
	s->buffer2 = NULL;
}

static int adapter_set_sample_rate(MSFilter *f, void *data) {
	AdapterState *s = (AdapterState*)f->data;
	s->sample_rate = *(int*)data;
	return 0;
}

static int adapter_get_sample_rate(MSFilter *f, void *data) {
	AdapterState *s = (AdapterState*)f->data;
	*(int*)data = s->sample_rate;
	return 0;
}

static int adapter_set_nchannels(MSFilter *f, void *data) {
	AdapterState *s = (AdapterState*)f->data;
	s->inputchans = *(int*)data;
	return 0;
}

static int adapter_get_nchannels(MSFilter *f, void *data) {
	AdapterState *s = (AdapterState*)f->data;
	*(int*)data = s->inputchans;
	return 0;
}

static int adapter_set_out_nchannels(MSFilter *f, void *data) {
	AdapterState *s = (AdapterState*)f->data;
	s->outputchans = *(int*)data;
	return 0;
}

static int adapter_get_out_nchannels(MSFilter *f, void *data) {
	AdapterState *s = (AdapterState*)f->data;
	*(int*)data = s->outputchans;
	return 0;
}

static MSFilterMethod methods[] = {
	{	MS_FILTER_SET_SAMPLE_RATE, adapter_set_sample_rate},
	{	MS_FILTER_GET_SAMPLE_RATE, adapter_get_sample_rate},
	{	MS_FILTER_SET_NCHANNELS , adapter_set_nchannels },
	{	MS_FILTER_GET_NCHANNELS, adapter_get_nchannels },
	{	MS_CHANNEL_ADAPTER_SET_OUTPUT_NCHANNELS, adapter_set_out_nchannels },
	{	MS_CHANNEL_ADAPTER_GET_OUTPUT_NCHANNELS, adapter_get_out_nchannels },
	{	0,	NULL }
};

MSFilterDesc ms_channel_adapter_desc = {
	MS_CHANNEL_ADAPTER_ID,
	"MSChannelAdapter",
	N_("A filter that converts from mono to stereo and vice versa."),
	MS_FILTER_OTHER,
	NULL,
	2,
	1,
	adapter_init,
	adapter_preprocess,
	adapter_process,
	adapter_postprocess,
	adapter_uninit,
	methods
};

MS_FILTER_DESC_EXPORT(ms_channel_adapter_desc)
