/*
 * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
 * All Rights Reserved
 *
 * Author: Eric Wild <ewild@sysmocom.de>
 *
 * 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 "ms.h"
#include "sigProcLib.h"
#include "signalVector.h"
#include "grgsm_vitac/grgsm_vitac.h"
extern "C" {
#include "sch.h"
}

#if !defined(SYNCTHINGONLY) || !defined(NODAMNLOG)
#define DBGLG(...) ms_trx::dummy_log()
#else
#define DBGLG(...) std::cerr
#endif

#if !defined(SYNCTHINGONLY)
#define DBGLG2(...) ms_trx::dummy_log()
#else
#define DBGLG2(...) std::cerr
#endif

static bool decode_sch(float *bits, bool update_global_clock)
{
	struct sch_info sch;
	ubit_t info[GSM_SCH_INFO_LEN];
	sbit_t data[GSM_SCH_CODED_LEN];

	float_to_sbit(&bits[3], &data[0], 62, 39);
	float_to_sbit(&bits[106], &data[39], 62, 39);

	if (!gsm_sch_decode(info, data)) {
		gsm_sch_parse(info, &sch);

		DBGLG() << "SCH : Decoded values" << std::endl;
		DBGLG() << "    BSIC: " << sch.bsic << std::endl;
		DBGLG() << "    TSC: " << (sch.bsic & 0x7) << std::endl;
		DBGLG() << "    T1  : " << sch.t1 << std::endl;
		DBGLG() << "    T2  : " << sch.t2 << std::endl;
		DBGLG() << "    T3p : " << sch.t3p << std::endl;
		DBGLG() << "    FN  : " << gsm_sch_to_fn(&sch) << std::endl;
		return true;
	}
	return false;
}

static void check_rcv_fn(GSM::Time t, bool first, unsigned int &lastfn, unsigned int &fnbm)
{
	if (first && t.TN() == 0) {
		lastfn = t.FN();
		fnbm = 1 << 0;
		first = false;
	}
	if (!first && t.FN() != (int)lastfn) {
		if (fnbm != 255)
			std::cerr << "rx " << lastfn << ":" << fnbm << " " << __builtin_popcount(fnbm) << std::endl;
		lastfn = t.FN();
		fnbm = 1 << t.TN();
	}

	fnbm |= 1 << t.TN();
}

static void handle_it(one_burst &e, signalVector &burst, unsigned int tsc, int scale)
{
	std::fill(burst.begin(), burst.begin() + burst.size(), 0.0);
	const auto is_sch = gsm_sch_check_ts(e.gsmts.TN(), e.gsmts.FN());
	const auto is_fcch = gsm_fcch_check_ts(e.gsmts.TN(), e.gsmts.FN());

	if (is_fcch)
		return;

	if (is_sch) {
		char outbin[148];
		convert_and_scale(burst.begin(), e.burst, ONE_TS_BURST_LEN * 2, SAMPLE_SCALE_FACTOR);
		std::stringstream dbgout;
#if 0
		{
			struct estim_burst_params ebp;
			auto rv2 = detectSCHBurst(burst, 4, 4, sch_detect_type::SCH_DETECT_FULL, &ebp);
			auto bits = demodAnyBurst(burst, SCH, 4, &ebp);
			// clamp_array(bits->begin(), 148, 1.5f);
			for (auto &i : *bits)
				i = (i > 0 ? 1 : -1);

			auto rv = decode_sch(bits->begin(), false);
			dbgout << "U DET@" << (rv2 ? "yes " : "   ") << "Timing offset     " << ebp.toa
			       << " symbols, DECODE: " << (rv ? "yes" : "---") << " ";

			delete bits;
		}
#endif
		{
			convert_and_scale(burst.begin(), burst.begin(), ONE_TS_BURST_LEN * 2, 1.f / float(scale));

			std::complex<float> channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
			auto ss = reinterpret_cast<std::complex<float> *>(burst.begin());
			int d_c0_burst_start = get_sch_chan_imp_resp(ss, &channel_imp_resp[0]);
			detect_burst(ss, &channel_imp_resp[0], d_c0_burst_start, outbin);

			SoftVector bits;
			bits.resize(148);
			for (int i = 0; i < 148; i++) {
				bits[i] = (!outbin[i]); // < 1 ? -1 : 1;
			}

			auto rv = decode_sch(bits.begin(), false);
			dbgout << "U SCH@"
			       << " " << e.gsmts.FN() << ":" << e.gsmts.TN() << " " << d_c0_burst_start
			       << " DECODE:" << (rv ? "yes" : "---") << std::endl;
		}

		DBGLG() << dbgout.str();
		return;
	}
#if 1
	convert_and_scale(burst.begin(), e.burst, ONE_TS_BURST_LEN * 2, 1.f / float(scale));
	// std::cerr << "@" << tsc << " " << e.gsmts.FN() << ":" << e.gsmts.TN() << " " << ebp.toa << " "
	// 	  << std::endl;

	char outbin[148];
	auto ss = reinterpret_cast<std::complex<float> *>(burst.begin());
	float ncmax, dcmax;
	std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR], chan_imp_resp2[CHAN_IMP_RESP_LENGTH * d_OSR];
	auto normal_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp[0], &ncmax, tsc);
	auto dummy_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp2[0], &dcmax, TS_DUMMY);
	auto is_nb = ncmax > dcmax;

	DBGLG() << " U " << (is_nb ? "NB" : "DB") << "@ o nb: " << normal_burst_start << " o db: " << dummy_burst_start
		<< std::endl;

	if (is_nb)
		detect_burst(ss, &chan_imp_resp[0], normal_burst_start, outbin);
	else
		detect_burst(ss, &chan_imp_resp2[0], dummy_burst_start, outbin);
	;
#ifdef DBGXX
	// auto bits = SoftVector(148);
	// for (int i = 0; i < 148; i++)
	// 	(bits)[i] = outbin[i] < 1 ? -1 : 1;
#endif
#endif
}

void rcv_bursts_test(rx_queue_t *q, unsigned int *tsc, int scale)
{
	static bool first = true;
	unsigned int lastfn = 0;
	unsigned int fnbm = 0;
	signalVector burst(ONE_TS_BURST_LEN, 100, 100);

	cpu_set_t cpuset;

	CPU_ZERO(&cpuset);
	CPU_SET(1, &cpuset);

	auto rv = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
	if (rv < 0) {
		std::cerr << "affinity: errreur! " << std::strerror(errno);
		exit(0);
	}

	int prio = sched_get_priority_max(SCHED_RR);
	struct sched_param param;
	param.sched_priority = prio;
	rv = sched_setscheduler(0, SCHED_RR, &param);
	if (rv < 0) {
		std::cerr << "scheduler: errreur! " << std::strerror(errno);
		exit(0);
	}

	while (1) {
		one_burst e;
		while (!q->spsc_pop(&e)) {
			q->spsc_prep_pop();
		}

		check_rcv_fn(e.gsmts, first, lastfn, fnbm);

		handle_it(e, burst, *tsc, scale);
#ifdef DBGXX
		rv = detectSCHBurst(*burst, 4, 4, sch_detect_type::SCH_DETECT_FULL, &ebp);
		if (rv > 0)
			std::cerr << "#" << e.gsmts.FN() << ":" << e.gsmts.TN() << " " << ebp.toa << std::endl;
		sched_yield();
#endif
	}
}