/* Manage identity of neighboring BSS cells for inter-BSC handover.
 *
 * Measurement reports tell us about neighbor ARFCN and BSIC. If that ARFCN and BSIC is not managed by
 * this local BSS, we need to tell the MSC a cell identity, like CGI, LAC+CI, etc. -- hence we need a
 * mapping from ARFCN+BSIC to Cell Identifier List, which needs to be configured by the user.
 */
/* (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 *
 * All Rights Reserved
 *
 * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <errno.h>

#include <osmocom/core/linuxlist.h>
#include <osmocom/core/utils.h>
#include <osmocom/gsm/gsm0808.h>

#include <osmocom/bsc/neighbor_ident.h>

#include <osmocom/ctrl/control_cmd.h>
#include <osmocom/ctrl/control_if.h>

#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/bts.h>
#include <osmocom/bsc/debug.h>

void bts_cell_ab(struct cell_ab *arfcn_bsic, const struct gsm_bts *bts)
{
	*arfcn_bsic = (struct cell_ab){
		.arfcn = bts->c0->arfcn,
		.bsic = bts->bsic,
	};
}

/* Find the local gsm_bts pointer that a specific other BTS' neighbor config refers to. Return NULL if there is no such
 * local cell in this BSS.
 */
int resolve_local_neighbor(struct gsm_bts **local_neighbor_p, const struct gsm_bts *from_bts,
			   const struct neighbor *neighbor)
{
	struct gsm_bts *bts;
	struct gsm_bts *bts_exact = NULL;
	struct gsm_bts *bts_wildcard = NULL;
	*local_neighbor_p = NULL;

	switch (neighbor->type) {
	case NEIGHBOR_TYPE_BTS_NR:
		bts = gsm_bts_num(bsc_gsmnet, neighbor->bts_nr);
		goto check_bts;

	case NEIGHBOR_TYPE_CELL_ID:
		/* Find cell id below */
		break;

	default:
		return -ENOTSUP;
	}

	/* NEIGHBOR_TYPE_CELL_ID */
	llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
		struct gsm0808_cell_id cell_id;
		gsm_bts_cell_id(&cell_id, bts);

		if (gsm0808_cell_ids_match(&cell_id, &neighbor->cell_id.id, true)) {
			if (bts_exact) {
				LOGP(DHO, LOGL_ERROR,
				     "Neighbor config error: Multiple BTS match %s (BTS %u and BTS %u)\n",
				     gsm0808_cell_id_name_c(OTC_SELECT, &neighbor->cell_id.id),
				     bts_exact->nr, bts->nr);
				return -EINVAL;
			} else {
				bts_exact = bts;
			}
		}

		if (!bts_wildcard && gsm0808_cell_ids_match(&cell_id, &neighbor->cell_id.id, false))
			bts_wildcard = bts;
	}

	bts = (bts_exact ? : bts_wildcard);

check_bts:
	/* A cell cannot be its own neighbor */
	if (bts == from_bts) {
		LOGP(DHO, LOGL_ERROR,
		     "Neighbor config error: BTS %u -> %s: this cell is configured as its own neighbor\n",
		     from_bts->nr, neighbor_to_str_c(OTC_SELECT, neighbor));
		return -EINVAL;
	}

	if (!bts)
		return -ENOENT;

	/* Double check whether ARFCN + BSIC config matches, if present. */
	if (neighbor->cell_id.ab_present) {
		struct cell_ab cell_ab;
		bts_cell_ab(&cell_ab, bts);
		if (!cell_ab_match(&cell_ab, &neighbor->cell_id.ab, false)) {
			LOGP(DHO, LOGL_ERROR, "Neighbor config error: Local BTS %d matches %s, but not ARFCN+BSIC %s\n",
			     bts->nr, gsm0808_cell_id_name_c(OTC_SELECT, &neighbor->cell_id.id),
			     cell_ab_to_str_c(OTC_SELECT, &cell_ab));
			return -EINVAL;
		}
	}

	*local_neighbor_p = bts;
	return 0;
}

int resolve_neighbors(struct gsm_bts **local_neighbor_p, struct gsm0808_cell_id_list2 *remote_neighbors,
		      struct gsm_bts *from_bts, const struct cell_ab *target_ab, bool log_errors)
{
	struct neighbor *n;
	struct gsm_bts *local_neighbor = NULL;
	struct gsm0808_cell_id_list2 remotes = {};

	if (local_neighbor_p)
		*local_neighbor_p = NULL;
	if (remote_neighbors)
		*remote_neighbors = (struct gsm0808_cell_id_list2){ 0 };

	llist_for_each_entry(n, &from_bts->neighbors, entry) {
		struct gsm_bts *neigh_bts;
		if (resolve_local_neighbor(&neigh_bts, from_bts, n) == 0) {
			/* This neighbor entry is a local cell neighbor. Do ARFCN and BSIC match? */
			struct cell_ab ab;
			bts_cell_ab(&ab, neigh_bts);
			if (!cell_ab_match(&ab, target_ab, false))
				continue;

			/* Found a local cell neighbor that matches the target_ab */

			/* If we already found one, these are ambiguous local neighbors */
			if (local_neighbor) {
				if (log_errors)
					LOGP(DHO, LOGL_ERROR, "Neighbor config error:"
					     " Local BTS %d -> %s resolves to local neighbor BTSes %u *and* %u\n",
					     from_bts->nr, cell_ab_to_str_c(OTC_SELECT, target_ab), local_neighbor->nr,
					     neigh_bts->nr);
				return -ENOTSUP;
			}
			local_neighbor = neigh_bts;

		} else if (n->type == NEIGHBOR_TYPE_CELL_ID && n->cell_id.ab_present) {
			/* This neighbor entry is a remote-BSS neighbor. There may be multiple remote neighbors,
			 * collect those in a gsm0808_cell_id_list2 (remote_target_cells). A limitation is that all of
			 * them need to be of the same cell id type. */
			struct gsm0808_cell_id_list2 add_item;
			int rc;

			if (!cell_ab_match(&n->cell_id.ab, target_ab, false))
				continue;

			/* Convert the gsm0808_cell_id to a list, so that we can use gsm0808_cell_id_list_add(). */
			gsm0808_cell_id_to_list(&add_item, &n->cell_id.id);
			rc = gsm0808_cell_id_list_add(&remotes, &add_item);
			if (rc < 0) {
				if (log_errors)
					LOGP(DHO, LOGL_ERROR, "Neighbor config error:"
					     " Local BTS %d -> %s resolves to remote-BSS neighbor %s;"
					     " Could not store this in neighbors list %s\n",
					     from_bts->nr, cell_ab_to_str_c(OTC_SELECT, target_ab),
					     gsm0808_cell_id_name_c(OTC_SELECT, &n->cell_id.id),
					     gsm0808_cell_id_list_name_c(OTC_SELECT, &remotes));
				return rc;
			}
		}
		/* else: neighbor entry that does not resolve to anything. */
	}

	if (local_neighbor_p)
		*local_neighbor_p = local_neighbor;
	if (remote_neighbors)
		*remote_neighbors = remotes;

	if (!local_neighbor && !remotes.id_list_len)
		return -ENOENT;
	return 0;
}

int cell_ab_to_str_buf(char *buf, size_t buflen, const struct cell_ab *cell)
{
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
	OSMO_STRBUF_PRINTF(sb, "ARFCN-BSIC:%u", cell->arfcn);
	if (cell->bsic == BSIC_ANY)
		OSMO_STRBUF_PRINTF(sb, "-any");
	else {
		OSMO_STRBUF_PRINTF(sb, "-%u", cell->bsic);
		if (cell->bsic > 0x3f)
			OSMO_STRBUF_PRINTF(sb, "[ERANGE>63]");
	}
	return sb.chars_needed;
}

char *cell_ab_to_str_c(void *ctx, const struct cell_ab *cell)
{
	OSMO_NAME_C_IMPL(ctx, 64, "ERROR", cell_ab_to_str_buf, cell)
}

int neighbor_to_str_buf(char *buf, size_t buflen, const struct neighbor *n)
{
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
	switch (n->type) {
	case NEIGHBOR_TYPE_BTS_NR:
		OSMO_STRBUF_PRINTF(sb, "BTS %u", n->bts_nr);
		break;
	case NEIGHBOR_TYPE_CELL_ID:
		OSMO_STRBUF_APPEND_NOLEN(sb, gsm0808_cell_id_name_buf, &n->cell_id.id);
		if (n->cell_id.ab_present) {
			OSMO_STRBUF_PRINTF(sb, " ");
			OSMO_STRBUF_APPEND(sb, cell_ab_to_str_buf, &n->cell_id.ab);
		}
		break;
	case NEIGHBOR_TYPE_UNSET:
		OSMO_STRBUF_PRINTF(sb, "UNSET");
		break;
	default:
		OSMO_STRBUF_PRINTF(sb, "INVALID");
		break;
	}
	return sb.chars_needed;
}

char *neighbor_to_str_c(void *ctx, const struct neighbor *n)
{
	OSMO_NAME_C_IMPL(ctx, 64, "ERROR", neighbor_to_str_buf, n);
}

bool neighbor_same(const struct neighbor *a, const struct neighbor *b, bool check_cell_ab)
{
	if (a == b)
		return true;
	if (a->type != b->type)
		return false;

	switch (a->type) {
	case NEIGHBOR_TYPE_BTS_NR:
		return a->bts_nr == b->bts_nr;

	case NEIGHBOR_TYPE_CELL_ID:
		if (check_cell_ab
		    && (a->cell_id.ab_present != b->cell_id.ab_present
			|| !cell_ab_match(&a->cell_id.ab, &b->cell_id.ab, true)))
			return false;
		return gsm0808_cell_ids_match(&a->cell_id.id, &b->cell_id.id, true);
	default:
		return a->type == b->type;
	}
}

/* Return true when the entry matches the search_for requirements.
 * If exact_match is false, a BSIC_ANY entry acts as wildcard to match any search_for on that ARFCN,
 * and a BSIC_ANY in search_for likewise returns any one entry that matches the ARFCN.
 * If exact_match is true, only identical bsic values return a match.
 * Note, typically wildcard BSICs are only in entry, e.g. the user configured list, and search_for
 * contains a specific BSIC, e.g. as received from a Measurement Report. */
bool cell_ab_match(const struct cell_ab *entry,
		   const struct cell_ab *search_for,
		   bool exact_match)
{
	if (entry->arfcn != search_for->arfcn)
		return false;

	if (exact_match && entry->bsic != search_for->bsic)
		return false;

	if (entry->bsic == BSIC_ANY || search_for->bsic == BSIC_ANY)
		return true;

	return entry->bsic == search_for->bsic;
}

bool cell_ab_valid(const struct cell_ab *cell)
{
	if (cell->bsic != BSIC_ANY && cell->bsic > 0x3f)
		return false;
	return true;
}

int neighbors_check_cfg(void)
{
	/* A local neighbor can be configured by BTS number, or by a cell ID. A local neighbor can omit the ARFCN+BSIC,
	 * in which case those are taken from that local BTS config. If a local neighbor has ARFCN+BSIC configured, it
	 * must match the local cell's configuration.
	 *
	 * A remote neighbor must always be a cell ID *and* ARFCN+BSIC.
	 *
	 * Hence any cell ID with ARFCN+BSIC where the cell ID is not found among the local cells is a remote-BSS
	 * neighbor.
	 */
	struct gsm_bts *bts;
	bool ok = true;

	llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
		struct neighbor *neighbor;
		struct gsm_bts *local_neighbor;
		llist_for_each_entry(neighbor, &bts->neighbors, entry) {
			switch (neighbor->type) {

			case NEIGHBOR_TYPE_BTS_NR:
				if (!gsm_bts_num(bsc_gsmnet, neighbor->bts_nr)) {
					LOGP(DHO, LOGL_ERROR, "Neighbor Configuration Error:"
					     " BTS %u -> BTS %u: There is no BTS nr %u\n",
					     bts->nr, neighbor->bts_nr, neighbor->bts_nr);
					ok = false;
				}
				break;

			default:
				switch (resolve_local_neighbor(&local_neighbor, bts, neighbor)) {
				case 0:
					break;
				case -ENOENT:
					if (!neighbor->cell_id.ab_present) {
						LOGP(DHO, LOGL_ERROR, "Neighbor Configuration Error:"
						     " BTS %u -> %s: There is no such local neighbor\n",
						     bts->nr, neighbor_to_str_c(OTC_SELECT, neighbor));
						ok = false;
					}
					break;
				default:
					/* Error already logged in resolve_local_neighbor() */
					ok = false;
					break;
				}
				break;
			}
		}
	}

	if (!ok)
		return -EINVAL;
	return 0;
}

/* Neighbor Resolution CTRL iface */

CTRL_CMD_DEFINE_RO(neighbor_resolve_cgi_ps_from_lac_ci, "neighbor_resolve_cgi_ps_from_lac_ci");

static int gsm_bts_get_cgi_ps(const struct gsm_bts *bts, struct osmo_cell_global_id_ps *cgi_ps)
{
	if (bts->gprs.mode == BTS_GPRS_NONE)
		return -ENOTSUP;

	cgi_ps->rai.lac.plmn = bts->network->plmn;
	cgi_ps->rai.lac.lac = bts->location_area_code;
	cgi_ps->rai.rac = bts->gprs.rac;
	cgi_ps->cell_identity = bts->cell_identity;

	return 0;
}

/* Attempt resolution of cgi_ps from ARFCN+BSIC of neighbor from BTS identified by LAC+CI */
int neighbor_address_resolution(const struct gsm_network *net, const struct cell_ab *ab,
				uint16_t lac, uint16_t cell_id,
				struct osmo_cell_global_id_ps *res_cgi_ps)
{
	struct gsm_bts *bts_tmp, *bts_found = NULL;
	struct osmo_cell_global_id_ps local_cgi_ps;
	const struct osmo_cell_global_id_ps *cgi_ps = NULL;
	struct gsm_bts *local_neighbor = NULL;
	struct gsm0808_cell_id_list2 remote_neighbors = { 0 };

	hash_for_each_possible(net->bts_by_lac_ci, bts_tmp, node_by_lac_ci,
			       LAC_CI_HASHTABLE_KEY(lac, cell_id)) {
		if (bts_tmp->location_area_code != lac)
			continue;
		if (bts_tmp->cell_identity != cell_id)
			continue;
		bts_found = bts_tmp;
		break;
	}

	if (!bts_found)
		goto notfound_err;

	LOG_BTS(bts_found, DLINP, LOGL_DEBUG, "Resolving neighbor BTS %u -> %s\n", bts_found->nr,
		cell_ab_to_str_c(OTC_SELECT, ab));

	if (resolve_neighbors(&local_neighbor, &remote_neighbors, bts_found, ab, true))
		goto notfound_err;

	/* resolve_neighbors() returns either a local_neighbor or remote_neighbors.
	 * Local-BSS neighbor? */
	if (local_neighbor) {
		/* Supporting GPRS? */
		if (gsm_bts_get_cgi_ps(local_neighbor, &local_cgi_ps) >= 0)
			cgi_ps = &local_cgi_ps;
	}

	/* Remote-BSS neighbor?
	 * By spec, there can be multiple remote neighbors for a given ARFCN+BSIC, but so far osmo-bsc enforces only a
	 * single remote neighbor. */
	if (remote_neighbors.id_list_len
	    && remote_neighbors.id_discr == CELL_IDENT_WHOLE_GLOBAL_PS) {
		cgi_ps = &remote_neighbors.id_list[0].global_ps;
	}

	/* No neighbor found */
	if (!cgi_ps)
		goto notfound_err;

	*res_cgi_ps = *cgi_ps;
	return 0;

notfound_err:
	return -1;
}

static int get_neighbor_resolve_cgi_ps_from_lac_ci(struct ctrl_cmd *cmd, void *data)
{
	struct gsm_network *net = (struct gsm_network *)data;
	char *tmp = NULL, *tok, *saveptr;
	struct cell_ab ab;
	unsigned int lac, cell_id;
	struct osmo_cell_global_id_ps cgi_ps;

	if (!cmd->variable)
		goto fmt_err;

	tmp = talloc_strdup(cmd, cmd->variable);
	if (!tmp) {
		cmd->reply = "OOM";
		return CTRL_CMD_ERROR;
	}

	if (!(tok = strtok_r(tmp, ".", &saveptr)))
		goto fmt_err;
	OSMO_ASSERT(strcmp(tok, "neighbor_resolve_cgi_ps_from_lac_ci") == 0);

	if (!(tok = strtok_r(NULL, ".", &saveptr)))
		goto fmt_err;
	lac = atoi(tok);

	if (!(tok = strtok_r(NULL, ".", &saveptr)))
		goto fmt_err;
	cell_id = atoi(tok);

	if (!(tok = strtok_r(NULL, ".", &saveptr)))
		goto fmt_err;
	ab.arfcn = atoi(tok);

	if (!(tok = strtok_r(NULL, "\0", &saveptr)))
		goto fmt_err;
	ab.bsic = atoi(tok);

	if (!cell_ab_valid(&ab))
		goto fmt_err;

	if (neighbor_address_resolution(net, &ab, lac, cell_id, &cgi_ps) < 0)
		goto notfound_err;

	ctrl_cmd_reply_printf(cmd, "%s", osmo_cgi_ps_name(&cgi_ps));
	talloc_free(tmp);
	return CTRL_CMD_REPLY;

notfound_err:
	talloc_free(tmp);
	cmd->reply = talloc_strdup(cmd, "No target CGI PS found");
	return CTRL_CMD_ERROR;
fmt_err:
	talloc_free(tmp);
	cmd->reply = talloc_strdup(cmd, "The format is <src_lac>,<src_cell_id>,<dst_arfcn>,<dst_bsic>");
	return CTRL_CMD_ERROR;
}

int neighbor_ctrl_cmds_install(struct gsm_network *net)
{
	int rc;

	rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_neighbor_resolve_cgi_ps_from_lac_ci);
	return rc;
}

struct ctrl_handle *neighbor_controlif_setup(struct gsm_network *net)
{
	/* DEPRECATED: see osmobsc-usermanual.pdf, section 16.1.1 Neighbor Address Resolution Service */
	return ctrl_interface_setup_dynip2(net, net->neigh_ctrl.addr, net->neigh_ctrl.port,
					   NULL, _LAST_CTRL_NODE_NEIGHBOR);
}
