/* Zeitansage processing
 *
 * (C) 2020 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/>.
 */

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include <time.h>
#include "../libsample/sample.h"
#include "../libdebug/debug.h"
#include "../libmobile/call.h"
#include "../libmncc/cause.h"
#include "zeitansage.h"

#define db2level(db)	pow(10, (double)(db) / 20.0)

/* list of calls */
zeit_call_t *zeit_call_list = NULL;

double audio_gain;
double early_audio;

#define	BEEP_TIME	16000	/* adjust distance from beep in samples */

int16_t *bntie_spl;
int bntie_size;
int bntie_time; /* sample index when intro is over */
int16_t *urrr_spl[24];
int urrr_size[24];
int urrr_time; /* sample index when hour is over */
int16_t *minuten_spl[60];
int minuten_size[60];
int minuten_time; /* sample index when minute is over */
int16_t *sekunden_spl[60];
int sekunden_size[60];
int sekunden_time; /* sample index when second is over */
int16_t *tut_spl;
int tut_size;
int tut_time; /* sample index when beep is over */

static const char *call_state_name(enum zeit_call_state state)
{
	static char invalid[16];

	switch (state) {
	case ZEIT_CALL_NULL:
		return "(NULL)";
	case ZEIT_CALL_BEEP:
		return "BEEP";
	case ZEIT_CALL_INTRO:
		return "INTRO";
	case ZEIT_CALL_HOUR:
		return "HOUR";
	case ZEIT_CALL_MINUTE:
		return "MINUTE";
	case ZEIT_CALL_SECOND:
		return "SECOND";
	case ZEIT_CALL_PAUSE:
		return "PAUSE";
	}

	sprintf(invalid, "invalid(%d)", state);
	return invalid;
}

static void zeit_display_status(void)
{
	zeit_call_t *call;

	display_status_start();
	for (call = zeit_call_list; call; call = call->next)
		display_status_subscriber(call->caller_id, call_state_name(call->state));
	display_status_end();
}


static void call_new_state(zeit_call_t *call, enum zeit_call_state new_state)
{
	if (call->state == new_state)
		return;
	PDEBUG(DZEIT, DEBUG_DEBUG, "State change: %s -> %s\n", call_state_name(call->state), call_state_name(new_state));
	call->state = new_state;
	zeit_display_status();
}

/* global init */
int zeit_init(double audio_level_dBm, int alerting)
{
	int i;

	/* the recordings are speech level, so we apply gain as is */
	audio_gain = db2level(audio_level_dBm);

	early_audio = alerting;

	/* get maximum length for each speech segment */
	tut_time = BEEP_TIME;
	bntie_time = bntie_size + tut_time;
	urrr_time = 0;
	for (i = 0; i < 24; i++) {
		if (urrr_size[i] > urrr_time)
			urrr_time = urrr_size[i];
	}
	urrr_time += bntie_time;
	minuten_time = 0;
	for (i = 0; i < 60; i++) {
		if (minuten_size[i] > minuten_time)
			minuten_time = minuten_size[i];
	}
	minuten_time += urrr_time;
	sekunden_time = 0;
	for (i = 0; i < 60; i += 10) {
		if (sekunden_size[i] > sekunden_time)
			sekunden_time = sekunden_size[i];
	}
	sekunden_time += minuten_time;

	PDEBUG(DZEIT, DEBUG_DEBUG, "Total time to play anouncement, starting with beep: %.2f seconds\n", (double)sekunden_time / 8000.0);

	return 0;
}

/* global exit */
void zeit_exit(void)
{
}

/* calculate what time to speak */
static void zeit_calc_time(zeit_call_t *call, time_t time_sec)
{
	struct tm *tm;

	/* we speak 10 seconds in advance */
	time_sec += 10;

	tm = localtime(&time_sec);
	call->h = tm->tm_hour;
	call->m = tm->tm_min;
	call->s = tm->tm_sec;

	PDEBUG(DZEIT, DEBUG_INFO, "The time at the next beep is: %d:%02d:%02d\n", call->h, call->m, call->s);
}

static void call_timeout(struct timer *timer);

/* Create call instance */
static zeit_call_t *zeit_call_create(uint32_t callref, const char *id)
{
	zeit_call_t *call, **callp;
	double now, time_offset;
	time_t time_sec;

	PDEBUG(DZEIT, DEBUG_INFO, "Creating call instance to play time for caller '%s'.\n", id);

	/* create */
	call = calloc(1, sizeof(*call));
	if (!call) {
		PDEBUG(DZEIT, DEBUG_ERROR, "No mem!\n");
		abort();
	}

	/* init */
	call->callref = callref;
	strncpy(call->caller_id, id, sizeof(call->caller_id) - 1);
	timer_init(&call->timer, call_timeout, call);
	now = get_time();
	time_offset = fmod(now, 10.0);
	time_sec = (int)floor(now / 10.0) * 10;
	call->spl_time = (int)(time_offset * 8000.0);
	zeit_calc_time(call, time_sec);
	timer_start(&call->timer, 10.0 - time_offset);

	/* link */
	callp = &zeit_call_list;
	while ((*callp))
		callp = &(*callp)->next;
	(*callp) = call;

	return call;
}

/* Destroy call instance */
static void zeit_call_destroy(zeit_call_t *call)
{
	zeit_call_t **callp;

	/* unlink */
	callp = &zeit_call_list;
	while ((*callp) != call)
		callp = &(*callp)->next;
	(*callp) = call->next;

	/* cleanup */
	timer_exit(&call->timer);

	/* destroy */
	free(call);

	/* update display */
	zeit_display_status();
}

/* play samples for one call */
static void call_play(zeit_call_t *call)
{
	int i = 0;
	int16_t chunk[160];
	sample_t spl[160];
	int16_t *play_spl;	/* current sample */
	int play_size;		/* current size of sample*/
	int play_index;		/* current sample index */
	int play_max;		/* total length to plax */
	int spl_time;		/* sample offset from start of 10 minutes */

	spl_time = call->spl_time;

next_sample:
	/* select sample from current sample time stamp */
	if (spl_time < tut_time) {
		play_index = spl_time;
		play_max = tut_time;
		play_size = tut_size;
		play_spl = tut_spl;
		call_new_state(call, ZEIT_CALL_BEEP);
	} else
	if (spl_time < bntie_time) {
		play_index = spl_time - tut_time;
		play_max = bntie_time - tut_time;
		play_size = bntie_size;
		play_spl = bntie_spl;
		call_new_state(call, ZEIT_CALL_INTRO);
	} else
	if (spl_time < urrr_time) {
		play_index = spl_time - bntie_time;
		play_max = urrr_time - bntie_time;
		play_size = urrr_size[call->h];
		play_spl = urrr_spl[call->h];
		call_new_state(call, ZEIT_CALL_HOUR);
	} else
	if (spl_time < minuten_time) {
		play_index = spl_time - urrr_time;
		play_max = minuten_time - urrr_time;
		play_size = minuten_size[call->m];
		play_spl = minuten_spl[call->m];
		call_new_state(call, ZEIT_CALL_MINUTE);
	} else
	if (spl_time < sekunden_time) {
		play_index = spl_time - minuten_time;
		play_max = sekunden_time - minuten_time;
		play_size = sekunden_size[call->s];
		play_spl = sekunden_spl[call->s];
		call_new_state(call, ZEIT_CALL_SECOND);
	} else {
		play_index = 0;
		play_max = 0;
		play_size = 0;
		play_spl = NULL;
		call_new_state(call, ZEIT_CALL_PAUSE);
	}

	while (i < 160) {
		if (!play_size) {
			chunk[i++] = 0.0;
			continue;
		}
		/* go to next sample */
		if (play_index == play_max)
			goto next_sample;
		/* announcement or silence, if finished or not set */
		if (play_index < play_size)
			chunk[i++] = play_spl[play_index];
		else
			chunk[i++] = 0.0;
		play_index++;
		spl_time++;
	}

	call->spl_time = spl_time;

	/* convert to samples, apply gain and send toward fixed network */
	int16_to_samples(spl, chunk, 160);
	for (i = 0; i < 160; i++)
		spl[i] *= audio_gain;
	call_up_audio(call->callref, spl, 160);
}

/* loop through all calls and play the announcement */
void call_down_clock(void)
{
	zeit_call_t *call;

	for (call = zeit_call_list; call; call = call->next) {
		/* no callref */
		if (!call->callref)
			continue;
		/* beep or announcement */
		call_play(call);
	}
}

/* Timeout handling */
static void call_timeout(struct timer *timer)
{
	zeit_call_t *call = (zeit_call_t *)timer->priv;
	double now, time_offset;
	time_t time_sec;

	PDEBUG(DZEIT, DEBUG_INFO, "Beep!\n");

	now = get_time();

	time_offset = fmod(now, 10.0);
	time_sec = (int)floor(now / 10.0) * 10;
	/* if somehow the timer fires (a tiny bit) before the 10 seconds start */
	if (time_offset > 5.0) {
		time_offset -= 10.0;
		time_sec += 10;
	}
	call->spl_time = 0;
	zeit_calc_time(call, time_sec);
	timer_start(&call->timer, 10.0 - time_offset);
}

/* Call control starts call towards clock */
int call_down_setup(int callref, const char __attribute__((unused)) *caller_id, enum number_type __attribute__((unused)) caller_type, const char *dialing)
{
	zeit_call_t __attribute__((unused)) *call;

	/* create call process to page station or send out-of-order message */
	call = zeit_call_create(callref, caller_id);
	if (early_audio) {
		call_up_alerting(callref);
		call_up_early(callref);
	} else
		call_up_answer(callref, dialing);

	return 0;
}

void call_down_answer(int __attribute__((unused)) callref)
{
}

static void _release(int callref, int __attribute__((unused)) cause)
{
	zeit_call_t *call;

	PDEBUG(DZEIT, DEBUG_INFO, "Call has been disconnected by network.\n");

	for (call = zeit_call_list; call; call = call->next) {
		if (call->callref == callref)
			break;
	}
	if (!call) {
		PDEBUG(DZEIT, DEBUG_NOTICE, "Outgoing disconnect, but no callref!\n");
		call_up_release(callref, CAUSE_INVALCALLREF);
		return;
	}

	zeit_call_destroy(call);
}

/* Call control sends disconnect.
 * A queued ID will be kept until transmitted by mobile station.
 */
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);
}

/* Receive audio from call instance. */
void call_down_audio(int __attribute__((unused)) callref, sample_t __attribute__((unused)) *samples, int __attribute__((unused)) count)
{
}

void dump_info(void) {}

void sender_receive(sender_t __attribute__((unused)) *sender, sample_t __attribute__((unused)) *samples, int __attribute__((unused)) length, double __attribute__((unused)) rf_level_db) {}
void sender_send(sender_t __attribute__((unused)) *sender, sample_t __attribute__((unused)) *samples, uint8_t __attribute__((unused)) *power, int __attribute__((unused)) length) {}

