/* OsmoBTS VTY interface */


#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <osmocom/vty/logging.h>
#include <osmocom/vty/stats.h>
#include <osmocom/vty/misc.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/pcu/pcuif_proto.h>
#include "pcu_vty.h"
#include "gprs_rlcmac.h"
#include <pdch.h>
#include "bts.h"
#include "tbf.h"
#include "pcu_vty_functions.h"

extern void *tall_pcu_ctx;

static const struct value_string pcu_gsmtap_categ_names[] = {
	{ PCU_GSMTAP_C_DL_UNKNOWN,	"dl-unknown" },
	{ PCU_GSMTAP_C_DL_DUMMY,	"dl-dummy" },
	{ PCU_GSMTAP_C_DL_CTRL,		"dl-ctrl" },
	{ PCU_GSMTAP_C_DL_DATA_GPRS,	"dl-data-gprs" },
	{ PCU_GSMTAP_C_DL_DATA_EGPRS,	"dl-data-egprs" },
	{ PCU_GSMTAP_C_DL_PTCCH,	"dl-ptcch" },

	{ PCU_GSMTAP_C_UL_UNKNOWN,	"ul-unknown" },
	{ PCU_GSMTAP_C_UL_DUMMY,	"ul-dummy" },
	{ PCU_GSMTAP_C_UL_CTRL,		"ul-ctrl" },
	{ PCU_GSMTAP_C_UL_DATA_GPRS,	"ul-data-gprs" },
	{ PCU_GSMTAP_C_UL_DATA_EGPRS,	"ul-data-egprs" },

	{ 0, NULL }
};

static const struct value_string pcu_gsmtap_categ_help[] = {
	{ PCU_GSMTAP_C_DL_UNKNOWN,	"Unknown / Unparseable / Erroneous Downlink Blocks" },
	{ PCU_GSMTAP_C_DL_DUMMY,	"Downlink Dummy Blocks" },
	{ PCU_GSMTAP_C_DL_CTRL,		"Downlink Control Blocks" },
	{ PCU_GSMTAP_C_DL_DATA_GPRS,	"Downlink Data Blocks (GPRS)" },
	{ PCU_GSMTAP_C_DL_DATA_EGPRS,	"Downlink Data Blocks (EGPRS)" },
	{ PCU_GSMTAP_C_DL_PTCCH,	"Downlink PTCCH Blocks" },

	{ PCU_GSMTAP_C_UL_UNKNOWN,	"Unknown / Unparseable / Erroneous Downlink Blocks" },
	{ PCU_GSMTAP_C_UL_DUMMY,	"Uplink Dummy Blocks" },
	{ PCU_GSMTAP_C_UL_CTRL,		"Uplink Control Blocks" },
	{ PCU_GSMTAP_C_UL_DATA_GPRS,	"Uplink Data Blocks (GPRS)" },
	{ PCU_GSMTAP_C_UL_DATA_EGPRS,	"Uplink Data Blocks (EGPRS)" },

	{ 0, NULL }
};


DEFUN(cfg_pcu_gsmtap_categ, cfg_pcu_gsmtap_categ_cmd, "HIDDEN", "HIDDEN")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();
	int categ;

	categ = get_string_value(pcu_gsmtap_categ_names, argv[0]);
	if (categ < 0)
		return CMD_WARNING;

	bts->gsmtap_categ_mask |= (1 << categ);

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_gsmtap_categ, cfg_pcu_no_gsmtap_categ_cmd, "HIDDEN", "HIDDEN")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();
	int categ;

	categ = get_string_value(pcu_gsmtap_categ_names, argv[0]);
	if (categ < 0)
		return CMD_WARNING;

	bts->gsmtap_categ_mask &= ~(1 << categ);

	return CMD_SUCCESS;
}


int pcu_vty_go_parent(struct vty *vty)
{
	switch (vty->node) {
#if 0
	case TRX_NODE:
		vty->node = PCU_NODE;
		{
			struct gsm_bts_trx *trx = vty->index;
			vty->index = trx->bts;
		}
		break;
#endif
	default:
		vty->node = CONFIG_NODE;
	}
	return (enum node_type) vty->node;
}

int pcu_vty_is_config_node(struct vty *vty, int node)
{
	switch (node) {
	case PCU_NODE:
		return 1;
	default:
		return 0;
	}
}

static struct cmd_node pcu_node = {
	(enum node_type) PCU_NODE,
	"%s(config-pcu)# ",
	1,
};

static int config_write_pcu(struct vty *vty)
{
	struct gprs_rlcmac_bts *bts = bts_main_data();
	unsigned int i;

	vty_out(vty, "pcu%s", VTY_NEWLINE);
	if (bts->egprs_enabled)
		vty_out(vty, " egprs only%s", VTY_NEWLINE);

	vty_out(vty, " flow-control-interval %d%s", bts->fc_interval,
		VTY_NEWLINE);
	if (bts->fc_bvc_bucket_size)
		vty_out(vty, " flow-control force-bvc-bucket-size %d%s",
			bts->fc_bvc_bucket_size, VTY_NEWLINE);
	if (bts->fc_bvc_leak_rate)
		vty_out(vty, " flow-control force-bvc-leak-rate %d%s",
			bts->fc_bvc_leak_rate, VTY_NEWLINE);
	if (bts->fc_ms_bucket_size)
		vty_out(vty, " flow-control force-ms-bucket-size %d%s",
			bts->fc_ms_bucket_size, VTY_NEWLINE);
	if (bts->fc_ms_leak_rate)
		vty_out(vty, " flow-control force-ms-leak-rate %d%s",
			bts->fc_ms_leak_rate, VTY_NEWLINE);
	if (bts->force_cs) {
		if (bts->initial_cs_ul == bts->initial_cs_dl)
			vty_out(vty, " cs %d%s", bts->initial_cs_dl,
				VTY_NEWLINE);
		else
			vty_out(vty, " cs %d %d%s", bts->initial_cs_dl,
				bts->initial_cs_ul, VTY_NEWLINE);
	}
	if (bts->max_cs_dl && bts->max_cs_ul) {
		if (bts->max_cs_ul == bts->max_cs_dl)
			vty_out(vty, " cs max %d%s", bts->max_cs_dl,
				VTY_NEWLINE);
		else
			vty_out(vty, " cs max %d %d%s", bts->max_cs_dl,
				bts->max_cs_ul, VTY_NEWLINE);
	}
	if (bts->cs_adj_enabled)
		vty_out(vty, " cs threshold %d %d%s",
			bts->cs_adj_lower_limit, bts->cs_adj_upper_limit,
			VTY_NEWLINE);
	else
		vty_out(vty, " no cs threshold%s", VTY_NEWLINE);

	if (bts->cs_downgrade_threshold)
		vty_out(vty, " cs downgrade-threshold %d%s",
			bts->cs_downgrade_threshold, VTY_NEWLINE);
	else
		vty_out(vty, " no cs downgrade-threshold%s", VTY_NEWLINE);

	vty_out(vty, " cs link-quality-ranges cs1 %d cs2 %d %d cs3 %d %d cs4 %d%s",
		bts->cs_lqual_ranges[0].high,
		bts->cs_lqual_ranges[1].low,
		bts->cs_lqual_ranges[1].high,
		bts->cs_lqual_ranges[2].low,
		bts->cs_lqual_ranges[2].high,
		bts->cs_lqual_ranges[3].low,
		VTY_NEWLINE);

	vty_out(vty, " mcs link-quality-ranges mcs1 %d mcs2 %d %d mcs3 %d %d mcs4 %d %d mcs5 %d %d mcs6 %d %d mcs7 %d %d mcs8 %d %d mcs9 %d%s",
		bts->mcs_lqual_ranges[0].high,
		bts->mcs_lqual_ranges[1].low,
		bts->mcs_lqual_ranges[1].high,
		bts->mcs_lqual_ranges[2].low,
		bts->mcs_lqual_ranges[2].high,
		bts->mcs_lqual_ranges[3].low,
		bts->mcs_lqual_ranges[3].high,
		bts->mcs_lqual_ranges[4].low,
		bts->mcs_lqual_ranges[4].high,
		bts->mcs_lqual_ranges[5].low,
		bts->mcs_lqual_ranges[5].high,
		bts->mcs_lqual_ranges[6].low,
		bts->mcs_lqual_ranges[6].high,
		bts->mcs_lqual_ranges[7].low,
		bts->mcs_lqual_ranges[7].high,
		bts->mcs_lqual_ranges[8].low,
		VTY_NEWLINE);

	if (bts->initial_mcs_dl != 1 && bts->initial_mcs_ul != 1) {
		if (bts->initial_mcs_ul == bts->initial_mcs_dl)
			vty_out(vty, " mcs %d%s", bts->initial_mcs_dl,
				VTY_NEWLINE);
		else
			vty_out(vty, " mcs %d %d%s", bts->initial_mcs_dl,
				bts->initial_mcs_ul, VTY_NEWLINE);
	}

	if (bts->max_mcs_dl && bts->max_mcs_ul) {
		if (bts->max_mcs_ul == bts->max_mcs_dl)
			vty_out(vty, " mcs max %d%s", bts->max_mcs_dl,
				VTY_NEWLINE);
		else
			vty_out(vty, " mcs max %d %d%s", bts->max_mcs_dl,
				bts->max_mcs_ul, VTY_NEWLINE);
	}

	vty_out(vty, " window-size %d %d%s", bts->ws_base, bts->ws_pdch,
		VTY_NEWLINE);

	if (bts->dl_arq_type)
		vty_out(vty, " egprs dl arq-type arq2%s",
			VTY_NEWLINE);

	if (bts->force_llc_lifetime == 0xffff)
		vty_out(vty, " queue lifetime infinite%s", VTY_NEWLINE);
	else if (bts->force_llc_lifetime)
		vty_out(vty, " queue lifetime %d%s", bts->force_llc_lifetime,
			VTY_NEWLINE);
	if (bts->llc_discard_csec)
		vty_out(vty, " queue hysteresis %d%s", bts->llc_discard_csec,
			VTY_NEWLINE);
	if (bts->llc_idle_ack_csec)
		vty_out(vty, " queue idle-ack-delay %d%s", bts->llc_idle_ack_csec,
			VTY_NEWLINE);
	if (bts->llc_codel_interval_msec == LLC_CODEL_USE_DEFAULT)
		vty_out(vty, " queue codel%s", VTY_NEWLINE);
	else if (bts->llc_codel_interval_msec == LLC_CODEL_DISABLE)
		vty_out(vty, " no queue codel%s", VTY_NEWLINE);
	else
		vty_out(vty, " queue codel interval %d%s",
			bts->llc_codel_interval_msec/10, VTY_NEWLINE);

	if (bts->alloc_algorithm == alloc_algorithm_a)
		vty_out(vty, " alloc-algorithm a%s", VTY_NEWLINE);
	if (bts->alloc_algorithm == alloc_algorithm_b)
		vty_out(vty, " alloc-algorithm b%s", VTY_NEWLINE);
	if (bts->alloc_algorithm == alloc_algorithm_dynamic)
		vty_out(vty, " alloc-algorithm dynamic%s", VTY_NEWLINE);
	if (bts->force_two_phase)
		vty_out(vty, " two-phase-access%s", VTY_NEWLINE);
	vty_out(vty, " alpha %d%s", bts->alpha, VTY_NEWLINE);
	vty_out(vty, " gamma %d%s", bts->gamma * 2, VTY_NEWLINE);
	if (bts->dl_tbf_idle_msec)
		vty_out(vty, " dl-tbf-idle-time %d%s", bts->dl_tbf_idle_msec,
			VTY_NEWLINE);
	if (strcmp(bts->pcu_sock_path, PCU_SOCK_DEFAULT))
		vty_out(vty, " pcu-socket %s%s", bts->pcu_sock_path, VTY_NEWLINE);

	for (i = 0; i < 32; i++) {
		unsigned int cs = (1 << i);
		if (bts->gsmtap_categ_mask & cs) {
			vty_out(vty, " gsmtap-category %s%s",
				get_value_string(pcu_gsmtap_categ_names, i), VTY_NEWLINE);
		}
	}

	if (bts->gb_dialect_sns)
		vty_out(vty, " gb-dialect ip-sns%s", VTY_NEWLINE);
	else
		vty_out(vty, " gb-dialect classic%s", VTY_NEWLINE);

	return CMD_SUCCESS;
}

/* per-BTS configuration */
DEFUN(cfg_pcu,
      cfg_pcu_cmd,
      "pcu",
      "BTS specific configure")
{
	vty->node = PCU_NODE;

	return CMD_SUCCESS;
}

#define EGPRS_STR "EGPRS configuration\n"

DEFUN(cfg_pcu_egprs,
      cfg_pcu_egprs_cmd,
      "egprs only",
      EGPRS_STR "Use EGPRS and disable plain GPRS\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->egprs_enabled = 1;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_egprs,
      cfg_pcu_no_egprs_cmd,
      "no egprs",
      NO_STR EGPRS_STR)
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->egprs_enabled = 0;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_fc_interval,
      cfg_pcu_fc_interval_cmd,
      "flow-control-interval <1-10>",
      "Interval between sending subsequent Flow Control PDUs\n"
      "Interval time in seconds\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->fc_interval = atoi(argv[0]);

	return CMD_SUCCESS;
}
#define FC_STR "BSSGP Flow Control configuration\n"
#define FC_BMAX_STR(who) "Force a fixed value for the " who " bucket size\n"
#define FC_LR_STR(who) "Force a fixed value for the " who " leak rate\n"

DEFUN(cfg_pcu_fc_bvc_bucket_size,
      cfg_pcu_fc_bvc_bucket_size_cmd,
      "flow-control force-bvc-bucket-size <1-6553500>",
      FC_STR FC_BMAX_STR("BVC") "Bucket size in octets\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->fc_bvc_bucket_size = atoi(argv[0]);

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_fc_bvc_bucket_size,
      cfg_pcu_no_fc_bvc_bucket_size_cmd,
      "no flow-control force-bvc-bucket-size",
      NO_STR FC_STR FC_BMAX_STR("BVC"))
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->fc_bvc_bucket_size = 0;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_fc_bvc_leak_rate,
      cfg_pcu_fc_bvc_leak_rate_cmd,
      "flow-control force-bvc-leak-rate <1-6553500>",
      FC_STR FC_LR_STR("BVC") "Leak rate in bit/s\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->fc_bvc_leak_rate = atoi(argv[0]);

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_fc_bvc_leak_rate,
      cfg_pcu_no_fc_bvc_leak_rate_cmd,
      "no flow-control force-bvc-leak-rate",
      NO_STR FC_STR FC_LR_STR("BVC"))
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->fc_bvc_leak_rate = 0;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_fc_ms_bucket_size,
      cfg_pcu_fc_ms_bucket_size_cmd,
      "flow-control force-ms-bucket-size <1-6553500>",
      FC_STR FC_BMAX_STR("default MS") "Bucket size in octets\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->fc_ms_bucket_size = atoi(argv[0]);

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_fc_ms_bucket_size,
      cfg_pcu_no_fc_ms_bucket_size_cmd,
      "no flow-control force-ms-bucket-size",
      NO_STR FC_STR FC_BMAX_STR("default MS"))
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->fc_ms_bucket_size = 0;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_fc_ms_leak_rate,
      cfg_pcu_fc_ms_leak_rate_cmd,
      "flow-control force-ms-leak-rate <1-6553500>",
      FC_STR FC_LR_STR("default MS") "Leak rate in bit/s\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->fc_ms_leak_rate = atoi(argv[0]);

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_fc_ms_leak_rate,
      cfg_pcu_no_fc_ms_leak_rate_cmd,
      "no flow-control force-ms-leak-rate",
      NO_STR FC_STR FC_LR_STR("default MS"))
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->fc_ms_leak_rate = 0;

	return CMD_SUCCESS;
}

#define FC_BTIME_STR "Set target downlink maximum queueing time (only affects the advertised bucket size)\n"
DEFUN(cfg_pcu_fc_bucket_time,
      cfg_pcu_fc_bucket_time_cmd,
      "flow-control bucket-time <1-65534>",
      FC_STR FC_BTIME_STR "Time in centi-seconds\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->fc_bucket_time = atoi(argv[0]);

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_fc_bucket_time,
      cfg_pcu_no_fc_bucket_time_cmd,
      "no flow-control bucket-time",
      NO_STR FC_STR FC_BTIME_STR)
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->fc_bucket_time = 0;

	return CMD_SUCCESS;
}

#define CS_STR "Coding Scheme configuration\n"

DEFUN(cfg_pcu_cs,
      cfg_pcu_cs_cmd,
      "cs <1-4> [<1-4>]",
      CS_STR
      "Initial CS value to be used (overrides BTS config)\n"
      "Use a different initial CS value for the uplink")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();
	uint8_t cs = atoi(argv[0]);

	bts->force_cs = 1;
	bts->initial_cs_dl = cs;
	if (argc > 1)
		bts->initial_cs_ul = atoi(argv[1]);
	else
		bts->initial_cs_ul = cs;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_cs,
      cfg_pcu_no_cs_cmd,
      "no cs",
      NO_STR CS_STR)
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->force_cs = 0;

	return CMD_SUCCESS;
}

#define CS_MAX_STR "Set maximum values for adaptive CS selection (overrides BTS config)\n"
DEFUN(cfg_pcu_cs_max,
      cfg_pcu_cs_max_cmd,
      "cs max <1-4> [<1-4>]",
      CS_STR
      CS_MAX_STR
      "Maximum CS value to be used\n"
      "Use a different maximum CS value for the uplink")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();
	uint8_t cs = atoi(argv[0]);

	bts->max_cs_dl = cs;
	if (argc > 1)
		bts->max_cs_ul = atoi(argv[1]);
	else
		bts->max_cs_ul = cs;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_cs_max,
      cfg_pcu_no_cs_max_cmd,
      "no cs max",
      NO_STR CS_STR CS_MAX_STR)
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->max_cs_dl = 0;
	bts->max_cs_ul = 0;

	return CMD_SUCCESS;
}

#define MCS_STR "Modulation and Coding Scheme configuration (EGPRS)\n"

DEFUN(cfg_pcu_mcs,
      cfg_pcu_mcs_cmd,
      "mcs <1-9> [<1-9>]",
      MCS_STR
      "Initial MCS value to be used (default 1)\n"
      "Use a different initial MCS value for the uplink")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();
	uint8_t cs = atoi(argv[0]);

	bts->initial_mcs_dl = cs;
	if (argc > 1)
		bts->initial_mcs_ul = atoi(argv[1]);
	else
		bts->initial_mcs_ul = cs;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_mcs,
      cfg_pcu_no_mcs_cmd,
      "no mcs",
      NO_STR MCS_STR)
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->initial_mcs_dl = 1;
	bts->initial_mcs_ul = 1;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_mcs_max,
      cfg_pcu_mcs_max_cmd,
      "mcs max <1-9> [<1-9>]",
      MCS_STR
      CS_MAX_STR
      "Maximum MCS value to be used\n"
      "Use a different maximum MCS value for the uplink")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();
	uint8_t mcs = atoi(argv[0]);

	bts->max_mcs_dl = mcs;
	if (argc > 1)
		bts->max_mcs_ul = atoi(argv[1]);
	else
		bts->max_mcs_ul = mcs;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_mcs_max,
      cfg_pcu_no_mcs_max_cmd,
      "no mcs max",
      NO_STR MCS_STR CS_MAX_STR)
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->max_mcs_dl = 0;
	bts->max_mcs_ul = 0;

	return CMD_SUCCESS;
}

#define DL_STR "downlink specific configuration\n"

DEFUN(cfg_pcu_dl_arq_type,
      cfg_pcu_dl_arq_cmd,
      "egprs dl arq-type (spb|arq2)",
      EGPRS_STR DL_STR "ARQ options\n"
      "enable SPB(ARQ1) support\n"
      "enable ARQ2 support")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	if (!strcmp(argv[0], "arq2"))
		bts->dl_arq_type = 1;
	else
		bts->dl_arq_type = 0;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_window_size,
      cfg_pcu_window_size_cmd,
      "window-size <0-1024> [<0-256>]",
      "Window size configuration (b + N_PDCH * f)\n"
      "Base value (b)\n"
      "Factor for number of PDCH (f)")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();
	uint16_t b = atoi(argv[0]);

	bts->ws_base = b;
	if (argc > 1) {
		uint16_t f = atoi(argv[1]);
		bts->ws_pdch = f;
	}

	return CMD_SUCCESS;
}


#define QUEUE_STR "Packet queue options\n"
#define LIFETIME_STR "Set lifetime limit of LLC frame in centi-seconds " \
	"(overrides the value given by SGSN)\n"

DEFUN(cfg_pcu_queue_lifetime,
      cfg_pcu_queue_lifetime_cmd,
      "queue lifetime <1-65534>",
      QUEUE_STR LIFETIME_STR "Lifetime in centi-seconds")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();
	uint16_t csec = atoi(argv[0]);

	bts->force_llc_lifetime = csec;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_queue_lifetime_inf,
      cfg_pcu_queue_lifetime_inf_cmd,
      "queue lifetime infinite",
      QUEUE_STR LIFETIME_STR "Infinite lifetime")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->force_llc_lifetime = 0xffff;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_queue_lifetime,
      cfg_pcu_no_queue_lifetime_cmd,
      "no queue lifetime",
      NO_STR QUEUE_STR "Disable lifetime limit of LLC frame (use value given "
      "by SGSN)\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->force_llc_lifetime = 0;

	return CMD_SUCCESS;
}

#define QUEUE_HYSTERESIS_STR "Set lifetime hysteresis of LLC frame in centi-seconds " \
	"(continue discarding until lifetime-hysteresis is reached)\n"

DEFUN(cfg_pcu_queue_hysteresis,
      cfg_pcu_queue_hysteresis_cmd,
      "queue hysteresis <1-65535>",
      QUEUE_STR QUEUE_HYSTERESIS_STR "Hysteresis in centi-seconds")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();
	uint16_t csec = atoi(argv[0]);

	bts->llc_discard_csec = csec;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_queue_hysteresis,
      cfg_pcu_no_queue_hysteresis_cmd,
      "no queue hysteresis",
      NO_STR QUEUE_STR QUEUE_HYSTERESIS_STR)
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->llc_discard_csec = 0;

	return CMD_SUCCESS;
}

#define QUEUE_CODEL_STR "Set CoDel queue management\n"

DEFUN(cfg_pcu_queue_codel,
      cfg_pcu_queue_codel_cmd,
      "queue codel",
      QUEUE_STR QUEUE_CODEL_STR)
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->llc_codel_interval_msec = LLC_CODEL_USE_DEFAULT;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_queue_codel_interval,
      cfg_pcu_queue_codel_interval_cmd,
      "queue codel interval <1-1000>",
      QUEUE_STR QUEUE_CODEL_STR "Specify interval\n" "Interval in centi-seconds")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();
	uint16_t csec = atoi(argv[0]);

	bts->llc_codel_interval_msec = 10*csec;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_queue_codel,
      cfg_pcu_no_queue_codel_cmd,
      "no queue codel",
      NO_STR QUEUE_STR QUEUE_CODEL_STR)
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->llc_codel_interval_msec = LLC_CODEL_DISABLE;

	return CMD_SUCCESS;
}


#define QUEUE_IDLE_ACK_STR "Request an ACK after the last DL LLC frame in centi-seconds\n"

DEFUN(cfg_pcu_queue_idle_ack_delay,
      cfg_pcu_queue_idle_ack_delay_cmd,
      "queue idle-ack-delay <1-65535>",
      QUEUE_STR QUEUE_IDLE_ACK_STR "Idle ACK delay in centi-seconds")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();
	uint16_t csec = atoi(argv[0]);

	bts->llc_idle_ack_csec = csec;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_queue_idle_ack_delay,
      cfg_pcu_no_queue_idle_ack_delay_cmd,
      "no queue idle-ack-delay",
      NO_STR QUEUE_STR QUEUE_IDLE_ACK_STR)
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->llc_idle_ack_csec = 0;

	return CMD_SUCCESS;
}


DEFUN(cfg_pcu_alloc,
      cfg_pcu_alloc_cmd,
      "alloc-algorithm (a|b|dynamic)",
      "Select slot allocation algorithm to use when assigning timeslots on "
      "PACCH\n"
      "Single slot is assigned only\n"
      "Multiple slots are assigned for semi-duplex operation\n"
      "Dynamically select the algorithm based on the system state\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	switch (argv[0][0]) {
	case 'a':
		bts->alloc_algorithm = alloc_algorithm_a;
		break;
	case 'b':
		bts->alloc_algorithm = alloc_algorithm_b;
		break;
	default:
		bts->alloc_algorithm = alloc_algorithm_dynamic;
		break;
	}

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_two_phase,
      cfg_pcu_two_phase_cmd,
      "two-phase-access",
      "Force two phase access when MS requests single phase access\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->force_two_phase = 1;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_two_phase,
      cfg_pcu_no_two_phase_cmd,
      "no two-phase-access",
      NO_STR "Only use two phase access when requested my MS\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->force_two_phase = 0;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_alpha,
      cfg_pcu_alpha_cmd,
      "alpha <0-10>",
      "Alpha parameter for MS power control in units of 0.1 (see TS 05.08) "
      "NOTE: Be sure to set Alpha value at System information 13 too.\n"
      "Alpha in units of 0.1\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->alpha = atoi(argv[0]);

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_gamma,
      cfg_pcu_gamma_cmd,
      "gamma <0-62>",
      "Gamma parameter for MS power control in units of dB (see TS 05.08)\n"
      "Gamma in even unit of dBs\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->gamma = atoi(argv[0]) / 2;

	return CMD_SUCCESS;
}

DEFUN(show_bts_stats,
      show_bts_stats_cmd,
      "show bts statistics",
      SHOW_STR "BTS related functionality\nStatistics\n")
{
	vty_out_rate_ctr_group(vty, "", bts_main_data_stats());
	return CMD_SUCCESS;
}

#define IDLE_TIME_STR "keep an idle DL TBF alive for the time given\n"
DEFUN(cfg_pcu_dl_tbf_idle_time,
      cfg_pcu_dl_tbf_idle_time_cmd,
      "dl-tbf-idle-time <1-5000>",
      IDLE_TIME_STR "idle time in msec")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->dl_tbf_idle_msec = atoi(argv[0]);

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_dl_tbf_idle_time,
      cfg_pcu_no_dl_tbf_idle_time_cmd,
      "no dl-tbf-idle-time",
      NO_STR IDLE_TIME_STR)
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->dl_tbf_idle_msec = 0;

	return CMD_SUCCESS;
}

#define MS_IDLE_TIME_STR "keep an idle MS object alive for the time given\n"
DEFUN(cfg_pcu_ms_idle_time,
      cfg_pcu_ms_idle_time_cmd,
      "ms-idle-time <1-7200>",
      MS_IDLE_TIME_STR "idle time in sec")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->ms_idle_sec = atoi(argv[0]);

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_ms_idle_time,
      cfg_pcu_no_ms_idle_time_cmd,
      "no ms-idle-time",
      NO_STR MS_IDLE_TIME_STR)
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->ms_idle_sec = 0;

	return CMD_SUCCESS;
}

#define CS_ERR_LIMITS_STR "set thresholds for error rate based CS adjustment\n"
DEFUN(cfg_pcu_cs_err_limits,
      cfg_pcu_cs_err_limits_cmd,
      "cs threshold <0-100> <0-100>",
      CS_STR CS_ERR_LIMITS_STR "lower limit in %\n" "upper limit in %\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	uint8_t lower_limit = atoi(argv[0]);
	uint8_t upper_limit = atoi(argv[1]);

	if (lower_limit > upper_limit) {
		vty_out(vty,
			"The lower limit must be less than or equal to the "
			"upper limit.%s", VTY_NEWLINE);
		return CMD_WARNING;
	}

	bts->cs_adj_enabled = 1;
	bts->cs_adj_upper_limit = upper_limit;
	bts->cs_adj_lower_limit = lower_limit;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_cs_err_limits,
      cfg_pcu_no_cs_err_limits_cmd,
      "no cs threshold",
      NO_STR CS_STR CS_ERR_LIMITS_STR)
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->cs_adj_enabled = 0;
	bts->cs_adj_upper_limit = 100;
	bts->cs_adj_lower_limit = 0;

	return CMD_SUCCESS;
}

#define CS_DOWNGRADE_STR "set threshold for data size based CS downgrade\n"
DEFUN(cfg_pcu_cs_downgrade_thrsh,
      cfg_pcu_cs_downgrade_thrsh_cmd,
      "cs downgrade-threshold <1-10000>",
      CS_STR CS_DOWNGRADE_STR "downgrade if less octets left\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->cs_downgrade_threshold = atoi(argv[0]);

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_no_cs_downgrade_thrsh,
      cfg_pcu_no_cs_downgrade_thrsh_cmd,
      "no cs downgrade-threshold",
      NO_STR CS_STR CS_DOWNGRADE_STR)
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->cs_downgrade_threshold = 0;

	return CMD_SUCCESS;
}


DEFUN(cfg_pcu_cs_lqual_ranges,
      cfg_pcu_cs_lqual_ranges_cmd,
      "cs link-quality-ranges cs1 <0-35> cs2 <0-35> <0-35> cs3 <0-35> <0-35> cs4 <0-35>",
      CS_STR "Set link quality ranges\n"
      "Set quality range for CS-1 (high value only)\n"
      "CS-1 high (dB)\n"
      "Set quality range for CS-2\n"
      "CS-2 low (dB)\n"
      "CS-2 high (dB)\n"
      "Set quality range for CS-3\n"
      "CS-3 low (dB)\n"
      "CS-3 high (dB)\n"
      "Set quality range for CS-4 (low value only)\n"
      "CS-4 low (dB)\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	uint8_t cs1_high = atoi(argv[0]);
	uint8_t cs2_low = atoi(argv[1]);
	uint8_t cs2_high = atoi(argv[2]);
	uint8_t cs3_low = atoi(argv[3]);
	uint8_t cs3_high = atoi(argv[4]);
	uint8_t cs4_low = atoi(argv[5]);

	bts->cs_lqual_ranges[0].high = cs1_high;
	bts->cs_lqual_ranges[1].low  = cs2_low;
	bts->cs_lqual_ranges[1].high = cs2_high;
	bts->cs_lqual_ranges[2].low  = cs3_low;
	bts->cs_lqual_ranges[2].high = cs3_high;
	bts->cs_lqual_ranges[3].low  = cs4_low;

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_mcs_lqual_ranges,
      cfg_pcu_mcs_lqual_ranges_cmd,
      "mcs link-quality-ranges mcs1 <0-35> mcs2 <0-35> <0-35> mcs3 <0-35> <0-35> mcs4 <0-35> <0-35> mcs5 <0-35> <0-35> mcs6 <0-35> <0-35> mcs7 <0-35> <0-35> mcs8 <0-35> <0-35> mcs9 <0-35>",
      CS_STR "Set link quality ranges\n"
      "Set quality range for MCS-1 (high value only)\n"
      "MCS-1 high (dB)\n"
      "Set quality range for MCS-2\n"
      "MCS-2 high (dB)\n"
      "MCS-2 low (dB)\n"
      "Set quality range for MCS-3\n"
      "MCS-3 high (dB)\n"
      "MCS-3 low (dB)\n"
      "Set quality range for MCS-4\n"
      "MCS-4 high (dB)\n"
      "MCS-4 low (dB)\n"
      "Set quality range for MCS-5\n"
      "MCS-5 high (dB)\n"
      "MCS-5 low (dB)\n"
      "Set quality range for MCS-6\n"
      "MCS-6 low (dB)\n"
      "MCS-6 high (dB)\n"
      "Set quality range for MCS-7\n"
      "MCS-7 low (dB)\n"
      "MCS-7 high (dB)\n"
      "Set quality range for MCS-8\n"
      "MCS-8 low (dB)\n"
      "MCS-8 high (dB)\n"
      "Set quality range for MCS-9 (low value only)\n"
      "MCS-9 low (dB)\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	bts->mcs_lqual_ranges[0].high = atoi(argv[0]);
	bts->mcs_lqual_ranges[1].low  = atoi(argv[1]);
	bts->mcs_lqual_ranges[1].high = atoi(argv[2]);
	bts->mcs_lqual_ranges[2].low  = atoi(argv[3]);
	bts->mcs_lqual_ranges[2].high = atoi(argv[4]);
	bts->mcs_lqual_ranges[3].low  = atoi(argv[5]);
	bts->mcs_lqual_ranges[3].high  = atoi(argv[6]);
	bts->mcs_lqual_ranges[4].low  = atoi(argv[7]);
	bts->mcs_lqual_ranges[4].high  = atoi(argv[8]);
	bts->mcs_lqual_ranges[5].low  = atoi(argv[9]);
	bts->mcs_lqual_ranges[5].high  = atoi(argv[10]);
	bts->mcs_lqual_ranges[6].low  = atoi(argv[11]);
	bts->mcs_lqual_ranges[6].high  = atoi(argv[12]);
	bts->mcs_lqual_ranges[7].low  = atoi(argv[13]);
	bts->mcs_lqual_ranges[7].high  = atoi(argv[14]);
	bts->mcs_lqual_ranges[8].low  = atoi(argv[15]);

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_sock,
      cfg_pcu_sock_cmd,
      "pcu-socket PATH",
      "Configure the osmo-bts PCU socket file/path name\n"
      "Path of the socket to connect to\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	if (bts->pcu_sock_path) {
		/* FIXME: close the interface? */
		talloc_free(bts->pcu_sock_path);
	}
	bts->pcu_sock_path = talloc_strdup(tall_pcu_ctx, argv[0]);
	/* FIXME: re-open the interface? */

	return CMD_SUCCESS;
}

DEFUN(cfg_pcu_gb_dialect,
      cfg_pcu_gb_dialect_cmd,
      "gb-dialect (classic|ip-sns)",
      "Select which Gb interface dialect to use\n"
      "Classic Gb interface with NS-{RESET,BLOCK,UNBLOCK} and static configuration\n"
      "Modern Gb interface with IP-SNS (Sub Network Service) and dynamic configuration\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();

	if (!strcmp(argv[0], "ip-sns"))
		bts->gb_dialect_sns = true;
	else
		bts->gb_dialect_sns = false;

	return CMD_SUCCESS;
}

DEFUN(show_tbf,
      show_tbf_cmd,
      "show tbf all",
      SHOW_STR "information about TBFs\n" "All TBFs\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();
	return pcu_vty_show_tbf_all(vty, bts);
}

DEFUN(show_ms_all,
      show_ms_all_cmd,
      "show ms all",
      SHOW_STR "information about MSs\n" "All TBFs\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();
	return pcu_vty_show_ms_all(vty, bts);
}

DEFUN(show_ms_tlli,
      show_ms_tlli_cmd,
      "show ms tlli TLLI",
      SHOW_STR "information about MSs\n" "Select MS by TLLI\n" "TLLI as hex\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();
	char *endp = NULL;
	unsigned long long tlli = strtoll(argv[0], &endp, 16);
	if ((endp != NULL && *endp != 0) || tlli > 0xffffffffULL) {
		vty_out(vty, "Invalid TLLI.%s", VTY_NEWLINE);
		return CMD_WARNING;
	}
	return pcu_vty_show_ms_by_tlli(vty, bts, (uint32_t)tlli);
}

DEFUN(show_ms_imsi,
      show_ms_imsi_cmd,
      "show ms imsi IMSI",
      SHOW_STR "information about MSs\n" "Select MS by IMSI\n" "IMSI\n")
{
	struct gprs_rlcmac_bts *bts = bts_main_data();
	return pcu_vty_show_ms_by_imsi(vty, bts, argv[0]);
}

static const char pcu_copyright[] =
	"Copyright (C) 2012 by Ivan Kluchnikov <kluchnikovi@gmail.com> and \r\n"
	"                      Andreas Eversberg <jolly@eversberg.eu>\r\n"
	"License GNU GPL version 2 or later\r\n"
	"This is free software: you are free to change and redistribute it.\r\n"
	"There is NO WARRANTY, to the extent permitted by law.\r\n";

struct vty_app_info pcu_vty_info = {
	.name		= "Osmo-PCU",
	.version	= PACKAGE_VERSION,
	.copyright	= pcu_copyright,
	.go_parent_cb	= pcu_vty_go_parent,
	.is_config_node	= pcu_vty_is_config_node,
};

int pcu_vty_init(const struct log_info *cat)
{
//	install_element_ve(&show_pcu_cmd);

	cfg_pcu_gsmtap_categ_cmd.string = vty_cmd_string_from_valstr(tall_pcu_ctx, pcu_gsmtap_categ_names,
						"gsmtap-category (",
						"|",")", VTY_DO_LOWER);
	cfg_pcu_gsmtap_categ_cmd.doc = vty_cmd_string_from_valstr(tall_pcu_ctx, pcu_gsmtap_categ_help,
						"GSMTAP Category\n",
						"\n", "", 0);
	cfg_pcu_no_gsmtap_categ_cmd.string = vty_cmd_string_from_valstr(tall_pcu_ctx, pcu_gsmtap_categ_names,
						"no gsmtap-category (",
						"|",")", VTY_DO_LOWER);
	cfg_pcu_no_gsmtap_categ_cmd.doc = vty_cmd_string_from_valstr(tall_pcu_ctx, pcu_gsmtap_categ_help,
						NO_STR "GSMTAP Category\n",
						"\n", "", 0);

	logging_vty_add_cmds(cat);
	osmo_stats_vty_add_cmds(cat);

	install_node(&pcu_node, config_write_pcu);
	install_element(CONFIG_NODE, &cfg_pcu_cmd);
	install_element(PCU_NODE, &cfg_pcu_egprs_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_egprs_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_two_phase_cmd);
	install_element(PCU_NODE, &cfg_pcu_cs_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_cs_cmd);
	install_element(PCU_NODE, &cfg_pcu_cs_max_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_cs_max_cmd);
	install_element(PCU_NODE, &cfg_pcu_cs_err_limits_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_cs_err_limits_cmd);
	install_element(PCU_NODE, &cfg_pcu_cs_downgrade_thrsh_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_cs_downgrade_thrsh_cmd);
	install_element(PCU_NODE, &cfg_pcu_cs_lqual_ranges_cmd);
	install_element(PCU_NODE, &cfg_pcu_mcs_lqual_ranges_cmd);
	install_element(PCU_NODE, &cfg_pcu_mcs_cmd);
	install_element(PCU_NODE, &cfg_pcu_dl_arq_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_mcs_cmd);
	install_element(PCU_NODE, &cfg_pcu_mcs_max_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_mcs_max_cmd);
	install_element(PCU_NODE, &cfg_pcu_window_size_cmd);
	install_element(PCU_NODE, &cfg_pcu_queue_lifetime_cmd);
	install_element(PCU_NODE, &cfg_pcu_queue_lifetime_inf_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_queue_lifetime_cmd);
	install_element(PCU_NODE, &cfg_pcu_queue_hysteresis_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_queue_hysteresis_cmd);
	install_element(PCU_NODE, &cfg_pcu_queue_codel_cmd);
	install_element(PCU_NODE, &cfg_pcu_queue_codel_interval_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_queue_codel_cmd);
	install_element(PCU_NODE, &cfg_pcu_queue_idle_ack_delay_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_queue_idle_ack_delay_cmd);
	install_element(PCU_NODE, &cfg_pcu_alloc_cmd);
	install_element(PCU_NODE, &cfg_pcu_two_phase_cmd);
	install_element(PCU_NODE, &cfg_pcu_fc_interval_cmd);
	install_element(PCU_NODE, &cfg_pcu_fc_bucket_time_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_fc_bucket_time_cmd);
	install_element(PCU_NODE, &cfg_pcu_fc_bvc_bucket_size_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_fc_bvc_bucket_size_cmd);
	install_element(PCU_NODE, &cfg_pcu_fc_bvc_leak_rate_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_fc_bvc_leak_rate_cmd);
	install_element(PCU_NODE, &cfg_pcu_fc_ms_bucket_size_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_fc_ms_bucket_size_cmd);
	install_element(PCU_NODE, &cfg_pcu_fc_ms_leak_rate_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_fc_ms_leak_rate_cmd);
	install_element(PCU_NODE, &cfg_pcu_alpha_cmd);
	install_element(PCU_NODE, &cfg_pcu_gamma_cmd);
	install_element(PCU_NODE, &cfg_pcu_dl_tbf_idle_time_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_dl_tbf_idle_time_cmd);
	install_element(PCU_NODE, &cfg_pcu_ms_idle_time_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_ms_idle_time_cmd);
	install_element(PCU_NODE, &cfg_pcu_gsmtap_categ_cmd);
	install_element(PCU_NODE, &cfg_pcu_no_gsmtap_categ_cmd);
	install_element(PCU_NODE, &cfg_pcu_sock_cmd);
	install_element(PCU_NODE, &cfg_pcu_gb_dialect_cmd);

	install_element_ve(&show_bts_stats_cmd);
	install_element_ve(&show_tbf_cmd);
	install_element_ve(&show_ms_all_cmd);
	install_element_ve(&show_ms_tlli_cmd);
	install_element_ve(&show_ms_imsi_cmd);

	return 0;
}
