/* 5-Ton-Folge call processing
 *
 * (C) 2021 by Andreas Eversberg <jolly@eversberg.eu>
 * All Rights Reserved
 *
 * 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/>.
 */

#define CHAN fuenf->sender.kanal

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include "../libsample/sample.h"
#include "../liblogging/logging.h"
#include "../libmobile/call.h"
#include "../libmobile/cause.h"
#include <osmocom/cc/message.h>
#include "../liboptions/options.h"
#include "fuenf.h"
#include "dsp.h"

void bos_list_channels(void)
{
	printf("Channels\tBand\n");
	printf("------------------------\n");
	printf("101 - 125\t2 Meter\n");
	printf("  1 -  92\t2 Meter\n");
	printf("347 - 509\t4 Meter\n");
        printf("-> Give channel number or any frequency in MHz (using a dot, e.g. '169.810').\n");
        printf("\n");
}

/* Convert channel to frequency */
double bos_kanal2freq(const char *kanal)
{
        int k;

	if (strchr(kanal, '.'))
		return atof(kanal) * 1e6;

	k = atoi(kanal);

	if (k >= 101 && k <= 125)
		return 169.810e6 + 20e3 * (k - 101);

	if (k >= 1 && k <= 92)
		return 172.160e6 + 20e3 * (k - 1);

	if (k >= 347 && k <= 509)
		return 84.015e6 + 20e3 * (k - 347);

	return 0.0;
}

/* Convert frequency to channel, if possible */
const char *bos_freq2kanal(const char *freq)
{
	double f;
	char kanal[8];

	if (!strchr(freq, '.'))
		return options_strdup(freq);

	f = atof(freq) * 1e6;

	if (f >= 169.810e6 && f <= 170.290e6 && fmod(f - 169.810e6, 20e3) == 0.0) {
		sprintf(kanal, "%.0f", (f - 169.810e6) / 20e3 + 101);
		return options_strdup(kanal);
	}

	if (f >= 172.160e6 && f <= 173.980e6 && fmod(f - 172.160e6, 20e3) == 0.0) {
		sprintf(kanal, "%.0f", (f - 172.160e6) / 20e3 + 1);
		return options_strdup(kanal);
	}

	if (f >= 84.015e6 && f <= 87.255e6 && fmod(f - 84.015e6, 20e3) == 0.0) {
		sprintf(kanal, "%.0f", (f - 84.015e6) / 20e3 + 347);
		return options_strdup(kanal);
	}

	return options_strdup(freq);
}

const char *fuenf_state_name[] = {
	"IDLE",
	"RUF",
	"DURCHSAGE",
};

const char *fuenf_funktion_name[8] = {
	"Ruf",
	"Feueralarm",
	"Probealarm",
	"Warnung der Befoelkerung",
	"ABC-Alarm",
	"Entwarnung",
	"Katastrophenalarm",
	"Turbo-Scanner",
};

/* check if number is a valid pager ID */
const char *bos_number_valid(const char *number)
{
	/* assume that the number has valid length(s) and digits */

	if (number[5] && (number[5] < '0' || number[5] > '6'))
		return "Illegal 'Sirenenalarm' digit #6 (Use 1..6 only)";
	return NULL;
}

int fuenf_init(void)
{
	return 0;
}

void fuenf_exit(void)
{
}

static void fuenf_display_status(void)
{
	sender_t *sender;
	fuenf_t *fuenf;

	display_status_start();
	for (sender = sender_head; sender; sender = sender->next) {
		fuenf = (fuenf_t *) sender;
		display_status_channel(fuenf->sender.kanal, NULL, fuenf_state_name[fuenf->state]);
	}
	display_status_end();
}

void fuenf_new_state(fuenf_t *fuenf, enum fuenf_state new_state)
{
	if (fuenf->state == new_state)
		return;
	LOGP_CHAN(DFUENF, LOGL_DEBUG, "State change: %s -> %s\n", fuenf_state_name[fuenf->state], fuenf_state_name[new_state]);
	fuenf->state = new_state;
	fuenf_display_status();
}

static int fuenf_scan_or_loopback(fuenf_t *fuenf)
{
	char rufzeichen[16];

	if (fuenf->scan_from < fuenf->scan_to) {
		sprintf(rufzeichen, "%05d", fuenf->scan_from++);
		LOGP_CHAN(DFUENF, LOGL_NOTICE, "Transmitting ID '%s'.\n", rufzeichen);
		dsp_setup(fuenf, rufzeichen, fuenf->default_funktion);
		return 1;
	}

	if (fuenf->sender.loopback) {
		LOGP(DFUENF, LOGL_INFO, "Sending 5-Ton-Ruf for loopback test.\n");
		dsp_setup(fuenf, "10357", FUENF_FUNKTION_FEUER);
		return 1;
	}

	return 0;
}

/* Create transceiver instance and link to a list. */
int fuenf_create(const char *kanal, double frequency, const char *device, int use_sdr, int samplerate, double rx_gain, double tx_gain, int tx, int rx, double max_deviation, double signal_deviation, enum fuenf_funktion funktion, uint32_t scan_from, uint32_t scan_to, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback)
{
	fuenf_t *fuenf;
	int rc;

	fuenf = calloc(1, sizeof(*fuenf));
	if (!fuenf) {
		LOGP(DFUENF, LOGL_ERROR, "No memory!\n");
		return -ENOMEM;
	}

	LOGP(DFUENF, LOGL_DEBUG, "Creating '5-Ton-Folge' instance for 'Kanal' = %s (sample rate %d).\n", kanal, samplerate);

	/* init general part of transceiver */
	rc = sender_create(&fuenf->sender, kanal, frequency, frequency, device, use_sdr, samplerate, rx_gain, tx_gain, 0, 0, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback, PAGING_SIGNAL_NONE);
	if (rc < 0) {
		LOGP(DFUENF, LOGL_ERROR, "Failed to init transceiver process!\n");
		goto error;
	}

	/* init audio processing */
	rc = dsp_init_sender(fuenf, samplerate, max_deviation, signal_deviation);
	if (rc < 0) {
		LOGP(DFUENF, LOGL_ERROR, "Failed to init audio processing!\n");
		goto error;
	}

	fuenf->tx = tx;
	fuenf->rx = rx;
	fuenf->default_funktion = funktion;
	fuenf->scan_from = scan_from;
	fuenf->scan_to = scan_to;

	fuenf_display_status();

	LOGP(DFUENF, LOGL_NOTICE, "Created 'Kanal' %s\n", kanal);

	/* start scanning, if enabled, otherwise send loopback sequence, if enabled */
	fuenf_scan_or_loopback(fuenf);

	return 0;

error:
	fuenf_destroy(&fuenf->sender);

	return rc;
}

/* Destroy transceiver instance and unlink from list. */
void fuenf_destroy(sender_t *sender)
{
	fuenf_t *fuenf = (fuenf_t *) sender;

	LOGP(DFUENF, LOGL_DEBUG, "Destroying '5-Ton-Folge' instance for 'Kanal' = %s.\n", sender->kanal);

	dsp_cleanup_sender(fuenf);
	sender_destroy(&fuenf->sender);
	free(fuenf);
}

/* call sign was transmitted */
void fuenf_tx_done(fuenf_t *fuenf)
{
	LOGP_CHAN(DFUENF, LOGL_INFO, "Done sending 5-Ton-Ruf.\n");

	/* start scanning, if enabled, otherwise send loopback sequence, if enabled */
	if (fuenf_scan_or_loopback(fuenf)) {
		return;
	}

	/* go talker state */
	if (fuenf->callref && fuenf->tx_funktion == FUENF_FUNKTION_RUF) {
		LOGP_CHAN(DFUENF, LOGL_INFO, "Caller may talk now.\n");
		fuenf_new_state(fuenf, FUENF_STATE_DURCHSAGE);
		return;
	}

	/* go idle */
	fuenf_new_state(fuenf, FUENF_STATE_IDLE);
	if (fuenf->callref) {
		LOGP_CHAN(DFUENF, LOGL_INFO, "Releasing call toward network.\n");
		call_up_release(fuenf->callref, CAUSE_NORMAL);
	}
}

void fuenf_rx_callsign(fuenf_t *fuenf, const char *callsign)
{
	LOGP_CHAN(DFUENF, LOGL_INFO, "Received 5-Ton-Ruf with call sign '%s'.\n", callsign);
}

void fuenf_rx_function(fuenf_t *fuenf, enum fuenf_funktion funktion)
{
	LOGP_CHAN(DFUENF, LOGL_INFO, "Received function '%s'.\n", fuenf_funktion_name[funktion]);
}

/* Call control starts call towards transmitter. */
int call_down_setup(int callref, const char __attribute__((unused)) *caller_id, enum number_type __attribute__((unused)) caller_type, const char *dialing)
{
	char channel = '\0';
	sender_t *sender;
	fuenf_t *fuenf;
	char rufzeichen[6];
	enum fuenf_funktion funktion;

	/* find transmitter */
	for (sender = sender_head; sender; sender = sender->next) {
		/* skip channels that are different than requested */
		if (channel && sender->kanal[0] != channel)
			continue;
		fuenf = (fuenf_t *) sender;
		if (fuenf->state != FUENF_STATE_IDLE)
			continue;
		/* check if base station cannot transmit */
		if (!fuenf->tx)
			continue;
		break;
	}
	if (!sender) {
		if (channel)
			LOGP(DFUENF, LOGL_NOTICE, "Cannot page, because given station not available, rejecting!\n");
		else
			LOGP(DFUENF, LOGL_NOTICE, "Cannot page, no trasmitting station idle, rejecting!\n");
		return -CAUSE_NOCHANNEL;
	}

	strncpy(rufzeichen, dialing, 5);
	rufzeichen[5] = '\0';
	switch (dialing[5]) {
	case '0':
		funktion = FUENF_FUNKTION_RUF;
		break;
	case '1':
		funktion = FUENF_FUNKTION_FEUER;
		break;
	case '2':
		funktion = FUENF_FUNKTION_PROBE;
		break;
	case '3':
		funktion = FUENF_FUNKTION_WARNUNG;
		break;
	case '4':
		funktion = FUENF_FUNKTION_ABC;
		break;
	case '5':
		funktion = FUENF_FUNKTION_ENTWARNUNG;
		break;
	case '6':
		funktion = FUENF_FUNKTION_KATASTROPHE;
		break;
	case '\0':
		funktion = fuenf->default_funktion;
		break;
	default:
		return -CAUSE_INVALNUMBER;
	}

	LOGP_CHAN(DFUENF, LOGL_INFO, "Sending 5-Ton-Ruf with call sign '%s' and function '%s'.\n", rufzeichen, fuenf_funktion_name[funktion]);

	dsp_setup(fuenf, rufzeichen, funktion);

	fuenf_new_state(fuenf, FUENF_STATE_RUF);
	fuenf->callref = callref;
	/* must answer to hear paging tones. */
	call_up_answer(fuenf->callref, "");

	return 0;
}

void call_down_answer(int __attribute__((unused)) callref, struct timeval __attribute__((unused)) *tv_meter)
{
}


static void _release(int __attribute__((unused)) callref, int __attribute__((unused)) cause)
{
	sender_t *sender;
	fuenf_t *fuenf;

	LOGP(DFUENF, LOGL_INFO, "Call has been disconnected by network.\n");

	for (sender = sender_head; sender; sender = sender->next) {
		fuenf = (fuenf_t *) sender;
		if (fuenf->callref == callref)
			break;
	}
	if (!sender) {
		LOGP(DBNETZ, LOGL_NOTICE, "Outgoing release, but no callref!\n");
		/* don't send release, because caller already released */
		return;
        }

	/* remove call. go idle, if talking */
        fuenf->callref = 0;
	if (fuenf->state == FUENF_STATE_DURCHSAGE)
		fuenf_new_state(fuenf, FUENF_STATE_IDLE);
}

void call_down_disconnect(int callref, int cause)
{
	_release(callref, cause);

	call_up_release(callref, cause);
}

/* Call control releases call toward mobile station. */
void call_down_release(int callref, int cause)
{
	_release(callref, cause);
}

void dump_info(void) {}

