/*
   Copyright (C) 2009 - 2010

   Artem Makhutov <artem@makhutov.org>
   http://www.makhutov.org

   Dmitry Vagin <dmitry2004@yandex.ru>

   bg <bg_one@mail.ru>
*/
#include "ast_config.h"

#include <asterisk.h>
#include <asterisk/cli.h>			/* struct ast_cli_entry; struct ast_cli_args */
#include <asterisk/callerid.h>			/* ast_describe_caller_presentation() */

#include "ast_compat.h"				/* asterisk compatibility fixes */

#include "cli.h"
#include "chan_dongle.h"			/* devices */
#include "helpers.h"				/* ITEMS_OF() send_ccwa_set() send_reset() send_sms() send_ussd() */
#include "pdiscovery.h"				/* pdiscovery_list_begin() pdiscovery_list_next() pdiscovery_list_end() */
#include "error.h"

static const char * restate2str_msg(restate_time_t when);

static char* complete_device (const char* word, int state)
{
	struct pvt*	pvt;
	char*	res = NULL;
	int	which = 0;
	int	wordlen = strlen (word);

	AST_RWLIST_RDLOCK (&gpublic->devices);
	AST_RWLIST_TRAVERSE (&gpublic->devices, pvt, entry)
	{
		if (!strncasecmp (PVT_ID(pvt), word, wordlen) && ++which > state)
		{
			res = ast_strdup (PVT_ID(pvt));
			break;
		}
	}
	AST_RWLIST_UNLOCK (&gpublic->devices);

	return res;
}

static char* cli_show_devices (struct ast_cli_entry* e, int cmd, struct ast_cli_args* a)
{
	struct pvt* pvt;

#define FORMAT1 "%-12.12s %-5.5s %-10.10s %-4.4s %-4.4s %-7.7s %-14.14s %-10.10s %-17.17s %-16.16s %-16.16s %-14.14s\n"
#define FORMAT2 "%-12.12s %-5d %-10.10s %-4d %-4d %-7d %-14.14s %-10.10s %-17.17s %-16.16s %-16.16s %-14.14s\n"

	switch (cmd)
	{
		case CLI_INIT:
			e->command =	"dongle show devices";
			e->usage   =	"Usage: dongle show devices\n"
					"       Shows the state of Dongle devices.\n";
			return NULL;

		case CLI_GENERATE:
			return NULL;
	}

	if (a->argc != 3)
	{
		return CLI_SHOWUSAGE;
	}

	ast_cli (a->fd, FORMAT1, "ID", "Group", "State", "RSSI", "Mode", "Submode", "Provider Name", "Model", "Firmware", "IMEI", "IMSI", "Number");

	AST_RWLIST_RDLOCK (&gpublic->devices);
	AST_RWLIST_TRAVERSE (&gpublic->devices, pvt, entry)
	{
		ast_mutex_lock (&pvt->lock);
		ast_cli (a->fd, FORMAT2,
			PVT_ID(pvt),
			CONF_SHARED(pvt, group),
			pvt_str_state(pvt),
			pvt->rssi,
			pvt->linkmode,
			pvt->linksubmode,
			pvt->provider_name,
			pvt->model,
			pvt->firmware,
			pvt->imei,
			pvt->imsi,
			pvt->subscriber_number
		);
		ast_mutex_unlock (&pvt->lock);
	}
	AST_RWLIST_UNLOCK (&gpublic->devices);

#undef FORMAT1
#undef FORMAT2

	return CLI_SUCCESS;
}

static char* cli_show_device_settings (struct ast_cli_entry* e, int cmd, struct ast_cli_args* a)
{
	struct pvt* pvt;

	switch (cmd)
	{
		case CLI_INIT:
			e->command =	"dongle show device settings";
			e->usage   =	"Usage: dongle show device settings <device>\n"
					"       Shows the settings of Dongle device.\n";
			return NULL;

		case CLI_GENERATE:
			if (a->pos == 4)
			{
				return complete_device (a->word, a->n);
			}
			return NULL;
	}

	if (a->argc != 5)
	{
		return CLI_SHOWUSAGE;
	}

	pvt = find_device (a->argv[4]);
	if (pvt)
	{
		ast_cli (a->fd, "------------- Settings ------------\n");
		ast_cli (a->fd, "  Device                  : %s\n", PVT_ID(pvt));
		ast_cli (a->fd, "  Audio                   : %s\n", CONF_UNIQ(pvt, audio_tty));
		ast_cli (a->fd, "  Data                    : %s\n", CONF_UNIQ(pvt, data_tty));
		ast_cli (a->fd, "  IMEI                    : %s\n", CONF_UNIQ(pvt, imei));
		ast_cli (a->fd, "  IMSI                    : %s\n", CONF_UNIQ(pvt, imsi));
		ast_cli (a->fd, "  Channel Language        : %s\n", CONF_SHARED(pvt, language));
		ast_cli (a->fd, "  Context                 : %s\n", CONF_SHARED(pvt, context));
		ast_cli (a->fd, "  Exten                   : %s\n", CONF_SHARED(pvt, exten));
		ast_cli (a->fd, "  Group                   : %d\n", CONF_SHARED(pvt, group));
		ast_cli (a->fd, "  RX gain                 : %d\n", CONF_SHARED(pvt, rxgain));
		ast_cli (a->fd, "  TX gain                 : %d\n", CONF_SHARED(pvt, txgain));
		ast_cli (a->fd, "  U2Diag                  : %d\n", CONF_SHARED(pvt, u2diag));
		ast_cli (a->fd, "  Use CallingPres         : %s\n", CONF_SHARED(pvt, usecallingpres) ? "Yes" : "No");
		ast_cli (a->fd, "  Default CallingPres     : %s\n", CONF_SHARED(pvt, callingpres) < 0 ? "<Not set>" : ast_describe_caller_presentation (CONF_SHARED(pvt, callingpres)));
		ast_cli (a->fd, "  Auto delete SMS         : %s\n", CONF_SHARED(pvt, autodeletesms) ? "Yes" : "No");
		ast_cli (a->fd, "  Disable SMS             : %s\n", CONF_SHARED(pvt, disablesms) ? "Yes" : "No");
		ast_cli (a->fd, "  Reset Dongle            : %s\n", CONF_SHARED(pvt, resetdongle) ? "Yes" : "No");
		ast_cli (a->fd, "  Call Waiting            : %s\n", dc_cw_setting2str(CONF_SHARED(pvt, callwaiting)));
		ast_cli (a->fd, "  DTMF                    : %s\n", dc_dtmf_setting2str(CONF_SHARED(pvt, dtmf)));
		ast_cli (a->fd, "  Minimal DTMF Gap        : %d\n", CONF_SHARED(pvt, mindtmfgap));
		ast_cli (a->fd, "  Minimal DTMF Duration   : %d\n", CONF_SHARED(pvt, mindtmfduration));
		ast_cli (a->fd, "  Minimal DTMF Interval   : %d\n", CONF_SHARED(pvt, mindtmfinterval));
		ast_cli (a->fd, "  Initial device state    : %s\n\n", dev_state2str(CONF_SHARED(pvt, initstate)));

		ast_mutex_unlock (&pvt->lock);
	}
	else
	{
		ast_cli (a->fd, "Device %s not found\n", a->argv[4]);
	}

	return CLI_SUCCESS;
}

static char* cli_show_device_state (struct ast_cli_entry* e, int cmd, struct ast_cli_args* a)
{
	struct pvt* pvt;
	struct ast_str * statebuf;
	char buf[40];

	switch (cmd)
	{
		case CLI_INIT:
			e->command =	"dongle show device state";
			e->usage   =	"Usage: dongle show device state <device>\n"
					"       Shows the state of Dongle device.\n";
			return NULL;

		case CLI_GENERATE:
			if (a->pos == 4)
			{
				return complete_device (a->word, a->n);
			}
			return NULL;
	}

	if (a->argc != 5)
	{
		return CLI_SHOWUSAGE;
	}

	pvt = find_device (a->argv[4]);
	if (pvt)
	{
		statebuf = pvt_str_state_ex(pvt);

		ast_cli (a->fd, "-------------- Status -------------\n");
		ast_cli (a->fd, "  Device                  : %s\n", PVT_ID(pvt));
		ast_cli (a->fd, "  State                   : %s\n", ast_str_buffer(statebuf));
		ast_cli (a->fd, "  Audio                   : %s\n", PVT_STATE(pvt, audio_tty));
		ast_cli (a->fd, "  Data                    : %s\n", PVT_STATE(pvt, data_tty));
		ast_cli (a->fd, "  Voice                   : %s\n", (pvt->has_voice) ? "Yes" : "No");
		ast_cli (a->fd, "  SMS                     : %s\n", (pvt->has_sms) ? "Yes" : "No");
		ast_cli (a->fd, "  Manufacturer            : %s\n", pvt->manufacturer);
		ast_cli (a->fd, "  Model                   : %s\n", pvt->model);
		ast_cli (a->fd, "  Firmware                : %s\n", pvt->firmware);
		ast_cli (a->fd, "  IMEI                    : %s\n", pvt->imei);
		ast_cli (a->fd, "  IMSI                    : %s\n", pvt->imsi);
		ast_cli (a->fd, "  GSM Registration Status : %s\n", GSM_regstate2str(pvt->gsm_reg_status));
		ast_cli (a->fd, "  RSSI                    : %d, %s\n", pvt->rssi, rssi2dBm(pvt->rssi, buf, sizeof(buf)));
		ast_cli (a->fd, "  Mode                    : %s\n", sys_mode2str(pvt->linkmode));
		ast_cli (a->fd, "  Submode                 : %s\n", sys_submode2str(pvt->linksubmode));
		ast_cli (a->fd, "  Provider Name           : %s\n", pvt->provider_name);
		ast_cli (a->fd, "  Location area code      : %s\n", pvt->location_area_code);
		ast_cli (a->fd, "  Cell ID                 : %s\n", pvt->cell_id);
		ast_cli (a->fd, "  Subscriber Number       : %s\n", pvt->subscriber_number);
		ast_cli (a->fd, "  SMS Service Center      : %s\n", pvt->sms_scenter);
		ast_cli (a->fd, "  Use UCS-2 encoding      : %s\n", pvt->use_ucs2_encoding ? "Yes" : "No");
		ast_cli (a->fd, "  Tasks in queue          : %u\n", PVT_STATE(pvt, at_tasks));
		ast_cli (a->fd, "  Commands in queue       : %u\n", PVT_STATE(pvt, at_cmds));
		ast_cli (a->fd, "  Call Waiting            : %s\n", pvt->has_call_waiting ? "Enabled" : "Disabled" );
		ast_cli (a->fd, "  Current device state    : %s\n", dev_state2str(pvt->current_state) );
		ast_cli (a->fd, "  Desired device state    : %s\n", dev_state2str(pvt->desired_state) );
		ast_cli (a->fd, "  When change state       : %s\n", restate2str_msg(pvt->restart_time) );

		ast_cli (a->fd, "  Calls/Channels          : %u\n", PVT_STATE(pvt, chansno));
		ast_cli (a->fd, "    Active                : %u\n", PVT_STATE(pvt, chan_count[CALL_STATE_ACTIVE]));
		ast_cli (a->fd, "    Held                  : %u\n", PVT_STATE(pvt, chan_count[CALL_STATE_ONHOLD]));
		ast_cli (a->fd, "    Dialing               : %u\n", PVT_STATE(pvt, chan_count[CALL_STATE_DIALING]));
		ast_cli (a->fd, "    Alerting              : %u\n", PVT_STATE(pvt, chan_count[CALL_STATE_ALERTING]));
		ast_cli (a->fd, "    Incoming              : %u\n", PVT_STATE(pvt, chan_count[CALL_STATE_INCOMING]));
		ast_cli (a->fd, "    Waiting               : %u\n", PVT_STATE(pvt, chan_count[CALL_STATE_WAITING]));
		ast_cli (a->fd, "    Releasing             : %u\n", PVT_STATE(pvt, chan_count[CALL_STATE_RELEASED]));
		ast_cli (a->fd, "    Initializing          : %u\n\n", PVT_STATE(pvt, chan_count[CALL_STATE_INIT]));
/* TODO: show call waiting  network setting and local config value */
		ast_mutex_unlock (&pvt->lock);

		ast_free(statebuf);
	}
	else
	{
		ast_cli (a->fd, "Device %s not found\n", a->argv[4]);
	}

	return CLI_SUCCESS;
}

#/* */
static int32_t getACD(uint32_t calls, uint32_t duration)
{
	int32_t acd;

	if(calls) {
		acd = duration / calls;
	} else {
		acd = -1;
	}

	return acd;
}

#/* */
static int32_t getASR(uint32_t total, uint32_t handled)
{
	int32_t asr;
	if(total) {
		asr = handled * 100 / total;
	} else {
		asr = -1;
	}

	return asr;
}

static char* cli_show_device_statistics (struct ast_cli_entry* e, int cmd, struct ast_cli_args* a)
{
	struct pvt * pvt;

	switch (cmd)
	{
		case CLI_INIT:
			e->command =	"dongle show device statistics";
			e->usage   =	"Usage: dongle show device statistics <device>\n"
					"       Shows the statistics of Dongle device.\n";
			return NULL;

		case CLI_GENERATE:
			if (a->pos == 4)
			{
				return complete_device (a->word, a->n);
			}
			return NULL;
	}

	if (a->argc != 5)
	{
		return CLI_SHOWUSAGE;
	}

	pvt = find_device (a->argv[4]);
	if (pvt)
	{
		ast_cli (a->fd, "-------------- Statistics -------------\n");
		ast_cli (a->fd, "  Device                      : %s\n", PVT_ID(pvt));
		ast_cli (a->fd, "  Queue tasks                 : %u\n", PVT_STAT(pvt, at_tasks));
		ast_cli (a->fd, "  Queue commands              : %u\n", PVT_STAT(pvt, at_cmds));
		ast_cli (a->fd, "  Responses                   : %u\n", PVT_STAT(pvt, at_responses));
		ast_cli (a->fd, "  Bytes of read responses     : %u\n", PVT_STAT(pvt, d_read_bytes));
		ast_cli (a->fd, "  Bytes of written commands   : %u\n", PVT_STAT(pvt, d_write_bytes));
		ast_cli (a->fd, "  Bytes of read audio         : %llu\n", (unsigned long long int)PVT_STAT(pvt, a_read_bytes));
		ast_cli (a->fd, "  Bytes of written audio      : %llu\n", (unsigned long long int)PVT_STAT(pvt, a_write_bytes));
		ast_cli (a->fd, "  Readed frames               : %u\n", PVT_STAT(pvt, read_frames));
		ast_cli (a->fd, "  Readed short frames         : %u\n", PVT_STAT(pvt, read_sframes));
		ast_cli (a->fd, "  Wrote frames                : %u\n", PVT_STAT(pvt, write_frames));
		ast_cli (a->fd, "  Wrote short frames          : %u\n", PVT_STAT(pvt, write_tframes));
		ast_cli (a->fd, "  Wrote silence frames        : %u\n", PVT_STAT(pvt, write_sframes));
		ast_cli (a->fd, "  Write buffer overflow bytes : %llu\n", (unsigned long long int)PVT_STAT(pvt, write_rb_overflow_bytes));
		ast_cli (a->fd, "  Write buffer overflow count : %u\n", PVT_STAT(pvt, write_rb_overflow));
		ast_cli (a->fd, "  Incoming calls              : %u\n", PVT_STAT(pvt, in_calls));
		ast_cli (a->fd, "  Waiting calls               : %u\n", PVT_STAT(pvt, cw_calls));
		ast_cli (a->fd, "  Handled input calls         : %u\n", PVT_STAT(pvt, in_calls_handled));
		ast_cli (a->fd, "  Fails to PBX run            : %u\n", PVT_STAT(pvt, in_pbx_fails));
		ast_cli (a->fd, "  Attempts to outgoing calls  : %u\n", PVT_STAT(pvt, out_calls));
		ast_cli (a->fd, "  Answered outgoing calls     : %u\n", PVT_STAT(pvt, calls_answered[CALL_DIR_OUTGOING]));
		ast_cli (a->fd, "  Answered incoming calls     : %u\n", PVT_STAT(pvt, calls_answered[CALL_DIR_INCOMING]));
		ast_cli (a->fd, "  Seconds of outgoing calls   : %u\n", PVT_STAT(pvt, calls_duration[CALL_DIR_OUTGOING]));
		ast_cli (a->fd, "  Seconds of incoming calls   : %u\n", PVT_STAT(pvt, calls_duration[CALL_DIR_INCOMING]));
		ast_cli (a->fd, "  ACD for incoming calls      : %d\n", getACD(PVT_STAT(pvt, calls_answered[CALL_DIR_INCOMING]), PVT_STAT(pvt, calls_duration[CALL_DIR_INCOMING])));
		ast_cli (a->fd, "  ACD for outgoing calls      : %d\n", getACD(PVT_STAT(pvt, calls_answered[CALL_DIR_OUTGOING]), PVT_STAT(pvt, calls_duration[CALL_DIR_OUTGOING])));
/*
		ast_cli (a->fd, "  ACD                         : %d\n",
			getACD(
				PVT_STAT(pvt, calls_answered[CALL_DIR_OUTGOING])
				+ PVT_STAT(pvt, calls_answered[CALL_DIR_INCOMING]),

				PVT_STAT(pvt, calls_duration[CALL_DIR_OUTGOING])
				+ PVT_STAT(pvt, calls_duration[CALL_DIR_INCOMING])
				)
			);
*/
		ast_cli (a->fd, "  ASR for incoming calls      : %d\n", getASR(PVT_STAT(pvt, in_calls) + PVT_STAT(pvt, cw_calls), PVT_STAT(pvt, calls_answered[CALL_DIR_INCOMING])) );
		ast_cli (a->fd, "  ASR for outgoing calls      : %d\n\n", getASR(PVT_STAT(pvt, out_calls), PVT_STAT(pvt, calls_answered[CALL_DIR_OUTGOING])));
/*
		ast_cli (a->fd, "  ASR                         : %d\n\n",
			getASR(
				PVT_STAT(pvt, out_calls)
				+ PVT_STAT(pvt, in_calls)
				+ PVT_STAT(pvt, cw_calls),

				PVT_STAT(pvt, calls_answered[CALL_DIR_OUTGOING])
				+ PVT_STAT(pvt, calls_answered[CALL_DIR_INCOMING])
				)
			);
*/
		ast_mutex_unlock (&pvt->lock);
	}
	else
	{
		ast_cli (a->fd, "Device %s not found\n", a->argv[4]);
	}

	return CLI_SUCCESS;
}


static char* cli_show_version (struct ast_cli_entry* e, int cmd, struct ast_cli_args* a)
{
	switch (cmd)
	{
		case CLI_INIT:
			e->command =	"dongle show version";
			e->usage   =	"Usage: dongle show version\n"
					"       Shows the version of module.\n";
			return NULL;

		case CLI_GENERATE:
			return NULL;
	}

	if (a->argc != 3)
	{
		return CLI_SHOWUSAGE;
	}

	ast_cli (a->fd, "\n%s: %s, Version %s, Revision %s\nProject Home: %s\nBug Reporting: %s\n\n", AST_MODULE, MODULE_DESCRIPTION, MODULE_VERSION, PACKAGE_REVISION, MODULE_URL, MODULE_BUGREPORT);

	return CLI_SUCCESS;
}

static char* cli_cmd (struct ast_cli_entry* e, int cmd, struct ast_cli_args* a)
{
	const char * msg;

	switch (cmd)
	{
		case CLI_INIT:
			e->command =	"dongle cmd";
			e->usage   =	"Usage: dongle cmd <device> <command>\n"
					"       Send <command> to the rfcomm port on the device\n"
					"       with the specified <device>.\n";
			return NULL;

		case CLI_GENERATE:
			if (a->pos == 2)
			{
				return complete_device (a->word, a->n);
			}
			return NULL;
	}

	if (a->argc != 4)
	{
		return CLI_SHOWUSAGE;
	}

	int res = send_at_command(a->argv[2], a->argv[3]);
	ast_cli (a->fd, "[%s] '%s' %s\n", a->argv[2], a->argv[3], res < 0 ? error2str(chan_dongle_err) : "AT command queued");

	return CLI_SUCCESS;
}

static char* cli_ussd (struct ast_cli_entry* e, int cmd, struct ast_cli_args* a)
{

	switch (cmd)
	{
		case CLI_INIT:
			e->command = "dongle ussd";
			e->usage =
				"Usage: dongle ussd <device> <command>\n"
				"       Send ussd <command> to the dongle\n"
				"       with the specified <device>.\n";
			return NULL;

		case CLI_GENERATE:
			if (a->pos == 2)
			{
				return complete_device (a->word, a->n);
			}
			return NULL;
	}

	if (a->argc != 4)
	{
		return CLI_SHOWUSAGE;
	}

	int res = send_ussd(a->argv[2], a->argv[3]);
	ast_cli(a->fd, "[%s] %s\n", a->argv[2], res < 0 ? error2str(chan_dongle_err) : "USSD queued for send");

	return CLI_SUCCESS;
}

static char* cli_sms (struct ast_cli_entry* e, int cmd, struct ast_cli_args* a)
{
	struct ast_str * buf;
	int i;

	switch (cmd)
	{
		case CLI_INIT:
			e->command = "dongle sms";
			e->usage =
				"Usage: dongle sms <device> <number> <message>\n"
				"       Send a SMS to <number> with the <message> from <device>\n";
			return NULL;

		case CLI_GENERATE:
			if (a->pos == 2)
			{
				return complete_device (a->word, a->n);
			}
			return NULL;
	}

	if (a->argc < 5)
	{
		return CLI_SHOWUSAGE;
	}

	buf = ast_str_create (160 * 255);
	for (i = 4; i < a->argc; i++)
	{
		if (i < (a->argc - 1))
		{
			ast_str_append (&buf, 0, "%s ", a->argv[i]);
		}
		else
		{
			ast_str_append (&buf, 0, "%s", a->argv[i]);
		}
	}

	int res = send_sms(a->argv[2], a->argv[3], ast_str_buffer(buf), 0, "1", "UNKNOWN", 8);
	ast_free (buf);
	ast_cli(a->fd, "[%s] %s\n", a->argv[2], res < 0 ? error2str(chan_dongle_err) : "SMS queued for send");

	return CLI_SUCCESS;
}

#if ASTERISK_VERSION_NUM >= 10800 /* 1.8+ */
typedef const char * const * ast_cli_complete2_t;
#else /* 1.8- */
typedef char * const * ast_cli_complete2_t;
#endif /* ^1.8- */

static char* cli_ccwa_set (struct ast_cli_entry* e, int cmd, struct ast_cli_args* a)
{
	static const char * const choices[] = { "enable", "disable", NULL };
	call_waiting_t enable;

	switch (cmd)
	{
		case CLI_INIT:
			e->command = "dongle callwaiting";
			e->usage =
				"Usage: dongle callwaiting disable|enable <device>\n"
				"       Disable/Enable Call-Waiting on <device>\n";
			return NULL;

		case CLI_GENERATE:
			if (a->pos == 2)
			{
				return ast_cli_complete(a->word, (ast_cli_complete2_t)choices, a->n);
			}
			if (a->pos == 3)
			{
				return complete_device (a->word, a->n);
			}
			return NULL;
	}

	if (a->argc < 4)
	{
		return CLI_SHOWUSAGE;
	}
	if (strcasecmp("disable", a->argv[2]) == 0)
		enable = CALL_WAITING_DISALLOWED;
	else if (strcasecmp("enable", a->argv[2]) == 0)
		enable = CALL_WAITING_ALLOWED;
	else
		return CLI_SHOWUSAGE;

	int res = send_ccwa_set(a->argv[3], enable);
	ast_cli(a->fd, "[%s] %s\n", a->argv[3], res < 0 ? error2str(chan_dongle_err) : "Call-Waiting commands queued for execute");

	return CLI_SUCCESS;
}

static char* cli_reset (struct ast_cli_entry* e, int cmd, struct ast_cli_args* a)
{
	switch (cmd)
	{
		case CLI_INIT:
			e->command = "dongle reset";
			e->usage =
				"Usage: dongle reset <device>\n"
				"       Reset dongle <device>\n";
			return NULL;

		case CLI_GENERATE:
			if (a->pos == 2)
			{
				return complete_device (a->word, a->n);
			}
			return NULL;
	}

	if (a->argc != 3)
	{
		return CLI_SHOWUSAGE;
	}

	int res = send_reset(a->argv[2]);
	ast_cli(a->fd, "[%s] %s\n", a->argv[2], res < 0 ? error2str(chan_dongle_err) : "Reset command queued for execute");

	return CLI_SUCCESS;
}

static const char * const a_choices[] = { "now", "gracefully", "when", NULL };
static const char * const a_choices2[] = { "convenient", NULL };

static const char * restate2str_msg(restate_time_t when)
{
	static const char * const choices[] = { "now", "gracefully", "when convenient" };
	return enum2str(when, choices, ITEMS_OF(choices));
}

#/* */
static char* cli_restart_event(struct ast_cli_entry* e, int cmd, struct ast_cli_args* a, dev_state_t event)
{

	static char * const cmds[] = {
		"dongle stop",
		"dongle restart",
		"dongle remove",
		};
	static const char * const usage[] = {
		"Usage: dongle stop < now | gracefully | when convenient > <device>\n"
		"       Stop dongle <device>\n",

		"Usage: dongle restart < now | gracefully | when convenient > <device>\n"
		"       Restart dongle <device>\n",

		"Usage: dongle remove < now | gracefully | when convenient > <device>\n"
		"       Remove dongle <device>\n",
		};

	const char * device = NULL;
	int res;
	int i;

	switch (cmd)
	{
		case CLI_INIT:
			e->command = cmds[event];
			e->usage = usage[event];
			return NULL;

		case CLI_GENERATE:
			switch(a->pos)
			{
				case 2:
					return ast_cli_complete(a->word, (ast_cli_complete2_t)a_choices, a->n);
				case 3:
					if(strcasecmp(a->argv[2], "when") == 0)
						return ast_cli_complete(a->word, (ast_cli_complete2_t)a_choices2, a->n);
					return complete_device(a->word, a->n);
					break;
				case 4:
					if(strcasecmp(a->argv[2], "when") == 0 && strcasecmp(a->argv[3], "convenient") == 0)
						return complete_device(a->word, a->n);
			}
			return NULL;
	}

	if(a->argc != 4 && a->argc != 5)
	{
		return CLI_SHOWUSAGE;
	}

	for(i = 0; a_choices[i]; i++)
	{
		if(strcasecmp(a->argv[2], a_choices[i]) == 0)
		{
			if(i == RESTATE_TIME_CONVENIENT)
			{
				if(a->argc == 5 && strcasecmp(a->argv[3], a_choices2[0]) == 0)
				{
					device = a->argv[4];
				}
			}
			else if(a->argc == 4)
			{
				device = a->argv[3];
			}

			if(device)
			{
				res = schedule_restart_event(event, i, device);
				ast_cli(a->fd, "[%s] %s\n", device, res < 0 ? error2str(chan_dongle_err) : dev_state2str_msg(event));
				return CLI_SUCCESS;
			}
			break;
		}
	}
	return CLI_SHOWUSAGE;
}

#/* */
static char* cli_stop(struct ast_cli_entry* e, int cmd, struct ast_cli_args* a)
{
	return cli_restart_event(e, cmd, a, DEV_STATE_STOPPED);
}

#/* */
static char* cli_restart(struct ast_cli_entry* e, int cmd, struct ast_cli_args* a)
{
	return cli_restart_event(e, cmd, a, DEV_STATE_RESTARTED);
}

#/* */
static char * cli_remove(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
	return cli_restart_event(e, cmd, a, DEV_STATE_REMOVED);
}

#/* */
static char* cli_start(struct ast_cli_entry* e, int cmd, struct ast_cli_args* a)
{
	switch (cmd)
	{
		case CLI_INIT:
			e->command =	"dongle start";
			e->usage   =	"Usage: dongle start <device>\n"
					"       Start dongle <device>\n";
			return NULL;

		case CLI_GENERATE:
			if(a->pos == 2)
				return complete_device(a->word, a->n);
			return NULL;
	}

	if (a->argc != 3)
	{
		return CLI_SHOWUSAGE;
	}

	int res = schedule_restart_event(DEV_STATE_STARTED, RESTATE_TIME_NOW, a->argv[2]);
	ast_cli(a->fd, "[%s] %s\n", a->argv[2], res < 0 ? error2str(chan_dongle_err) : dev_state2str_msg(DEV_STATE_STARTED));

	return CLI_SUCCESS;
}

static char * cli_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
	int ok = 0;
	int i;

	switch (cmd)
	{
		case CLI_INIT:
			e->command =	"dongle reload";
			e->usage   =	"Usage: dongle reload < now | gracefully | when convenient >\n"
					"       Reloads the chan_dongle configuration\n";
			return NULL;

		case CLI_GENERATE:
			switch(a->pos)
			{
				case 2:
					return ast_cli_complete(a->word, (ast_cli_complete2_t)a_choices, a->n);
				case 3:
					if(strcasecmp(a->argv[2], "when") == 0)
						return ast_cli_complete(a->word, (ast_cli_complete2_t)a_choices2, a->n);
			}
			return NULL;
	}

	if (a->argc != 3 && a->argc != 4)
	{
		return CLI_SHOWUSAGE;
	}

	for(i = 0; a_choices[i]; i++)
	{
		if(strcasecmp(a->argv[2], a_choices[i]) == 0)
		{
			if(i == RESTATE_TIME_CONVENIENT)
			{
				if(a->argc == 4 && strcasecmp(a->argv[3], a_choices2[0]) == 0)
				{
					ok = 1;
				}
			}
			else if(a->argc == 3)
			{
				ok = 1;
			}

			if(ok)
			{
				pvt_reload(i);
				return CLI_SUCCESS;
			}
			break;
		}
	}
	return CLI_SHOWUSAGE;
}

#/* */
static char * cli_discovery(struct ast_cli_entry * e, int cmd, struct ast_cli_args * a)
{
	const struct pdiscovery_cache_item * item;
	const struct pdiscovery_result * res;
	struct pvt * pvt;
	const char * imei;
	const char * imsi;
	int imeilen;
	int imsilen;

	switch (cmd) {
		case CLI_INIT:
			e->command =	"dongle discovery";
			e->usage   =	"Usage: dongle discovery\n"
					"       Discovery devices and create config\n";
			return NULL;

		case CLI_GENERATE:
			return NULL;
	}

	if (a->argc != 2) {
		return CLI_SHOWUSAGE;
	}

	AST_RWLIST_RDLOCK(&gpublic->devices);
	for(res = pdiscovery_list_begin(&item); res; res = pdiscovery_list_next(&item)) {
		AST_RWLIST_TRAVERSE (&gpublic->devices, pvt, entry) {
			if(strcmp(PVT_STATE(pvt, data_tty), res->ports.ports[INTERFACE_TYPE_DATA]) == 0) {
				break;
			}
		}
		if(pvt) {
/*
			ast_cli(a->fd, "; existing device\n");
			ast_cli(a->fd, "[%s](defaults)\n", PVT_ID(pvt));

			if(CONF_UNIQ(pvt, audio_tty)[0])
				ast_cli(a->fd, "audio=%s\n", CONF_UNIQ(pvt, audio_tty));
			else
				ast_cli(a->fd, ";audio=%s\n", PVT_STATE(pvt, audio_tty));

			if(CONF_UNIQ(pvt, data_tty)[0])
				ast_cli(a->fd, "data=%s\n", CONF_UNIQ(pvt, data_tty));
			else
				ast_cli(a->fd, ";data=%s\n", PVT_STATE(pvt, data_tty));

			if(CONF_UNIQ(pvt, imei)[0])
				ast_cli(a->fd, "imei=%s\n", CONF_UNIQ(pvt, imei));
			else
				ast_cli(a->fd, ";imei=%s\n", pvt->imei);

			if(CONF_UNIQ(pvt, imsi)[0])
				ast_cli(a->fd, "imsi=%s\n\n", CONF_UNIQ(pvt, imsi));
			else
				ast_cli(a->fd, ";imsi=%s\n\n", pvt->imsi);
*/
		} else {
			imei = S_OR(res->imei, "");
			imsi = S_OR(res->imsi, "");

			imeilen = strlen(imei);
			imsilen = strlen(imsi);

			ast_cli(a->fd, "; discovered device\n");
			ast_cli(a->fd, "[dc_%s_%s](defaults)\n", imei + imeilen - MIN(imeilen,4), imsi + imsilen - MIN(imsilen,4));
			ast_cli(a->fd, ";audio=%s\n", res->ports.ports[INTERFACE_TYPE_VOICE]);
			ast_cli(a->fd, ";data=%s\n", res->ports.ports[INTERFACE_TYPE_DATA]);
			ast_cli(a->fd, "imei=%s\n", imei);
			ast_cli(a->fd, "imsi=%s\n\n", imsi);
		}
	}
	pdiscovery_list_end();
	AST_RWLIST_UNLOCK(&gpublic->devices);

	return CLI_SUCCESS;
}



static struct ast_cli_entry cli[] = {
	AST_CLI_DEFINE (cli_show_devices,	"Show Dongle devices state"),
	AST_CLI_DEFINE (cli_show_device_settings,"Show Dongle device settings"),
	AST_CLI_DEFINE (cli_show_device_state,	 "Show Dongle device state"),
	AST_CLI_DEFINE (cli_show_device_statistics,"Show Dongle device statistics"),
	AST_CLI_DEFINE (cli_show_version,	"Show module version"),
	AST_CLI_DEFINE (cli_cmd,		"Send commands to port for debugging"),
	AST_CLI_DEFINE (cli_ussd,		"Send USSD commands to the dongle"),
	AST_CLI_DEFINE (cli_sms,		"Send SMS from the dongle"),
	AST_CLI_DEFINE (cli_ccwa_set,		"Enable/Disable Call-Waiting on the dongle"),
	AST_CLI_DEFINE (cli_reset,		"Reset dongle now"),

	AST_CLI_DEFINE (cli_stop,		"Stop dongle"),
	AST_CLI_DEFINE (cli_restart,		"Restart dongle"),
	AST_CLI_DEFINE (cli_remove,		"Remove dongle"),
	AST_CLI_DEFINE (cli_reload,		"Reload dongle"),

	AST_CLI_DEFINE (cli_start,		"Start dongle"),
	AST_CLI_DEFINE (cli_discovery,		"Discovery devices and create config"),
};

#/* */
EXPORT_DEF void cli_register()
{
	ast_cli_register_multiple (cli, ITEMS_OF(cli));
}

#/* */
EXPORT_DEF void cli_unregister()
{
	ast_cli_unregister_multiple (cli, ITEMS_OF(cli));
}

/*
static char * ast_str_truncate2(struct ast_str *buf, ssize_t len)
{
	if (len < 0) {
		buf->__AST_STR_USED += ((ssize_t) abs(len)) > ((ssize_t) buf->__AST_STR_USED) ? (ssize_t)-buf->__AST_STR_USED : (ssize_t)len;
	} else {
		buf->__AST_STR_USED = len;
	}
	buf->__AST_STR_STR[buf->__AST_STR_USED] = '\0';
	return buf->__AST_STR_STR;
}
*/
