#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>

#include <libpicoevent.h>
#include "libvoice.h"
#include "line.h"
#include "line-dect.h"
#include "main.h"
#include "ubus.h"

#ifndef UCI_CONFIG_DIR
#define UCI_CONFIG_DIR "/etc/config/"
#endif

int log_level = LOG_INFO;
static int audio_rx_fd;
static int audio_tx_fd;
static int event_fd;
static const char event_str[] = "/tmp/endpt_event";
static const char audio_tx_str[] = "/tmp/endpt_audio_tx";
static const char audio_rx_str[] = "/tmp/endpt_audio_rx";
static pe_stream_t *event_stream;
static pe_stream_t *audio_rx_stream;
static pe_list_t *event_list;
static pe_bus_t *audio_rx_bus;
static pe_bus_t *audio_tx_bus;
int picoBase = -1;

// Send a media packet to Asterisk
static void send_media_to_asterisk(const struct media_packet_t *packet, int size) {
	pe_bus_send(audio_tx_bus, (uint8_t *)packet, size);
}

// Send a media packet to voice engine
void send_media_to_voice_engine(pe_packet_t *p) {
	int conIdx;
	struct media_packet_t *packet = (struct media_packet_t *)p->data;

	conIdx = voice_connection_find(packet->line, packet->connection_id);
	if (conIdx == -1 || (packet->rtp[0] != 0x80 && packet->rtp[0] != 0x81) || !packet->rtp_size) {
		ENDPT_ERR("%s: bad packet\n", __func__);
		return;
	}

	voice_write_media_packet(packet);
}

// Handle media stream from Asterisk on audio rx socket and re-posts it to internal audio rx bus
static void audio_rx_stream_handler(pe_stream_t *stream __attribute__((unused)), pe_event_t *event) {
	if (pe_bus_receive(audio_rx_bus, (pe_event_t *)event) < 0) {
		exit_failure("audio_bus rx buffer full\n");
		return; // Drop packets if we can't cope the pace
	}

	pe_bus_dispatch(audio_rx_bus);
}

// Notify main thread there is a new event. We need to
// make the ubus call from the main context. This function
// is executed by a libvoip thread.
int send_event_main(struct line_event_t *ev, char x) {
	if (!event_list || !event_fd) return -1;

	pe_list_add(event_list, (void*)ev);

	/* Trigger event loop to read event from queue. */
	write(event_fd, &x, sizeof(char));

	return 0;
}

// Handler for events sent to the main thread (in
// general the events originates from libvoip).
static void event_stream_handler(pe_stream_t *stream __attribute__((unused)), pe_event_t *event) {
	struct line_event_t *ev;

	if(!event || !event->in || event->count <= 0) return;

	switch(*event->in) {
		case EVENT_MAIN_LINE:
			ev = (struct line_event_t*) pe_list_get(event_list);
			if(!ev) break;
			ENDPT_DBG("Main event handler line %d event %s\n", ev->line, ev->name);
			ubus_call_asterisk(ev);
			ubus_broadcast_event(ev); // Also broadcast event to all
			break;

		case EVENT_MAIN_QUIT:
			ENDPT_DBG("Shutting down on quit event\n");
			ubus_disable_receive();
			voice_connection_deinit();
			line_dect_deinit();
			voice_line_deinit();
			voice_engine_shutdown();
			ubus_close();
			exit_succes("voicemngr exits");
			break;

		default:
			ENDPT_WARN("unknown event '%c'\n", *event->in);
			break;
	}
}

static void fifos_init(void) {
	int ret;

	/* Create event list for event fifo. */
	event_list = pe_list_new();
	if (!event_list)
		exit_failure("pe_list_new\n");

	/* Create event fifo. */
	ret = mkfifo(event_str, 0666);
	if ((ret == -1) && (errno != EEXIST)) exit_failure("Failed to create event pipe");
	event_fd = open(event_str, O_RDWR|O_NONBLOCK);
	if(event_fd == -1) exit_failure("Failed to open event pipe");

	/* Create audio fifos. Both ends open in R/W mode to become
	 * independent of daemon startup order and peer restart
	 * recovery. However, later each end use unidirectional flow. */
	ret = mkfifo(audio_tx_str, 0666);
	if ((ret == -1) && (errno != EEXIST)) exit_failure("Failed to create tx pipe");
	audio_tx_fd = open(audio_tx_str, O_RDWR|O_LARGEFILE|O_NONBLOCK);
	if(audio_tx_fd == -1) exit_failure("Failed to open tx pipe");
	ret = mkfifo(audio_rx_str, 0666);
	if ((ret == -1) && (errno != EEXIST)) exit_failure("Failed to create rx pipe");
	audio_rx_fd = open(audio_rx_str, O_RDWR|O_LARGEFILE|O_NONBLOCK);
	if(audio_rx_fd == -1) exit_failure("Failed to open rx pipe");
}

static void streams_and_buses_init(void) {
	/* Listen for callback data from libvoip in our main event loop.
	   We need to do it this way as ubus is not thread safe. */
	event_stream = pe_stream_new(event_fd);
	pe_stream_add_handler(event_stream, sizeof(char), event_stream_handler);
	pe_base_add_stream(picoBase, event_stream);

	/* Listen for audio frames from asterisk. */
	audio_rx_stream = pe_stream_new(audio_rx_fd);
	pe_stream_add_handler(audio_rx_stream, 4096, audio_rx_stream_handler);
	pe_base_add_stream(picoBase, audio_rx_stream);

	audio_rx_bus = pe_bus_new(audio_rx_fd);
	if (!audio_rx_bus) exit_failure("Failed to create audio_rx bus");
	pe_bus_add_handler(audio_rx_bus, send_media_to_voice_engine);

	/* Send audio frames to asterisk. */
	audio_tx_bus = pe_bus_new(audio_tx_fd);
	if (!audio_tx_bus) exit_failure("Failed to create audio_tx bus");
}

// Posix signal handler. Wake up main event loop to exit prog.
static void signal_handler(int signum __attribute__((unused)), siginfo_t *info __attribute__((unused)), void *ucontext __attribute__((unused))) {
	if(event_fd) send_event_main(NULL, EVENT_MAIN_QUIT);
}

// Setup Posix signal handling
static int signals_init(void) {
	const int signals_handled[] = { SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGPWR };
	struct sigaction action;
	unsigned int i;

	memset(&action, 0, sizeof(action));
	action.sa_sigaction = signal_handler;
	action.sa_flags = SA_SIGINFO | SA_RESTART;
	sigemptyset(&action.sa_mask);

	for(i = 0; i < ARRAY_SIZE(signals_handled); i++) {
		sigaddset(&action.sa_mask, signals_handled[i]);
	}

	for(i = 0; i < ARRAY_SIZE(signals_handled); i++) {
		if(sigaction(signals_handled[i], &action, NULL)) {
			perror("Error trapping signals");
			return -1;
		}
	}

	return 0;
}

static void voicemngr_event_report(int line, const char *event, int data) {
	/* Re-send the event to the main thread, which in turn re-send
	 * to Asterisk. We need to do this due to UBUS is not thread safe,
	 * so all UBUS calls must be done from a single thread. */
	if(!DEBUG_LOOPBACK) { // Skip when debugging
		struct line_event_t *msg = malloc(sizeof(struct line_event_t));

		if (!msg) {
			ENDPT_ERR("%s: out of memory\n", __func__);
			return;
		}
		msg->name = event;
		msg->data = (data < 0 ? 0 : data); // Discard negative values
		msg->line = line;
		send_event_main(msg, EVENT_MAIN_LINE);
	}
}

static int voicemngr_get_voice_port_cfg(const char *hw_board_voice_names, struct terminal_info_t *voice_port_cfg)
{
	int res = 0;
	char *duplication;
	char *str, *token, *saveptr;

	if (!hw_board_voice_names || *hw_board_voice_names == '\0') {
		ENDPT_ERR("%s: environmental variable hw_board_VoicePortNames is not defined or empty\n", __func__);
		return -1;
	}

	memset(voice_port_cfg, 0, sizeof(*voice_port_cfg));

	/* Duplicate the string to prevent environmental variable's value being modified by strtok_r(). That
	 * might cause the wrong value being returned when running the program multiple times */
	duplication = strdup(hw_board_voice_names);
	if (!duplication) {
		ENDPT_ERR("%s: out of memory\n", __func__);
		return -1;
	}
	ENDPT_DBG("hw_board_voice_names=[%s]\n", hw_board_voice_names);
	for (str = duplication; (token = strtok_r(str, " ,;:\t", &saveptr)) != NULL; str = NULL) {
		if ((size_t)voice_port_cfg->num_voice_ports >=
			sizeof(voice_port_cfg->voice_ports) / sizeof(voice_port_cfg->voice_ports)[0]) {
			ENDPT_ERR("%s: too many voice ports\n", __func__);
			res = -1;
			break;
		}

		if (strcasestr(token, "Tel") != NULL || strcasestr(token, "FXS") != NULL) {
			ENDPT_INFO("%s: port %d is FXS\n", __func__, voice_port_cfg->num_voice_ports);
			voice_port_cfg->voice_ports[voice_port_cfg->num_voice_ports++] = VOICE_LINE_FXS;
			voice_port_cfg->num_fxs++;
		} else if (strcasestr(token, "DECT") != NULL) {
			ENDPT_INFO("%s: port %d is DECT\n", __func__, voice_port_cfg->num_voice_ports);
			voice_port_cfg->voice_ports[voice_port_cfg->num_voice_ports++] = VOICE_LINE_DECT;
			voice_port_cfg->num_dect++;
		} else {
			ENDPT_ERR("%s: invalid port type, %s\n", __func__, token);
			res = -1;
			break;
		}
	}

	free(duplication);
	return res;
}

int main(int argc, char **argv) {
	(void) argc;
	struct uci_context *context = NULL;
	struct uci_package *package = NULL;
	struct terminal_info_t voice_port_cfg;
	int res, has_dect;
	char *error;
	const char *hw_board_has_dect;
	const char *hw_board_voice_names;

	openlog(argv[0], LOG_PID, LOG_DAEMON);

	// Load the UCI package
	context = uci_alloc_context();
	if (!context) {
		ENDPT_ERR("uci_alloc_context() failed\n");
		return EXIT_FAILURE;
	}
	uci_set_confdir(context, UCI_CONFIG_DIR);
	res = uci_load(context, uciStrConfig, &package);
	if (res != 0 || !package) {
		uci_get_errorstr(context, &error, "");
		ENDPT_ERR("Failed to load uci package %s, %s\n", uciStrConfig, error);
		free(error);
		uci_free_context(context);
		context = NULL;
		return EXIT_FAILURE;
	}

	config_syslog(context, package);
	ENDPT_INFO("syslog level is set to %d\n", log_level);
	setlogmask(LOG_UPTO(log_level));

	// Unbuffered stdout
	if(isatty(STDOUT_FILENO))
		setbuf(stdout, NULL);

	// Check that ASCII to DTMF convertion works.
	if(!isdigit('0') || !isalpha('a')) {
		ENDPT_ERR("Source has invalid encoding.\n");
		goto __error_ret;
	}

	/* Does the HW has DECT populated? This is stored in the
	 * "db", but that can't be queried via the normal uci API so we
	 * export it as an environment var in the startup script. */
	hw_board_has_dect = getenv("hw_board_hasDect");
	has_dect = hw_board_has_dect && *hw_board_has_dect == '1';

	hw_board_voice_names = getenv("hw_board_VoicePortNames");
	if (voicemngr_get_voice_port_cfg(hw_board_voice_names, &voice_port_cfg)) goto __error_ret;

	// Call this first. Find out how many phone lines we have
	if(voice_get_terminal_info(&voice_port_cfg, &terminal_info)) goto __error_ret;

	if(voice_register_cb_event_report(voicemngr_event_report)) goto __error_ret;
	if(voice_register_cb_egress_media(send_media_to_asterisk)) goto __error_ret;
	if(voice_line_preinit()) goto __error_ret;
	if(signals_init()) goto __error_ret;
	if((picoBase = pe_base_new()) < 0) goto __error_ret;
	fifos_init();
	streams_and_buses_init();
	if(ubus_init()) goto __error_ret;

	// Get configuration
	if(config_init(context, package)) goto __error_ret;
	uci_free_context(context);
	context = NULL;
	package = NULL;

	if(voice_line_init(has_dect)) goto __error_ret;
	if(line_dect_init()) goto __error_ret;
	if(voice_connection_init()) goto __error_ret;

	// Enable UBUS when all initializations have finished
	if(ubus_enable_receive()) goto __error_ret;

	ENDPT_INFO("voicemngr has started successfully\n");

	// Listen for events and dispatch them to handlers. Run forever and does not return.
	pe_base_dispatch(picoBase);

	ENDPT_WARN("pe_base_dispatch() exited unexpectedly\n");

__error_ret:
	if (package)
		uci_unload(context, package);
	if (context)
		uci_free_context(context);
	line_dect_deinit();
	voice_line_deinit();
	voice_connection_deinit();
	voice_engine_shutdown();
	ubus_disable_receive();
	ubus_close();
	return EXIT_FAILURE;
}