/* Handover FSM API for intra-BSC and inter-BSC Handover. */
#pragma once

#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/handover.h>

#define LOGPHOBTS(bts, level, fmt, args...) \
	LOGP(DHODEC, level, "(BTS %u) " fmt, bts->nr, ## args)

#define LOGPHOLCHAN(lchan, level, fmt, args...) \
	LOGP(DHODEC, level, "(lchan %u.%u%u%u %s %s) (subscr %s) " fmt, \
	     lchan->ts->trx->bts->nr, \
	     lchan->ts->trx->nr, \
	     lchan->ts->nr, \
	     lchan->nr, \
	     gsm_chan_t_name(lchan->type), \
	     gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode), \
	     bsc_subscr_name(lchan->conn ? lchan->conn->bsub : NULL), \
	     ## args)

#define LOGPHOLCHANTOBTS(lchan, new_bts, level, fmt, args...) \
	LOGP(DHODEC, level, "(lchan %u.%u%u%u %s %s)->(BTS %u) (subscr %s) " fmt, \
	     lchan->ts->trx->bts->nr, \
	     lchan->ts->trx->nr, \
	     lchan->ts->nr, \
	     lchan->nr, \
	     gsm_chan_t_name(lchan->type), \
	     gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode), \
	     new_bts->nr, \
	     bsc_subscr_name(lchan->conn ? lchan->conn->bsub : NULL), \
	     ## args)

#define LOGPHOLCHANTOREMOTE(lchan, remote_cil, level, fmt, args...) \
	LOGP(DHODEC, level, "(lchan %u.%u%u%u %s %s)->(remote-BSS %s) (subscr %s) " fmt, \
	     lchan->ts->trx->bts->nr, \
	     lchan->ts->trx->nr, \
	     lchan->ts->nr, \
	     lchan->nr, \
	     gsm_chan_t_name(lchan->type), \
	     gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode), \
	     gsm0808_cell_id_list_name(remote_cil), \
	     bsc_subscr_name(lchan->conn ? lchan->conn->bsub : NULL), \
	     ## args)

#define LOGPHOCAND(candidate, level, fmt, args...) do {\
	if ((candidate)->target.bts) \
		LOGPHOLCHANTOBTS((candidate)->current.lchan, (candidate)->target.bts, level, fmt, ## args); \
	else if ((candidate)->target.cell_ids.id_list_len) \
		LOGPHOLCHANTOREMOTE((candidate)->current.lchan, &(candidate)->target.cell_ids, level, fmt, ## args); \
	} while (0)

/* Terminology:
 * Intra-Cell: stays within one BTS, this should actually be an Assignment.
 * Intra-BSC: stays within one BSC, but moves between BTSes.
 * Inter-BSC: moves between BSCs.
 * Inter-BSC Out: move away from this BSC to another one.
 * Inter-BSC In: move from another BSC to this one.
 */

enum handover_fsm_state {
	HO_ST_NOT_STARTED,

	HO_ST_WAIT_LCHAN_ACTIVE,
	HO_ST_WAIT_MGW_ENDPOINT_TO_MSC,
	HO_ST_WAIT_RR_HO_DETECT,
	HO_ST_WAIT_RR_HO_COMPLETE,
	HO_ST_WAIT_LCHAN_ESTABLISHED,

	/* The inter-BSC Outgoing Handover FSM has completely separate states, but since it makes sense for it
	 * to also live in conn->ho.fi, it should share the same event enum. From there it is merely
	 * cosmetic to just include the separate fairly trivial states in the same FSM definition.
	 * An inter-BSC Outgoing FSM is almost unnecessary. The sole reason is to wait whether the MSC
	 * indeed clears the conn, and if not to log and count a failed handover attempt. */
	HO_OUT_ST_WAIT_HO_COMMAND,
	HO_OUT_ST_WAIT_CLEAR,
};

enum handover_fsm_event {
	HO_EV_LCHAN_ACTIVE,
	HO_EV_LCHAN_ESTABLISHED,
	HO_EV_LCHAN_ERROR,
	HO_EV_MSC_MGW_OK,
	HO_EV_MSC_MGW_FAIL,
	HO_EV_RR_HO_DETECT,
	HO_EV_RR_HO_COMPLETE,
	HO_EV_RR_HO_FAIL,
	HO_EV_CONN_RELEASING,

	HO_OUT_EV_BSSMAP_HO_COMMAND,
};

struct ho_out_rx_bssmap_ho_command {
	const uint8_t *l3_info;
	const uint8_t l3_info_len;
};

/* To be sent along with the HO_EV_RR_HO_DETECT */
struct handover_rr_detect_data {
	struct msgb *msg;
	const uint8_t *access_delay;
};

int handover_request(struct handover_out_req *req);
void handover_start(struct handover_out_req *req);
void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
				 struct msgb *ho_request_msg);
void handover_end(struct gsm_subscriber_connection *conn, enum handover_result result);

const char *handover_status(struct gsm_subscriber_connection *conn);
bool handover_is_sane(struct gsm_subscriber_connection *conn, struct gsm_lchan *old_lchan,
		      struct gsm_lchan *new_lchan);
