#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <libubus.h>
#include <sched.h>         // sched_yield()
#include <assert.h>
#include <alloca.h>

#include <libpicoevent.h>  // for err_exit()
#include "libvoice.h"
#include "line.h"
#include "line-dect.h"
#include "main.h"
#include "ubus.h"

static char *pcmState2Str(callid_state state)
{
	if (state <= CALLID_INVALID)
		return "Invalid";
	else if (state == CALLID_OBTAINING)
		return "Obtaining";
	else
		return "Established";
}

void pcm_states_dump(const char *func, int line)
{
	ENDPT_DBG("%s: line=%d, pcm_callid[%d]=\'%s\', pcm_callid[%d]=\'%s\'\n", func, line,
		PCM_0, pcmState2Str(lines[line].pcm_callid[PCM_0]),
		PCM_1, pcmState2Str(lines[line].pcm_callid[PCM_1]));
}

// Get call_id state (-1, 0, 1)
callid_state get_callid_state(int call_id) {
	if (call_id <= CALLID_INVALID)
		return CALLID_INVALID;
	else if (call_id == CALLID_OBTAINING)
		return CALLID_OBTAINING;
	else
		return CALLID_ESTABLISHED;
}

static int send_dect_event_to_asterisk(int line, struct dect_event_t dectEvnt)
{
	struct line_event_t *msg;

	msg = malloc(sizeof(struct line_event_t));
	msg->name = dectEvnt.name;
	msg->data = 0;
	msg->line = line;

	ubus_call_asterisk(msg);
	ubus_broadcast_event(msg);

	return 0;
}

//-------------------------------------------------------------
// If a line has been disabled in run time we simulate
// it as always being busy by going off-hook forever. This
// is used by DECT when the HW is unpopulated.
static int perhaps_simulate_busy(int line, struct voice_ubus_req_t *ubus_req)
{
	const struct voice_event_t *ev;
	struct line_event_t *msg;

	if(!lines[line].simulated_busy)
		return 0;

	// Inform Asterisk only once per UBUS session or we get a loop
	assert(ubus_req && ubus_req->reqIn);
	if(lines[line].simulated_busy_peer_id == ubus_req->reqIn->peer) {
		return 1;
	}
	lines[line].simulated_busy_peer_id = ubus_req->reqIn->peer;

	for(ev = event_map; ev->event != VOICE_EVT_OFFHOOK; ev++)
		continue;
	msg = malloc(sizeof(struct line_event_t));
	if (msg) {
		msg->name = ev->name;
		msg->data = 0;
		msg->line = line;
		send_event_main(msg, EVENT_MAIN_LINE);
	} else {
		ENDPT_ERR("%s: out of memory\n", __func__);
	}

	return 1;
}
//----------------------------------------------------------------------------------
// DECT handset tone trigger
static int dect_tone_play(int line, int pcm, enum VOICE_SIGNAL signal, const char *data, struct voice_ubus_req_t *ubus_req)
{
	struct line_req_t *line_req = NULL;
	ENDPT_DBG("line=%d, pcm=%d, data=%s\n", line, pcm, data);
	pcm_states_dump(__func__, line);

	assert(ubus_req);
	line_req = calloc(1, sizeof(struct line_req_t));
	if(!line_req) {
		ENDPT_ERR("%s: out of memory\n", __func__);
		return -1;
	}
	line_req->line = line;
	line_req->connection_id = -1;
	line_req->pcm_id = pcm;
	switch(signal) {
		case VOICE_SIG_UNOBTAINABLE:
		case VOICE_SIG_BUSY:
		case VOICE_SIG_NETBUSY:
			line_req->action = ACTION_SIG_BUSY_TONE; //only busy tone available
			break;
		default:
			free(line_req);
			return -1;
	}

	ENDPT_DBG("line %d set pcm to %d\n", line, line_req->pcm_id);
	memcpy(&line_req->ubus, ubus_req, sizeof(struct voice_ubus_req_t));

	assert(lines[line].type == VOICE_LINE_DECT);
	assert(line_req);
	if(ubus_queue_req_to_dectmngr(line_req) || ubus_process_queued_reqs_to_dectmngr())
		return -1;
	ubus_defer_request(line_req->ubus.ctx, line_req->ubus.reqIn, &line_req->ubus.reqOut);
	line_req->ubus.reqIn = NULL;

	return 0;
}

static void setCLIDinfo(const char *data, struct line_req_t *line_req)
{
	if (!data || !line_req) {
		return;
	}

	size_t data_len = strlen(data);
	char cid_tmp[data_len + 1];

	strcpy(cid_tmp, data);
	char* ptr = cid_tmp;

	// Initialize struct fields to empty strings
	line_req->caller_id[0] = '\0';
	line_req->caller_name[0] = '\0';

	char *token = strtok_r(cid_tmp, ", ", &ptr);  // Skip the first field (date)
	if (!token) return;

	token = strtok_r(NULL, ", ", &ptr);  // Extract Caller ID
	if (token) {
		strncpy(line_req->caller_id, token, sizeof(line_req->caller_id) - 1);
		line_req->caller_id[sizeof(line_req->caller_id) - 1] = '\0';
	}

	token = strtok_r(NULL, ",", &ptr);  // Extract Caller Name (quoted, may be empty)
	if (token) {
		// Check if it's enclosed in quotes
		if (token[0] == '"') {
			token++; // Move past opening quote
			char *end_quote = strchr(token, '"');
			if (end_quote) {
				*end_quote = '\0'; // Terminate at closing quote
			}
		}
		if (strlen(token) > 0) { // Only copy if non-empty
			strncpy(line_req->caller_name, token, sizeof(line_req->caller_name) - 1);
			line_req->caller_name[sizeof(line_req->caller_name) - 1] = '\0';
		}
	}

	token = strtok_r(NULL, ", ", &ptr);  // Extract Line Number
	if (token) {
		line_req->sip_client_id = atoi(token);
	}
}

//----------------------------------------------------------------------------------
// Asterisk wants to make a FXS phone or DECT handset ring probably with a caller ID
static int line_signal_ring(int line, int pcm, enum VOICE_SIGNAL signal, const char *data, struct voice_ubus_req_t *ubus_req)
{
	struct line_req_t *line_req = NULL;
	int start_ring = data && atoi(data) != 0;

	ENDPT_DBG("line=%d, pcm=%d, data=%s\n", line, pcm, data);
	pcm_states_dump(__func__, line);

	// Relay the request to dectmngr if the line is DECT
	if(lines[line].type == VOICE_LINE_DECT) {
		assert(ubus_req);
		line_req = calloc(1, sizeof(struct line_req_t));
		if(!line_req) {
			ENDPT_ERR("%s: out of memory\n", __func__);
			return -1;
		}
		line_req->line = line;
		line_req->connection_id = -1;
		line_req->pcm_id = pcm;
		ENDPT_DBG("line %d set pcm to %d\n", line, line_req->pcm_id);
		memcpy(&line_req->ubus, ubus_req, sizeof(struct voice_ubus_req_t));

		switch(signal) {
			case VOICE_SIG_CALLID:
				// Parse the called ID string generated by Asterisk
				if (start_ring && data && strlen(data) >= MIN_CALLER_ID_LEN && strlen(data) <= MAX_CALLER_ID_LEN &&
				    data[CLID_TIME_DELIM] == ',' && data[CLID_NUMB_REC + 1] != ',') {
					setCLIDinfo(data, line_req);
				}
				break;

			case VOICE_SIG_RINGING:
			case VOICE_SIG_CALLID_RINGING:
				if (!start_ring && data) {
					ENDPT_DBG("DECT line=%d: stop ring. data=%s\n", line, data);

					if (!strcmp(data, "call_missed"))
						line_req->action = ACTION_CALL_MISSED;
					else
						line_req->action = ACTION_RINGING_STOP;
				}
				break;

			default:
				break;
		}
	}

	// What type of ring signal?
	switch(signal) {
		case VOICE_SIG_CALLID:
			if(line_req) {
				line_req->action = start_ring ? ACTION_SIG_RING : ACTION_RINGING_STOP;
			} else {
				return voice_line_signal(line, -1, signal, start_ring, (void *)data);
			}
			break;

		case VOICE_SIG_RINGING:
		case VOICE_SIG_CALLID_RINGING:
			if(line_req) {
				if(start_ring) {
					if(signal == VOICE_SIG_CALLID_RINGING) {
						// Dect ignore this enable signal
						free(line_req);
						return 0;
					}
					line_req->action = ACTION_SIG_RING;
					line_req->caller_id[0] = 0; // Discard enable/disable char or it will become the caller ID
				}
			} else {
				return voice_line_signal(line, -1, signal, start_ring, NULL);
			}
			break;
		case VOICE_SIG_CALL_WAITING: // only used for stopping call waiting indication
			if(line_req) {
				if(!start_ring) {
					line_req->action = ACTION_RINGING_STOP;
				}
				line_req->caller_id[0] = 0; // Discard enable/disable char or it will become the caller ID
				break;
			} else {
				return -1;
			}

		default:
			free(line_req);
			return -1;
	}

	// If getting here the request is aimed for DECT
	assert(lines[line].type == VOICE_LINE_DECT);
	assert(line_req);
	if(ubus_queue_req_to_dectmngr(line_req) || ubus_process_queued_reqs_to_dectmngr())
		return -1;
	ubus_defer_request(line_req->ubus.ctx, line_req->ubus.reqIn, &line_req->ubus.reqOut);
	line_req->ubus.reqIn = NULL;

	return 0;
}

//-------------------------------------------------------------
// Reception of a answer connection request from Asterisk. If
// line type is Dect we need to relay the requets to the Dectmngr.
static int line_signal_answer(int line, int pcm, const char *data, struct voice_ubus_req_t *ubus_req)
{
	struct line_req_t *line_req = NULL;

	ENDPT_DBG("line %d pcm: %d data: %s\n", line, pcm, data);
	pcm_states_dump(__func__, line);

	assert(ubus_req);
	line_req = calloc(1, sizeof(struct line_req_t));
	if(!line_req) return -1;
	line_req->line = line;
	line_req->connection_id = -1;
	line_req->pcm_id = pcm;
	line_req->action = ACTION_SIG_ANSWERED;
	memcpy(&line_req->ubus, ubus_req, sizeof(struct voice_ubus_req_t));

	assert(line_req);

	if(ubus_queue_req_to_dectmngr(line_req) || ubus_process_queued_reqs_to_dectmngr())
		return -1;
	ubus_defer_request(line_req->ubus.ctx, line_req->ubus.reqIn, &line_req->ubus.reqOut);
	line_req->ubus.reqIn = NULL;

	return 0;
}

//-------------------------------------------------------------
// Generate a signal to the phone, such as DTMF tones or ringing
int line_signal(int line, const char *signame, const char *data, struct voice_ubus_req_t *ubus_req)
{
	const struct voice_signal_t *sig;
	int res = 0;

	ENDPT_DBG("line=%d(type:%d), signame=%s, data=%s\n", line, lines[line].type, signame, data);

	for(sig = signal_map; sig->signal != VOICE_SIG_LAST && strcmp(signame, sig->name) != 0; sig++)
		continue;
	if(sig->signal == VOICE_SIG_LAST) {
		ENDPT_ERR("%s: signal %s is not supported\n", __func__, signame);
		return -1;
	}

	// Is line disabled?
	if(perhaps_simulate_busy(line, ubus_req))
		return 0;

	switch(sig->signal) {
		case VOICE_SIG_CALLID_RINGING:
		case VOICE_SIG_CALLID:
		case VOICE_SIG_RINGING:
			if(atoi(data) == 0) {
				res = line_signal_ring(line, get_callid_state(lines[line].pcm_callid[PCM_0]) == CALLID_OBTAINING ?
						PCM_0 : PCM_1, sig->signal, data, ubus_req);
				lines[line].signaled_call_waiting = 0;
			} else {
				res = line_signal_ring(line, get_callid_state(lines[line].pcm_callid[PCM_0]) == CALLID_INVALID ?
						PCM_0 : PCM_1, sig->signal, data, ubus_req);
			}
			break;

		case VOICE_SIG_INGRESS_DTMF: // Simulate phone keypad button presses
			res = voice_line_signal(line, -1, sig->signal, 1, (void *)data);
			break;

		case VOICE_SIG_CALL_WAITING:
			if (lines[line].type == VOICE_LINE_DECT) {
				ENDPT_DBG("lines[%d].signaled_call_waiting: %d\n", line, lines[line].signaled_call_waiting);
				if(data && strlen(data) >= MIN_CALLER_ID_LEN && strlen(data) <= MAX_CALLER_ID_LEN &&
					data[CLID_TIME_DELIM] == ',' && data[CLID_NUMB_REC + 1] != ',' &&
					lines[line].signaled_call_waiting == 0) {
					pcm_states_dump(__func__, line);
					// start call waiting
					res = line_signal_ring(line, get_callid_state(lines[line].pcm_callid[PCM_1]) == CALLID_INVALID ?
							PCM_1 : PCM_0, VOICE_SIG_CALLID, data, ubus_req);
					lines[line].signaled_call_waiting = 1;
				} else if(data && (data[0] == '0')) {
					// stop call waiting when accepted but not close the connection on the accepted line
					res = line_signal_ring(line, get_callid_state(lines[line].pcm_callid[PCM_1]) == CALLID_OBTAINING ?
							PCM_1 : PCM_0, VOICE_SIG_CALL_WAITING, data, ubus_req);
					lines[line].signaled_call_waiting = 0;
				}
			} else {
				res = voice_line_signal(line, -1, sig->signal, atoi(data), NULL);
			}
			break;

		case VOICE_SIG_UNOBTAINABLE:
			if (lines[line].type == VOICE_LINE_DECT) {
				pcm_states_dump(__func__, line);
				send_dect_event_to_asterisk(line, dect_event_map[DECT_EVT_RELEASE]);
			} else {
				res = voice_line_signal(line, -1, sig->signal, atoi(data), NULL);
			}
			break;
		case VOICE_SIG_BUSY:
		case VOICE_SIG_NETBUSY:
			if (lines[line].type == VOICE_LINE_DECT) {
				pcm_states_dump(__func__, line);
				if (get_callid_state(lines[line].pcm_callid[PCM_0])==CALLID_ESTABLISHED && get_callid_state(lines[line].pcm_callid[PCM_1])==CALLID_ESTABLISHED){
					send_dect_event_to_asterisk(line, dect_event_map[DECT_EVT_RELEASE]);
				} else if (get_callid_state(lines[line].pcm_callid[PCM_0]) == CALLID_ESTABLISHED || get_callid_state(lines[line].pcm_callid[PCM_1]) == CALLID_ESTABLISHED){
				// play tone on DECT
					res = dect_tone_play(line, get_callid_state(lines[line].pcm_callid[PCM_0]) == CALLID_ESTABLISHED ? PCM_0 : PCM_1, sig->signal, data, ubus_req);
				}
			} else {
				res = voice_line_signal(line, -1, sig->signal, atoi(data), NULL);
			}
			break;

		case VOICE_SIG_ANSWER:
			if ((lines[line].type == VOICE_LINE_DECT) && (data[0] == '1')) {
				res = line_signal_answer(line, get_callid_state(lines[line].pcm_callid[PCM_1]) >= CALLID_OBTAINING ?
						PCM_1 : PCM_0, data, ubus_req);
			} else {
				res = 0;
			}
			break;

		case VOICE_SIG_K_BREAK:
			if (lines[line].type == VOICE_LINE_FXS) {
				res = voice_line_signal(line, -1, sig->signal, atoi(data), NULL);
			}
			break;

		default:
			res = voice_line_signal(line, -1, sig->signal, atoi(data), NULL);
			break;
	}

	if (res != 0) {
		ENDPT_ERR("%s: failed to generate signal %s(%s) on line %d\n", __func__, sig->name, data, line);
	}

	return res;
}

//---------------------------------------------------------------------------------------------------
// Reception of a create connection request from Asterisk. If line type is DECT, we need to relay the
// request to the dectmngr.
int line_new_connection_by_asterisk(int line, int connection, struct voice_ubus_req_t *ubus_req)
{
	struct line_req_t *line_req;
	int res = 0;

	if (!voice_line_is_ready(line))
		return -1;

	ENDPT_DBG("line: %d, connection: %d\n", line, connection);
	pcm_states_dump(__func__, line);

	if (get_callid_state(lines[line].pcm_callid[PCM_0]) == CALLID_OBTAINING)
		lines[line].pcm_callid[PCM_0] = connection;
	else if (get_callid_state(lines[line].pcm_callid[PCM_1]) == CALLID_OBTAINING)
		lines[line].pcm_callid[PCM_1] = connection;

	if (lines[line].type == VOICE_LINE_DECT) {
		if (voice_line_is_offhook(line) || voice_line_get_connection_count(line) > 0) {
			/* dectmngr already has audio streaming, now libvoice and Asterisk should do the same. */
			res = voice_connection_create(line, line);
		} else {
			/* Relay request to dectmngr by putting the request in a queue, where it will wait until we get a
			 * chance to relay it to dectmngr. */
			line_req = calloc(1, sizeof(struct line_req_t));
			if(!line_req) {
				ENDPT_ERR("%s: out of memory\n", __func__);
				return -1;
			}
			line_req->line = line;
			line_req->connection_id = line;
			line_req->pcm_id = lines[line].pcm_callid[PCM_0] == connection ? PCM_0 : PCM_1;
			line_req->action = ACTION_CONN_CREATE;
			memcpy(&line_req->ubus, ubus_req, sizeof(struct voice_ubus_req_t));
			if (ubus_queue_req_to_dectmngr(line_req) || ubus_process_queued_reqs_to_dectmngr())
				return -1;
			ubus_defer_request(line_req->ubus.ctx, line_req->ubus.reqIn, &line_req->ubus.reqOut);
			line_req->ubus.reqIn = NULL; // Deny further use of input request
		}
	} else {
		res = voice_connection_create(line, line);
	}

	return res;
}

//---------------------------------------------------------------------------------------------------
// Reception of a connection parameter update request from Asterisk.
int line_connection_parm_update_by_asterisk(int line, int connection, struct config_update_struct *data) {
	int res = 0;

	if (!voice_line_is_ready(line))
		return -1;
	ENDPT_DBG("parm_update request for line: %d, connection: %d, mask: %d, codec: %d, ptime: %d, dtmf_mode: %d \n", line, connection, data->mask, data->codec, data->ptime, data->dtmf_mode);
	pcm_states_dump(__func__, line);

	res = voice_connection_parm_update(line, line, data);

	return res;
}

//------------------------------------------------------------------------------------------------------------
// Reception of a close connection request from Asterisk. If line type is DECT we need to relay the request to
// dectmngr.
int line_close_connection_by_asterisk(int line, int connection, struct voice_ubus_req_t *ubus_req)
{
	struct line_req_t *line_req;

	if (!voice_line_is_ready(line))
		return -1;

	ENDPT_DBG("line: %d, connection: %d\n", line, connection);
	pcm_states_dump(__func__, line);

	if (get_callid_state(lines[line].pcm_callid[PCM_0]) == CALLID_INVALID &&
		get_callid_state(lines[line].pcm_callid[PCM_1]) == CALLID_INVALID) {
		return voice_connection_close(line, line);
	}

	switch(lines[line].type) {
		case VOICE_LINE_DECT:
			/* Put the request in a queue, where it will wait
			 * until we get a chanse to relay it to dectmngr. */
			line_req = calloc(1, sizeof(struct line_req_t));
			if(!line_req) return -1;
			line_req->line = line;
			line_req->connection_id = line;
			if (get_callid_state(lines[line].pcm_callid[PCM_0]) == CALLID_ESTABLISHED &&
				get_callid_state(lines[line].pcm_callid[PCM_1]) == CALLID_INVALID) {
				line_req->pcm_id = PCM_0;
				lines[line].pcm_callid[PCM_0] = CALLID_INVALID;
			} else if (get_callid_state(lines[line].pcm_callid[PCM_1]) == CALLID_ESTABLISHED &&
				get_callid_state(lines[line].pcm_callid[PCM_0]) == CALLID_INVALID) {
				line_req->pcm_id = PCM_1;
				lines[line].pcm_callid[PCM_1] = CALLID_INVALID;
			}
			line_req->action = ACTION_CONN_CLOSE;
			if (connection == -1) {
				ENDPT_DBG("Set line_req->pcm_id to -1 for releasing all calls on the extension %d\n", line);
				line_req->pcm_id = connection;
			}
			memcpy(&line_req->ubus, ubus_req, sizeof(struct voice_ubus_req_t));
			if(ubus_queue_req_to_dectmngr(line_req) || ubus_process_queued_reqs_to_dectmngr())
				return -1;
			ubus_defer_request(line_req->ubus.ctx, line_req->ubus.reqIn, &line_req->ubus.reqOut);
			line_req->ubus.reqIn = NULL;	// Deny further use of input request
			break;

		default:
			if (get_callid_state(lines[line].pcm_callid[PCM_0]) == CALLID_ESTABLISHED)
				lines[line].pcm_callid[PCM_0] = CALLID_INVALID;
			else if (get_callid_state(lines[line].pcm_callid[PCM_1]) == CALLID_ESTABLISHED)
				lines[line].pcm_callid[PCM_1] = CALLID_INVALID;

			return voice_connection_close(line, line);
	}

	return 0;
}

int line_release_connection_by_asterisk(int line, int connection, struct voice_ubus_req_t *ubus_req)
{
	struct line_req_t *line_req;

	if (!voice_line_is_ready(line))
		return -1;

	ENDPT_DBG("line: %d connection: %d\n", line, connection);
	pcm_states_dump(__func__, line);

	// during start of call conference asterisk removes 1 of connection, we skip
	// it because voicemngr<->dectmngr needs to keep it active
	if (lines[line].conference_started == 1) {
		lines[line].conference_started = 0;
		return 0;
	}

	if (get_callid_state(lines[line].pcm_callid[PCM_0]) < CALLID_ESTABLISHED &&
		get_callid_state(lines[line].pcm_callid[PCM_1]) < CALLID_ESTABLISHED) {
		return 0;
	}

	if (lines[line].pcm_callid[PCM_0] != connection && lines[line].pcm_callid[PCM_1] != connection) {
		return 0;
	}

	switch(lines[line].type) {
		case VOICE_LINE_DECT:
			/* Put the request in a queue, where it will wait
			 * until we get a chanse to relay it to dectmngr. */
			line_req = calloc(1, sizeof(struct line_req_t));
			if(!line_req) return -1;
			line_req->line = line;
			line_req->connection_id = line;
			if (lines[line].pcm_callid[PCM_0] == connection) {
				line_req->pcm_id = PCM_0;
				lines[line].pcm_callid[PCM_0] = CALLID_INVALID;
			} else if (lines[line].pcm_callid[PCM_1] == connection) {
				line_req->pcm_id = PCM_1;
				lines[line].pcm_callid[PCM_1] = CALLID_INVALID;
			}
			line_req->action = ACTION_CONN_CLOSE;
			memcpy(&line_req->ubus, ubus_req, sizeof(struct voice_ubus_req_t));
			if(ubus_queue_req_to_dectmngr(line_req) || ubus_process_queued_reqs_to_dectmngr())
				return -1;
			ubus_defer_request(line_req->ubus.ctx, line_req->ubus.reqIn, &line_req->ubus.reqOut);
			line_req->ubus.reqIn = NULL; // Deny further use of input request
			break;

		default:
			if (lines[line].pcm_callid[PCM_0] == connection)
				lines[line].pcm_callid[PCM_0] = CALLID_INVALID;
			else if (lines[line].pcm_callid[PCM_1] == connection)
				lines[line].pcm_callid[PCM_1] = CALLID_INVALID;

			return voice_connection_close(line, line);
	}

	return 0;
}

int line_update_connection_by_pbx(int line, int pcm_callid)
{
	if (!voice_line_is_ready(line))
		return -1;

	ENDPT_DBG("Received update connection for line: %d with pcm_callid: %d\n", line, pcm_callid);
	pcm_states_dump(__func__, line);

	if(lines[line].type != VOICE_LINE_DECT)
		return 0;

	if (get_callid_state(lines[line].pcm_callid[PCM_0]) == CALLID_OBTAINING)
		lines[line].pcm_callid[PCM_0] = pcm_callid;
	else if (get_callid_state(lines[line].pcm_callid[PCM_1]) == CALLID_OBTAINING)
		lines[line].pcm_callid[PCM_1] = pcm_callid;

	return 0;
}

//-------------------------------------------------------------
// Reception of a create connection request from dectmngr.
// Generate a off-hook event and queue the request
// until Asterisk acknowledge the offh-ook event.
int line_new_connection_by_dect(int line, const char *cid, int pcm, struct voice_ubus_req_t *ubus_req)
{
	struct line_req_t *line_req;

	if (!voice_line_is_ready(line) || lines[line].type != VOICE_LINE_DECT)
		return -1;

	ENDPT_DBG("line=%d, pcm=%d, cid=%s\n", line, pcm, cid);
	pcm_states_dump(__func__, line);

	if (pcm == PCM_0 || pcm == PCM_1) {
		lines[line].pcm_callid[pcm] = CALLID_OBTAINING;
		lines[line].signaled_call_waiting = 0;
	}

	if (!voice_line_is_offhook(line)) {
		if (voice_line_simulate_hook(line, VOICE_EVT_OFFHOOK))
			return -1;
	} else {
		switch(pcm) {
			case CALL_TOGGLE:
				if (send_dect_event_to_asterisk(line, dect_event_map[DECT_EVT_SWITCH]))
					return -1;
				break;
			case CALL_CONFERENCE:
				if (send_dect_event_to_asterisk(line, dect_event_map[DECT_EVT_JOIN]))
					return -1;
				lines[line].conference_started = 1;
				break;
			default:
				break;
		}
	}

	if (simulate_digits_pressing(line, cid) == -1)
		return -1;

	/* Store the ubus request in a queue. It will be picked up
	 * later, via the Asterisk ubus answer handler. */
	line_req = calloc(1, sizeof(struct line_req_t));
	if (!line_req) return -1;
	line_req->line = line;
	line_req->connection_id = -1;
	line_req->action = ACTION_CONN_CREATE;
	memcpy(&line_req->ubus, ubus_req, sizeof(struct voice_ubus_req_t));
	if (ubus_queue_req_to_asterisk(line_req)) {
		free(line_req);
		return -1;
	}
	ubus_defer_request(line_req->ubus.ctx, line_req->ubus.reqIn, &line_req->ubus.reqOut);
	line_req->ubus.reqIn = NULL;	// Deny further use of input request

	return 0;
}

//-------------------------------------------------------------
// Reception of a close connection request from dectmngr.
// Generate a libvoip onhook event and queue the request
// until Asterisk acknowledge the onhook event.
int line_close_connection_by_dect(int line, int pcm, struct voice_ubus_req_t *ubus_req)
{
	struct line_req_t *line_req;

	if (!voice_line_is_ready(line))
		return -1;

	if(lines[line].type != VOICE_LINE_DECT) {
		ENDPT_ERR("%s: lines[%d].type != VOICE_LINE_DECT\n", __func__, line);
		return -1;
	}
	ENDPT_DBG("line %d: pcm = %d\n", line, pcm);
	pcm_states_dump(__func__, line);

	if (pcm == CALL_DECT_UNAVAILABLE) {
		struct line_event_t *msg;

		ENDPT_DBG("There is no available DECT handsets for the call. "
			"Send DECT_UNAVAILABLE event to Asterisk\n");

		msg = malloc(sizeof(struct line_event_t));
		if (msg) {
			msg->name = "DECT_UNAVAILABLE";
			msg->data = 0;
			msg->line = line;
			send_event_main(msg, EVENT_MAIN_LINE);
		}

		return send_reply_dectmngr(ubus_req, line + 1, // +1 Dectmngr lines starting from 1
			pcm, UBUS_STATUS_OK);
	}

	if ((pcm & 0xFFFF) == CALL_REJECT && ((pcm >> 16) == CALL_DEFAULT0 || (pcm >> 16) == CALL_DEFAULT1)) {
		if (get_callid_state(lines[line].pcm_callid[(pcm >> 16)]) == CALLID_OBTAINING) {
			struct line_event_t *msg;

			ENDPT_DBG("DECT handsets may have not answered the call yet. "
				"Send CALL_REJECT event to Asterisk\n");

			msg = malloc(sizeof(struct line_event_t));
			if (msg) {
				msg->name = "CALL_REJECT";
				msg->data = 0;
				msg->line = line;
				send_event_main(msg, EVENT_MAIN_LINE);
			}

			return send_reply_dectmngr(ubus_req, line + 1, // +1 Dectmngr lines starting from 1
				pcm, UBUS_STATUS_OK);
		} else {
			pcm = pcm >> 16;
		}
	}

	if(get_callid_state(lines[line].pcm_callid[PCM_0]) <= CALLID_OBTAINING ||
		get_callid_state(lines[line].pcm_callid[PCM_1]) <= CALLID_OBTAINING) {
		if(pcm == CALL_DEFAULT0 || pcm == CALL_DEFAULT1) {
			lines[line].pcm_callid[pcm] = CALLID_INVALID;
			if (get_callid_state(lines[line].pcm_callid[1-pcm]) == CALLID_OBTAINING) {
				lines[line].pcm_callid[1-pcm] = CALLID_INVALID;
			}
		}
		voice_line_simulate_hook(line, VOICE_EVT_ONHOOK);
		return send_reply_dectmngr(ubus_req, line + 1, // +1 Dectmngr lines starting from 1
			pcm, UBUS_STATUS_OK);
	}

	if(pcm == CALL_DEFAULT0 || pcm == CALL_DEFAULT1)
		lines[line].pcm_callid[pcm] = CALLID_INVALID;

	if(send_dect_event_to_asterisk(line, dect_event_map[DECT_EVT_RELEASE]))
		return -1;

	/* Store the request in a queue. It will be picked up
	 * later, via the Asterisk ubus answer handler. */
	line_req = calloc(1, sizeof(struct line_req_t));
	if(!line_req) return -1;
	line_req->line = line;
	line_req->connection_id = -1;
	line_req->action = ACTION_CONN_CLOSE;
	memcpy(&line_req->ubus, ubus_req, sizeof(struct voice_ubus_req_t));
	if(ubus_queue_req_to_asterisk(line_req)) {
		free(line_req);
		return -1;
	}
	ubus_defer_request(line_req->ubus.ctx, line_req->ubus.reqIn, &line_req->ubus.reqOut);
	line_req->ubus.reqIn = NULL;

	return 0;
}
