/*! \file i460_mux.c
 * ITU-T I.460 sub-channel multiplexer + demultiplexer */
/*
 * (C) 2020 by Harald Welte <laforge@gnumonks.org>
 *
 * SPDX-License-Identifier: GPL-2.0+
 *
 *  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 2 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, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *  MA  02110-1301, USA.
 */

#include <errno.h>

#include <osmocom/core/bits.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/i460_mux.h>

/* count the number of sub-channels in this I460 slot */
static int osmo_i460_subchan_count(struct osmo_i460_timeslot *ts)
{
	int i, num_used = 0;

	for (i = 0; i < ARRAY_SIZE(ts->schan); i++) {
		if (ts->schan[i].rate != OSMO_I460_RATE_NONE)
			num_used++;
	}

	return num_used;
}

/* does this channel have no sub-streams (single 64k subchannel)? */
static bool osmo_i460_has_single_64k_schan(struct osmo_i460_timeslot *ts)
{
	if (osmo_i460_subchan_count(ts) != 1)
		return false;

	if (ts->schan[0].rate != OSMO_I460_RATE_64k)
		return false;

	return true;
}

/***********************************************************************
 * Demultiplexer
 ***********************************************************************/

/* append a single bit to a sub-channel */
static void demux_subchan_append_bit(struct osmo_i460_subchan *schan, uint8_t bit)
{
	struct osmo_i460_subchan_demux *demux = &schan->demux;

	OSMO_ASSERT(demux->out_bitbuf);
	OSMO_ASSERT(demux->out_idx < demux->out_bitbuf_size);

	demux->out_bitbuf[demux->out_idx++] = bit ? 1 : 0;

	if (demux->out_idx >= demux->out_bitbuf_size) {
		if (demux->out_cb_bits)
			demux->out_cb_bits(demux->user_data, demux->out_bitbuf, demux->out_idx);
		else {
			/* pack bits into bytes */
			OSMO_ASSERT((demux->out_idx % 8) == 0);
			unsigned int num_bytes = demux->out_idx / 8;
			uint8_t bytes[num_bytes];
			osmo_ubit2pbit(bytes, demux->out_bitbuf, demux->out_idx);
			demux->out_cb_bytes(demux->user_data, bytes, num_bytes);
		}
		demux->out_idx = 0;
	}
}

/* extract those bits relevant to this schan of each byte in 'data' */
static void demux_subchan_extract_bits(struct osmo_i460_subchan *schan, const uint8_t *data, size_t data_len)
{
	int i;

	for (i = 0; i < data_len; i++) {
		uint8_t inbyte = data[i];
		uint8_t inbits = inbyte >> schan->bit_offset;

		/* extract the bits relevant to the given schan */
		switch (schan->rate) {
		case OSMO_I460_RATE_8k:
			demux_subchan_append_bit(schan, inbits & 0x01);
			break;
		case OSMO_I460_RATE_16k:
			demux_subchan_append_bit(schan, inbits & 0x01);
			demux_subchan_append_bit(schan, inbits & 0x02);
			break;
		case OSMO_I460_RATE_32k:
			demux_subchan_append_bit(schan, inbits & 0x01);
			demux_subchan_append_bit(schan, inbits & 0x02);
			demux_subchan_append_bit(schan, inbits & 0x04);
			demux_subchan_append_bit(schan, inbits & 0x08);
			break;
		case OSMO_I460_RATE_64k:
			demux_subchan_append_bit(schan, inbits & 0x01);
			demux_subchan_append_bit(schan, inbits & 0x02);
			demux_subchan_append_bit(schan, inbits & 0x04);
			demux_subchan_append_bit(schan, inbits & 0x08);
			demux_subchan_append_bit(schan, inbits & 0x10);
			demux_subchan_append_bit(schan, inbits & 0x20);
			demux_subchan_append_bit(schan, inbits & 0x40);
			demux_subchan_append_bit(schan, inbits & 0x80);
			break;
		default:
			OSMO_ASSERT(0);
		}
	}
}

/*! Data from E1 timeslot into de-multiplexer
 *  \param[in] ts timeslot state
 *  \param[in] data input data bytes as received from E1/T1
 *  \param[in] data_len length of data in bytes */
void osmo_i460_demux_in(struct osmo_i460_timeslot *ts, const uint8_t *data, size_t data_len)
{
	struct osmo_i460_subchan *schan;
	struct osmo_i460_subchan_demux *demux;
	int i;

	/* fast path if entire 64k slot is used */
	if (osmo_i460_has_single_64k_schan(ts)) {
		schan = &ts->schan[0];
		demux = &schan->demux;
		if (demux->out_cb_bytes)
			demux->out_cb_bytes(demux->user_data, data, data_len);
		else {
			ubit_t bits[data_len*8];
			osmo_pbit2ubit(bits, data, data_len*8);
			demux->out_cb_bits(demux->user_data, bits, data_len*8);
		}
		return;
	}

	/* Slow path iterating over all lchans */
	for (i = 0; i < ARRAY_SIZE(ts->schan); i++) {
		schan = &ts->schan[i];
		if (schan->rate == OSMO_I460_RATE_NONE)
			continue;
		demux_subchan_extract_bits(schan, data, data_len);
	}
}


/***********************************************************************
 * Multiplexer
 ***********************************************************************/

/*! enqueue a to-be-transmitted message buffer containing unpacked bits */
void osmo_i460_mux_enqueue(struct osmo_i460_subchan *schan, struct msgb *msg)
{
	OSMO_ASSERT(msgb_length(msg) > 0);
	msgb_enqueue(&schan->mux.tx_queue, msg);
}

/* mux: pull the next bit out of the given sub-channel */
static ubit_t mux_schan_provide_bit(struct osmo_i460_subchan *schan)
{
	struct osmo_i460_subchan_mux *mux = &schan->mux;
	struct msgb *msg;
	ubit_t bit;

	/* if we don't have anything to transmit, return '1' bits */
	if (llist_empty(&mux->tx_queue))
		return 0x01;

	msg = llist_entry(mux->tx_queue.next, struct msgb, list);
	bit = msgb_pull_u8(msg);

	/* free msgb if we have pulled the last bit */
	if (msgb_length(msg) <= 0) {
		llist_del(&msg->list);
		talloc_free(msg);
	}

	return bit;
}

/*! provide one byte with the subchan-specific bits of given sub-channel.
 *  \param[in] schan sub-channel that is to provide bits
 *  \parma[out] mask bitmask of those bits filled in
 *  \returns bits of given sub-channel */
static uint8_t mux_subchan_provide_bits(struct osmo_i460_subchan *schan, uint8_t *mask)
{
	uint8_t outbits = 0;
	uint8_t outmask;

	switch (schan->rate) {
	case OSMO_I460_RATE_8k:
		outbits = mux_schan_provide_bit(schan);
		outmask = 0x01;
		break;
	case OSMO_I460_RATE_16k:
		outbits |= mux_schan_provide_bit(schan) << 1;
		outbits |= mux_schan_provide_bit(schan) << 0;
		outmask = 0x03;
		break;
	case OSMO_I460_RATE_32k:
		outbits |= mux_schan_provide_bit(schan) << 3;
		outbits |= mux_schan_provide_bit(schan) << 2;
		outbits |= mux_schan_provide_bit(schan) << 1;
		outbits |= mux_schan_provide_bit(schan) << 0;
		outmask = 0x0F;
		break;
	case OSMO_I460_RATE_64k:
		outbits |= mux_schan_provide_bit(schan) << 7;
		outbits |= mux_schan_provide_bit(schan) << 6;
		outbits |= mux_schan_provide_bit(schan) << 5;
		outbits |= mux_schan_provide_bit(schan) << 4;
		outbits |= mux_schan_provide_bit(schan) << 3;
		outbits |= mux_schan_provide_bit(schan) << 2;
		outbits |= mux_schan_provide_bit(schan) << 1;
		outbits |= mux_schan_provide_bit(schan) << 0;
		outmask = 0xFF;
		break;
	default:
		OSMO_ASSERT(0);
	}
	*mask = outmask << schan->bit_offset;
	return outbits << schan->bit_offset;
}

/* provide one byte of multiplexed I.460 bits */
static uint8_t mux_timeslot_provide_bits(struct osmo_i460_timeslot *ts)
{
	int i, count = 0;
	uint8_t ret = 0xff; /* unused bits must be '1' as per I.460 */

	for (i = 0; i < ARRAY_SIZE(ts->schan); i++) {
		struct osmo_i460_subchan *schan = &ts->schan[i];
		uint8_t bits, mask;

		if (schan->rate == OSMO_I460_RATE_NONE)
			continue;
		count++;
		bits = mux_subchan_provide_bits(schan, &mask);
		ret &= ~mask;
		ret |= bits;
	}

	return ret;
}


/*! Data from E1 timeslot into de-multiplexer
 *  \param[in] ts timeslot state
 *  \param[out] out caller-provided buffer where to store generated output bytes
 *  \param[in] out_len number of bytes to be stored at out
 */
int osmo_i460_mux_out(struct osmo_i460_timeslot *ts, uint8_t *out, size_t out_len)
{
	int i;

	/* fast path if entire 64k slot is used */
	//if (osmo_i460_has_single_64k_schan(ts)) { }

	for (i = 0; i < out_len; i++)
		out[i] = mux_timeslot_provide_bits(ts);

	return out_len;
}


/***********************************************************************
 * Initialization / Control
 ***********************************************************************/


static int alloc_bitbuf(void *ctx, struct osmo_i460_subchan *schan, size_t num_bits)
{
	struct osmo_i460_subchan_demux *demux = &schan->demux;

	talloc_free(demux->out_bitbuf);
	demux->out_bitbuf = talloc_zero_size(ctx, num_bits);
	if (!demux->out_bitbuf)
		return -ENOMEM;
	demux->out_bitbuf_size = num_bits;

	return 0;
}


static int find_unused_subchan_idx(const struct osmo_i460_timeslot *ts)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(ts->schan); i++) {
		const struct osmo_i460_subchan *schan = &ts->schan[i];
		if (schan->rate == OSMO_I460_RATE_NONE)
			return i;
	}
	return -1;
}

/* reset subchannel struct into a defined state */
static void subchan_reset(struct osmo_i460_subchan *schan, bool first_time)
{
	/* Before we zero out the subchannel struct, we must be sure that the
	 * tx_queue is cleared and all dynamically allocated memory is freed.
	 * However, on an uninitalized subchannel struct we can not be sure
	 * that the pointers are valid. If the subchannel is reset the first
	 * time the caller must set first_time to true. */
	if (!first_time) {
		if (schan->demux.out_bitbuf)
			talloc_free(schan->demux.out_bitbuf);
		msgb_queue_free(&schan->mux.tx_queue);
	}

	/* Reset subchannel to a defined state */
	memset(schan, 0, sizeof(*schan));
	schan->rate = OSMO_I460_RATE_NONE;
	INIT_LLIST_HEAD(&schan->mux.tx_queue);
}

/*! initialize an I.460 timeslot */
void osmo_i460_ts_init(struct osmo_i460_timeslot *ts)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(ts->schan); i++) {
		struct osmo_i460_subchan *schan = &ts->schan[i];
		subchan_reset(schan, true);
	}
}

/*! add a new sub-channel to the given timeslot
 *  \param[in] ctx talloc context from where to allocate the internal buffer
 *  \param[in] ts timeslot to which to add a sub-channel
 *  \param[in] chd description of the sub-channel to be added
 *  \return pointer to sub-channel on success, NULL on error */
struct osmo_i460_subchan *
osmo_i460_subchan_add(void *ctx, struct osmo_i460_timeslot *ts, const struct osmo_i460_schan_desc *chd)
{
	struct osmo_i460_subchan *schan;
	int idx, rc;

	idx = find_unused_subchan_idx(ts);
	if (idx < 0)
		return NULL;

	schan = &ts->schan[idx];

	schan->rate = chd->rate;
	schan->bit_offset = chd->bit_offset;

	schan->demux.out_cb_bits = chd->demux.out_cb_bits;
	schan->demux.out_cb_bytes = chd->demux.out_cb_bytes;
	schan->demux.user_data = chd->demux.user_data;
	rc = alloc_bitbuf(ctx, schan, chd->demux.num_bits);
	if (rc < 0) {
		subchan_reset(schan, false);
		return NULL;
	}

	/* return number of schan in use */
	return schan;
}

/* remove a su-channel from the multiplex */
void osmo_i460_subchan_del(struct osmo_i460_subchan *schan)
{
	subchan_reset(schan, false);
}

/*! @} */
