/* Osmo BTS TRX WebSDR API implementation
 *
 * Copyright (C) 2026 Wavelet Lab <info@wavelet-lab.com>
 *
 * All Rights Reserved
 *
 * 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 <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

#include <osmocom/core/application.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/select.h>
#include <osmocom/core/talloc.h>

#include <osmocom/gsm/gsm_utils.h>

#include <osmo-bts/bts.h>
#include <osmo-bts/abis.h>
#include <osmo-bts/logging.h>

#include <osmo-trx-websdr.h>
#include <trx_stats_json.h>
#include <trx_if.h>
#include <assert.h>

#include "osmo-bts-trx-websdr.h"
#include "stats_json.h"


#define MAX_BUFS 32

#define CHUNK 625
#define CHUNK_COUNT 8
#define BURST_DURATION (CHUNK * CHUNK_COUNT)

unsigned modulateBits(const uint8_t *bits, unsigned bitlen, int16_t *outbuf);

extern struct gsm_bts *g_bts;

uint64_t sdr_send_ts = 0;
uint64_t g_last_ts = 0;

unsigned wptr = 0;
unsigned rptr = 0;
unsigned total_tx_late = 0;
unsigned start_fn = ~0u;

int16_t sdr_send_buffer[MAX_BUFS * BURST_DURATION * 2];
uint64_t sdr_send_bts[MAX_BUFS];

static struct trx_l1h *g_l1h = NULL;


int apply_changes(const char *band, unsigned arfcn, const char *ipaccess_unitid, unsigned osmux_port)
{
	enum gsm_band b = gsm_band_parse(band);
	long ipaccess_id = strtol(ipaccess_unitid, NULL, 10);

	if (b == -EINVAL || ipaccess_id <= 0 || osmux_port <= 0)
		return -EINVAL;

	g_bts->band = b;
	g_bts->ip_access.site_id = ipaccess_id;
	g_bts->ip_access.bts_id = 0;
	g_bts->osmux.local_port = osmux_port;

	abis_update_bts_info(g_bts->ip_access.site_id, g_bts->ip_access.bts_id);
	return 0;
}

bool osmobts_init(const char *file)
{
	const char *params[] = {
		"./webusb-bts",
		"-c",
		file,
	};
	const size_t num_params = ARRAY_SIZE(params);

	int res = bts_main(num_params, (char **)params);
	LOGP(DTRX, LOGL_INFO, "Started Osmo BTS WebSDR API with config file: '%s', returned %d\n", file, res);

	if (res != EXIT_SUCCESS)
		return false;

	return true;
}

bool osmobts_apply(const char *band, unsigned arfcn, const char *ipaccess_unitid, unsigned osmux_port)
{
	int res = apply_changes(band, arfcn, ipaccess_unitid, osmux_port);
	if (res)
		return false;

	LOGP(DTRX, LOGL_INFO, "bts_apply config `%s:%d:%s:%d`\n", band, arfcn, ipaccess_unitid, osmux_port);

	return true;
}

int apitrx_init(struct trx_l1h *l1h)
{
	int res;

	res = osmotrxlib_init();
	if (res)
		return res;

	g_l1h = l1h;

	return 0;
}

int osmobts_get_stats(const char *group, char *buf, unsigned buflen)
{
	if (!strncmp(group, "stats", 5))
		return stats_to_json(buf, buflen);
	else if (!strncmp(group, "rate-counters", 13))
		return rate_counters_to_json(buf, buflen);
	else if (!strncmp(group, "bts", 3))
		return bts_to_json(buf, buflen);
	else if (!strncmp(group, "trx", 3))
		return trx_to_json(buf, buflen);
	else if (!strncmp(group, "transceiver", 11))
		return transceiver_to_json(buf, buflen);
	else if (!strncmp(group, "websdr", 6))
		return websdr_to_json(buf, buflen);

	return -EINVAL;
}

int apitrx_cmd_call(const char *command, char *response, size_t response_size)
{
	int res = osmotrxlib_process_command(command, response, response_size);
	LOGP(DTRX, LOGL_INFO, "Got CMD `%s` => res:%d, response: `%s`\n",
				command, res, response ? response : "NULL");

	return res;
}


int apitrx_tx_call(unsigned phyid, char *pdu_buffer, size_t pdu_len)
{
	int res = osmotrxlib_put_tx_burst(pdu_buffer, pdu_len);
	if (res < 0) {
		LOGP(DTRX, LOGL_ERROR, "osmotrxlib_put_tx_burst failed, res = %d\n", res);
		return res;
	}

	res = osmotrxlib_get_tx_short_vector();

	return res;
}

void osmotrxlib_on_clock_data(unsigned clock)
{
	if (start_fn == ~0u)
		start_fn = clock;

	fprintf(stderr, "CLOCK IND %u (start_fn=%u)\n", clock, start_fn);
	LOGP(DTRX, LOGL_INFO, "CLOCK IND %d\n", clock);
	apibts_clock_ind(g_l1h, clock);
}

int osmobts_push_rx_short_vector(short *buffers, int samples, int underrun, uint64_t ts, unsigned skipsamples)
{
	char burst_data[512];
	int res;

	g_last_ts = ts;

	res = osmotrxlib_push_rx_short_vector(buffers, samples, underrun, ts, skipsamples);
	if (res < 0) {
		LOGP(DTRX, LOGL_ERROR, "osmotrxlib_push_rx_short_vector failed res = %d\n", res);
		return res;
	}

	for (unsigned i = 0; i < 4; i++) {
		res = osmotrxlib_get_rx_burst(burst_data, sizeof(burst_data));
		if (res < 0) {
			LOGP(DTRX, LOGL_ERROR, "osmotrxlib_get_rx_burst failed res = %d burst = %d\n", res, i);
			continue;
		}
		if (res == 0) {
			LOGP(DTRX, LOGL_INFO, "osmotrxlib_get_rx_burst i=%d nodata\n", i);
			continue;
		}

		res = trx_data_read(g_l1h, (uint8_t *)burst_data, res);
		if (res < 0) {
			LOGP(DTRX, LOGL_ERROR, "trx_data_read failed res = %d burst = %d\n", res, i);
			continue;
		}
	}

	return 0;
}

int osmobts_get_tx_short_vector(uint64_t *ts, int16_t **bptr, unsigned *ptotlate)
{
	if (wptr == rptr)
		return -ENODATA;

	unsigned idx = rptr % MAX_BUFS;

	*ts = sdr_send_bts[idx];
	*bptr = sdr_send_buffer + idx * BURST_DURATION * 2;
	*ptotlate = total_tx_late;
	rptr++;

	return 0;
}

int apitrx_tx_call_fn(unsigned phyid, const struct burstinfo *pbi)
{
	/* Ignore slot information and types for now */
	unsigned boff = 0;
	unsigned blen;
	unsigned idx = wptr % MAX_BUFS;
	int16_t *outptr;
	assert(wptr < rptr + MAX_BUFS);

	if (sdr_send_ts == 0) {
		/* Start_fn is actually RX_FN + 2, so
		 * Delta is TX_FN - (IND_FN - 2), which is TX_FN - RX_FN!
		 */
		sdr_send_ts = (pbi->fn - start_fn + 2) * BURST_DURATION;
	}

	/* warm up things */
	if (sdr_send_ts < 100 * BURST_DURATION) {
		sdr_send_ts += BURST_DURATION;
		return 1;
	}

	if (g_last_ts + 256 > sdr_send_ts) {
		total_tx_late++;
		sdr_send_ts += BURST_DURATION;
		return 1;
	}

	outptr = sdr_send_buffer + idx * BURST_DURATION * 2;

	/* Iterate over TN */
	for (unsigned i = 0; i < CHUNK_COUNT; i++) {
		const struct burstinfo *pb = &pbi[i];
		blen = modulateBits(pb->burst, pb->len, outptr + boff);

		assert(blen == 612);
		assert(pb->tn == i);

		boff += CHUNK << 1;
	}

	sdr_send_bts[idx] = sdr_send_ts;

	wptr++;
	sdr_send_ts += BURST_DURATION;

	return 0;
}
