// Handles UBUS communication between Asterisk <-> dectmngr
// Takes care of translating Asterisk UBUS request to
// corresponding dectmngr requests.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <assert.h>

#include "libvoice.h"
#include "line-dect.h"
#include "line.h"
#include "ubus.h"

//-------------------------------------------------------------
static struct timespec ts_last_req_to_dectmngr = { 0, };
static int num_dectmngr_pending_reqs = 0;           // Number of of pending UBUS RPC calls both from and to dectmngr
static int current_handled_line;                    // Current Asterisk line request in investigation
static pe_list_t *ubus_req_list_to_dect = NULL;     // List of pending UBUS calls to dectmngr.
static pe_list_t *ubus_req_list_to_asterisk = NULL; // List of DECT UBUS calls waiting for Asterisk to answer.

//-------------------------------------------------------------
// Add a pending UBUS request destined for dectmngr to a wait queue.
int ubus_queue_req_to_dectmngr(struct line_req_t *req) {
	clock_gettime(CLOCK_MONOTONIC, &req->time_stamp);
	pe_list_add(ubus_req_list_to_dect, req);
	ENDPT_DBG("Added a new line_req to list for dectmngr and num_dectmngr_pending_reqs is %d\n",
				num_dectmngr_pending_reqs);
	return 0;
}

//-------------------------------------------------------------
// Add a pending UBUS request destined for Asterisk to a wait queue.
int ubus_queue_req_to_asterisk(struct line_req_t *req) {
	clock_gettime(CLOCK_MONOTONIC, &req->time_stamp);
	pe_list_add(ubus_req_list_to_asterisk, req);
	num_dectmngr_pending_reqs++;
	ENDPT_DBG("Added a new line_req to list for Asterisk and increased num_dectmngr_pending_reqs to %d\n",
				num_dectmngr_pending_reqs);
	return 0;
}

//-------------------------------------------------------------
// Callback function to find an old line_req_t
static int pe_list_find_old_req(void *arg) {
	struct line_req_t *req = (struct line_req_t*)arg;
	struct timespec ts_now = { 0, };

	clock_gettime(CLOCK_MONOTONIC, &ts_now);
	if ((ts_now.tv_sec - req->time_stamp.tv_sec) >= (UBUS_REQUEST_TIMEOUT + 1) && ts_now.tv_nsec >= req->time_stamp.tv_nsec)
		return 1;

	return 0;
}

//-------------------------------------------------------------
// Remove old line requests
static int rm_old_line_reqs() {
	while (num_dectmngr_pending_reqs > 0 && ubus_req_list_to_asterisk->count > 0) {
		struct line_req_t *req = pe_list_find(ubus_req_list_to_asterisk, pe_list_find_old_req);

		if (!req) {
			break;
		}
		pe_list_delete(ubus_req_list_to_asterisk, req);
		ENDPT_DBG("Remove one old line_req_t to Asterisk: line:%d, pcm_id:%d, connection_id:%d, action:%d, "
			"caller_id:%s, caller_name:%s\n",
			req->line, req->pcm_id, req->connection_id, req->action, req->caller_id, req->caller_name);
		free(req);
		num_dectmngr_pending_reqs--;
	}

	if (num_dectmngr_pending_reqs > 0 && ts_last_req_to_dectmngr.tv_sec > 0) {
		struct timespec ts_now = { 0, };

		clock_gettime(CLOCK_MONOTONIC, &ts_now);
		if ((ts_now.tv_sec - ts_last_req_to_dectmngr.tv_sec) >= (UBUS_REQUEST_TIMEOUT + 1) &&
			ts_now.tv_nsec >= ts_last_req_to_dectmngr.tv_nsec) {
			ENDPT_DBG("The last ubus request to dectmngr may have been timeout\n");
			num_dectmngr_pending_reqs--;
		}
	}

	return 0;
}

//-------------------------------------------------------------
// Pop oldest pending UBUS request from waiting queue if
// we have no outstanding communication with dectmngr.
int ubus_process_queued_reqs_to_dectmngr(void) {
	struct line_req_t *req;
	struct dectmngr_rpc_t dectmngr_rpc = { 0, };

	// Only one outstanding request at a time
	if (rm_old_line_reqs() || num_dectmngr_pending_reqs > 0) {
		ENDPT_DBG("Already %d pending request(s)\n", num_dectmngr_pending_reqs);
		return 0;
	}

	// Fetch oldest waiting request
	req = pe_list_get(ubus_req_list_to_dect);
	if (!req)
		return 0;

	ENDPT_DBG("Poped DECT req %p from list, action: %d pcmId: %d, caller_id: %s, callerName: %s\n",
			req, req->action, req->pcm_id, req->caller_id, req->caller_name);
	pcm_states_dump(__func__, req->line);

	switch (req->action) {
		case ACTION_CONN_CREATE:
		case ACTION_SIG_RING:
			if(ubus_call_dectmngr(voicemngr_line_to_dectmngr_line(req->line), 1, 0, req->caller_id, req->caller_name, req->pcm_id, req)) {
				free(req);
				return -1;
			}
			break;
		case ACTION_SIG_CW_CLOSE:
			if (get_callid_state(lines[req->line].pcm_callid[req->pcm_id]) == CALLID_ESTABLISHED){
				// do nothing if accept the call waiting
				free(req);
				return 0;
			}
			// ACTION_CONN_CLOSE: could not just skip due to compile warning is treated as error.
			if(ubus_call_dectmngr(voicemngr_line_to_dectmngr_line(req->line), 0, 1, req->caller_id, req->caller_name, req->pcm_id, req)) {
				free(req);
				return -1;
			}
			break;
		case ACTION_RINGING_STOP:
			dectmngr_rpc.action = req->action;
			dectmngr_rpc.extension_id = voicemngr_line_to_dectmngr_line(req->line);
			if(ubus_dectmngr_rpc(&dectmngr_rpc, req)) {
				free(req);
				return -1;
			}
			break;
		case ACTION_CONN_CLOSE:
			dectmngr_rpc.action = req->action;
			dectmngr_rpc.extension_id = voicemngr_line_to_dectmngr_line(req->line);
			dectmngr_rpc.pcm_id = req->pcm_id;
			if(ubus_dectmngr_rpc(&dectmngr_rpc, req)) {
				free(req);
				return -1;
			}
			break;
		case ACTION_SIG_BUSY_TONE:
			if(ubus_call_dectmngr(voicemngr_line_to_dectmngr_line(req->line), 0, 2, req->caller_id, req->caller_name, req->pcm_id, req)) {
				free(req);
				return -1;
			}
			break;
		case ACTION_SIG_ANSWERED:
			if(ubus_call_dectmngr(voicemngr_line_to_dectmngr_line(req->line), 2, 0, req->caller_id, req->caller_name, req->pcm_id, req)) {
				free(req);
				return -1;
			}
			break;
		default:
			ENDPT_DBG("Invalid action: %d\n", req->action);
			return -1;
	}

	clock_gettime(CLOCK_MONOTONIC, &ts_last_req_to_dectmngr);
	num_dectmngr_pending_reqs++;
	ENDPT_DBG("Increased num_dectmngr_pending_reqs to %d\n", num_dectmngr_pending_reqs);
	return 0;
}

//-----------------------------------------------------------------
// If appropriate, simulate keypad digit pressesing. Returned value
// -1  Something gets wrong
//  0  Does nothing
//  1  Keypad events have been handled
int simulate_digits_pressing(int line, const char *pressed_digits) {
	char *digits;
	
	if(lines[line].type != VOICE_LINE_DECT)
		return 0;

	ENDPT_DBG("line: %d pressed_digits: %s\n", line, (pressed_digits ? pressed_digits : ""));

	// Store digits in a list.
	if(pressed_digits && lines[line].pending_digits) {
		digits = strndup(pressed_digits, MAX_KEYPAD_DIGITS);
		if(digits) {
			ENDPT_DBG("Queueing line %d digits %s\n", line, pressed_digits);
			pe_list_add(lines[line].pending_digits, digits);
		}
	}

	if(!voice_line_is_offhook(line))
		return 0;

	// Pop digits from list.
	digits = pe_list_get(lines[line].pending_digits);
	if(digits) {
		int res;
		ENDPT_DBG("Dequeued line %d digits %s\n", line, digits);
		res = line_signal(line, "keypad", digits, NULL);
		free(digits);
		return (res == 0 ? 1 : -1);
	}

	return 0;
}

//-------------------------------------------------------------
// dectmngr has answered an UBUS RPC "call" request. Did it
// accept the line action request? Then we call libvoice to
// start/stop the audio stream and inform Asterisk to proceed
// with phone call setup.
int ubus_cb_dectmngr_replied(struct line_req_t *req, enum ubus_msg_status reply_status) {
	if(!req)
		return -1;

	ENDPT_DBG("got answer req %p from dectmngr, action: %d, line: %d, pcmId: %d, conId: %d\n",
		req, req->action, req->line, req->pcm_id, req->connection_id);
	pcm_states_dump(__func__, req->line);

	switch (req->action) {
		case ACTION_CONN_CREATE:
			if(reply_status == UBUS_STATUS_OK && voice_connection_create(req->line, req->line)) {
				reply_status = UBUS_STATUS_UNKNOWN_ERROR;
			}
			break;
		case ACTION_CONN_CLOSE:
		case ACTION_RINGING_STOP:
			if (req->pcm_id == PCM_0 || req->pcm_id == PCM_1) {
				lines[req->line].pcm_callid[req->pcm_id] = CALLID_INVALID;
			} else if (req->pcm_id == -1) {
				// Close all calls
				lines[req->line].pcm_callid[PCM_0] = CALLID_INVALID;
				lines[req->line].pcm_callid[PCM_1] = CALLID_INVALID;
			}
			if (get_callid_state(lines[req->line].pcm_callid[PCM_0]) == CALLID_ESTABLISHED ||
				get_callid_state(lines[req->line].pcm_callid[PCM_1]) == CALLID_ESTABLISHED)
				break;
			reply_status = voice_connection_close(req->line, req->line) ? // Close regardless of any dectmngr error.
				UBUS_STATUS_UNKNOWN_ERROR : UBUS_STATUS_OK;
			break;
		case ACTION_SIG_RING:
			if ((req->pcm_id == PCM_0 && get_callid_state(lines[req->line].pcm_callid[PCM_1]) == CALLID_OBTAINING) ||
				(req->pcm_id == PCM_1 && get_callid_state(lines[req->line].pcm_callid[PCM_0]) == CALLID_OBTAINING))
				break;
			if (req->pcm_id == PCM_0 || req->pcm_id == PCM_1)
				lines[req->line].pcm_callid[req->pcm_id] = CALLID_OBTAINING;
			break;
		default:
			break;
	}

	send_reply_asterisk(&req->ubus, reply_status);
	free(req);
	if (num_dectmngr_pending_reqs > 0) {
		num_dectmngr_pending_reqs--;
		ENDPT_DBG("Decreased num_dectmngr_pending_reqs to %d\n", num_dectmngr_pending_reqs);
	}

	return 0;
}

//-------------------------------------------------------------
// libpicoevent let us iterate though a list with dect ubus
// requests waiting for an answer. In this list find the
// UBUS request object of interest and ignore the rest.
static int pe_list_cb_find_req(void *arg) {
	struct line_req_t *req = (struct line_req_t*) arg;
	return (req->line == current_handled_line);
}

//-------------------------------------------------------------
// Asterisk has answered an UBUS RPC "call" request initiated
// by dectmngr. Now relay the Asterisk reply back to dectmngr.
void ubus_cb_asterisk_replied(struct line_event_t *lineEv, enum ubus_msg_status reply_status) {
	struct line_req_t *req;
	int epEvntIdx, dectEvntIdx;

	if(!lineEv || !voice_line_is_ready(lineEv->line)) {
		ENDPT_ERR("Invalid argument\n");
		return;
	}

	if(lines[lineEv->line].simulated_hook) {
		voice_hook_simulation_maintain(lineEv->line);
	}

	// Find map index to the line event that was sent to Asterisk
	for(epEvntIdx = 0; event_map[epEvntIdx].event != VOICE_EVT_END &&
		strcasecmp(lineEv->name, event_map[epEvntIdx].name); epEvntIdx++);
	// Check also DECT event map
	for(dectEvntIdx = 0; dect_event_map[dectEvntIdx].event != DECT_EVT_END &&
		strcasecmp(lineEv->name, dect_event_map[dectEvntIdx].name); dectEvntIdx++);
	if(event_map[epEvntIdx].event == VOICE_EVT_END && dect_event_map[dectEvntIdx].event == DECT_EVT_END)
		return;

	// Find the current line request matching the line event.
	assert(current_handled_line == -1);
	current_handled_line = lineEv->line;
	req = pe_list_find(ubus_req_list_to_asterisk, pe_list_cb_find_req);
	current_handled_line = -1;
	if (!req)
		return; // None found, then do nothing

	assert(req->ubus.ctx);
	ENDPT_DBG("Poped Asterisk request %p line %d event %s from list, action %d\n",
		req, lineEv->line, lineEv->name, req->action);

	if(reply_status == UBUS_STATUS_OK) {
		switch (req->action) {
			case ACTION_CONN_CREATE:
				switch(event_map[epEvntIdx].event) {
					case VOICE_EVT_DTMF0:
					case VOICE_EVT_DTMF1:
					case VOICE_EVT_DTMF2:
					case VOICE_EVT_DTMF3:
					case VOICE_EVT_DTMF4:
					case VOICE_EVT_DTMF5:
					case VOICE_EVT_DTMF6:
					case VOICE_EVT_DTMF7:
					case VOICE_EVT_DTMF8:
					case VOICE_EVT_DTMF9:
					case VOICE_EVT_DTMFS:
					case VOICE_EVT_DTMFH:
					case VOICE_EVT_DTMFA:
					case VOICE_EVT_DTMFB:
					case VOICE_EVT_DTMFC:
					case VOICE_EVT_DTMFD:
					case VOICE_EVT_OFFHOOK:
					case VOICE_EVT_FLASH:
						/* By now Asterisk knows the terminal is off-hook. If we have any queued phone number digits
						 * they are sent now. */
						if (simulate_digits_pressing(lineEv->line, NULL) != 0)
							return;
						break;

					default: // Ignore other events
						if (dect_event_map[dectEvntIdx].event == DECT_EVT_END)
							return;
						break;
				}
				break;

			case ACTION_CONN_CLOSE:
				if(event_map[epEvntIdx].event != VOICE_EVT_ONHOOK && dect_event_map[dectEvntIdx].event != DECT_EVT_RELEASE)
					return;
				break;

			default:
				break;
		}
	}

	send_reply_dectmngr(&req->ubus, voicemngr_line_to_dectmngr_line(lineEv->line), lineEv->line, reply_status);
	pe_list_delete(ubus_req_list_to_asterisk, req);
	free(req);
	if (num_dectmngr_pending_reqs > 0) {
		num_dectmngr_pending_reqs--;
		ENDPT_DBG("Decreased num_dectmngr_pending_reqs to %d\n", num_dectmngr_pending_reqs);
	}

	return;
}

//-------------------------------------------------------------
// Translate dectmngr line to voicemngr line
int dectmngr_line_to_voicemngr_line(int line) {
	return line - 1;
}

//-------------------------------------------------------------
// Translate voicemngr line to dectmngr line
int voicemngr_line_to_dectmngr_line(int line) {
	return line + 1;
}

//-------------------------------------------------------------
int line_dect_init(void) {
	ubus_req_list_to_dect = pe_list_new();
	ubus_req_list_to_asterisk = pe_list_new();
	current_handled_line = -1;

	return 0;
}

//-------------------------------------------------------------
// Free resources and cleanup
int line_dect_deinit(void) {
	struct line_req_t *req;
	pe_list_t *reqLists[2];
	int i;

	reqLists[0] = ubus_req_list_to_dect;
	reqLists[1] = ubus_req_list_to_asterisk;

	for(i = 0; i < 2; i++) {
		if(!reqLists[i])
			continue;

		do {
			req = pe_list_get(reqLists[i]);
			if(req) {
				send_reply_asterisk(&req->ubus, UBUS_STATUS_CONNECTION_FAILED);
				free(req);
			}
		} while(req);

		free(reqLists[i]);
	}

	num_dectmngr_pending_reqs = 0;

	return 0;
}