#pragma once

#include <stdint.h>
#include <stdbool.h>

#include <osmocom/core/utils.h>

#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/protocol/gsm_08_08.h>
#include <osmocom/gsm/gsm23003.h>

#include <osmocom/bsc/meas_rep.h>

/* If .present is false, use the default value defined elsewhere. If true, use .val below.
 * (A practical benefit of this is that the default initialization sets .present to false, so that even if a .val == 0
 * is a valid value, a struct containing this as member does not need to explicitly set .val = INVALID_VAL_CONSTANT.) */
struct optional_val {
	bool present;
	int val;
};

/* Maximum number of neighbor cells whose average we track */
#define MAX_NEIGH_MEAS		10
/* Maximum size of the averaging window for neighbor cells */
#define MAX_WIN_NEIGH_AVG	10
/* Maximum number of report history we store */
#define MAX_MEAS_REP		10

/* processed neighbor measurements for one cell */
struct neigh_meas_proc {
	uint16_t arfcn;
	uint8_t bsic;
	uint8_t rxlev[MAX_WIN_NEIGH_AVG];
	unsigned int rxlev_cnt;
	uint8_t last_seen_nr;
};

enum channel_rate {
	CH_RATE_SDCCH,
	CH_RATE_HALF,
	CH_RATE_FULL,
};

enum channel_rate chan_t_to_chan_rate(enum gsm_chan_t chan_t);

enum lchan_csd_mode {
	LCHAN_CSD_M_NT,
	LCHAN_CSD_M_T_1200_75,
	LCHAN_CSD_M_T_600,
	LCHAN_CSD_M_T_1200,
	LCHAN_CSD_M_T_2400,
	LCHAN_CSD_M_T_9600,
	LCHAN_CSD_M_T_14400,
	LCHAN_CSD_M_T_29000,
	LCHAN_CSD_M_T_32000,
};

struct channel_mode_and_rate {
	enum gsm48_chan_mode chan_mode;
	enum channel_rate chan_rate;
	uint16_t s15_s0;
	/* only used for GSM48_CMODE_DATA_* */
	enum lchan_csd_mode csd_mode;
};

/* Channel Request reason */
enum gsm_chreq_reason_t {
	GSM_CHREQ_REASON_EMERG,
	GSM_CHREQ_REASON_PAG,
	GSM_CHREQ_REASON_CALL,
	GSM_CHREQ_REASON_LOCATION_UPD,
	GSM_CHREQ_REASON_OTHER,
	GSM_CHREQ_REASON_PDCH,
};

static inline bool gsm_chreq_reason_is_voicecall(enum gsm_chreq_reason_t reason)
{
	return reason == GSM_CHREQ_REASON_EMERG || reason == GSM_CHREQ_REASON_CALL;
}

/* State of the SAPIs in the lchan */
enum lchan_sapi_state {
	LCHAN_SAPI_S_NONE,
	LCHAN_SAPI_S_REQ,
	LCHAN_SAPI_S_ASSIGNED,
	LCHAN_SAPI_S_REL,
	LCHAN_SAPI_S_ERROR,
};

/* is the data link established? who established it? */
#define LCHAN_SAPI_UNUSED	0
#define LCHAN_SAPI_MS		1
#define LCHAN_SAPI_NET		2

#define MAX_A5_KEY_LEN	(128/8)

struct gsm_encr {
	uint8_t alg_a5_n; /* N: 0 (A5/0), 1 (A5/1), ... 7 (A5/7) */
	uint8_t key_len;
	uint8_t key[MAX_A5_KEY_LEN];
	bool kc128_present;
	uint8_t kc128[16];
};

enum lchan_activate_for {
	ACTIVATE_FOR_NONE,
	ACTIVATE_FOR_MS_CHANNEL_REQUEST,
	ACTIVATE_FOR_ASSIGNMENT,
	ACTIVATE_FOR_HANDOVER,
	ACTIVATE_FOR_VTY,
	ACTIVATE_FOR_MODE_MODIFY_RTP,
};

extern const struct value_string lchan_activate_mode_names[];
static inline const char *lchan_activate_mode_name(enum lchan_activate_for activ_for)
{ return get_value_string(lchan_activate_mode_names, activ_for); }

enum imm_ass_time {
	IMM_ASS_TIME_POST_CHAN_ACK = 0,
	IMM_ASS_TIME_PRE_CHAN_ACK,
	IMM_ASS_TIME_PRE_TS_ACK,
};

struct lchan_activate_info {
	enum lchan_activate_for activ_for;
	/* If activ_for == ACTIVATE_FOR_MS_CHANNEL_REQUEST, the original CHREQ reason. */
	enum gsm_chreq_reason_t chreq_reason;
	struct gsm_subscriber_connection *for_conn;
	struct channel_mode_and_rate ch_mode_rate;
	struct gsm_encr encr;
	bool requires_voice_stream;
	bool wait_before_switching_rtp; /*< true = requires LCHAN_EV_READY_TO_SWITCH_RTP */
	uint16_t msc_assigned_cic;
	/* During intra-BSC handover, we keep the MGW endpoint intact and just re-route to the new lchan. This
	 * activate_info is for the new lchan, the re_use_mgw_endpoint_from_lchan points at the old lchan. */
	struct gsm_lchan *re_use_mgw_endpoint_from_lchan;
	bool ta_known;
	uint8_t ta;

	/* The TSC Set to use if 'use' is true, otherwise automatically determine the TSC Set value to use. Valid range
	 * is 1 to 4, as described in 3GPP TS 45.002. */
	struct optional_val tsc_set;
	/* The TSC to use if 'use' is true, otherwise automatically determine the TSC value to use. Valid range is 0 to
	 * 7, as described in 3GPP TS 45.002. */
	struct optional_val tsc;

	bool vamos;

	/* A copy of bts->imm_ass_time at the time where Channel Activation was requested. A change in the VTY
	 * configuration has immediate effect on the value, so make sure we don't get mixed up when it gets changed
	 * while a channel activation is in progress. */
	enum imm_ass_time imm_ass_time;
};

enum lchan_modify_for {
	MODIFY_FOR_NONE,
	MODIFY_FOR_ASSIGNMENT,
	MODIFY_FOR_VTY,
};

extern const struct value_string lchan_modify_for_names[];
static inline const char *lchan_modify_for_name(enum lchan_modify_for modify_for)
{ return get_value_string(lchan_modify_for_names, modify_for); }

struct lchan_modify_info {
	enum lchan_modify_for modify_for;
	struct channel_mode_and_rate ch_mode_rate;
	bool requires_voice_stream;
	uint16_t msc_assigned_cic;

	/* The TSC Set to use if 'use' is true, otherwise automatically determine the TSC Set value to use. Valid range
	 * is 1 to 4, as described in 3GPP TS 45.002. */
	struct optional_val tsc_set;
	/* The TSC to use if 'use' is true, otherwise automatically determine the TSC value to use. Valid range is 0 to
	 * 7, as described in 3GPP TS 45.002. */
	struct optional_val tsc;

	bool vamos;
};

/* Measurement pre-processing state */
struct gsm_power_ctrl_meas_proc_state {
	/* Number of measurements processed */
	unsigned int meas_num;
	/* Algorithm specific data */
	union {
		struct {
			/* Scaled up 100 times average value */
			int Avg100;
		} ewma;
	};
};

struct lchan_power_ctrl_state {
	/* Measurement pre-processing state (for dynamic mode) */
	struct gsm_power_ctrl_meas_proc_state rxlev_meas_proc;
	struct gsm_power_ctrl_meas_proc_state rxqual_meas_proc;
	/* Number of SACCH blocks to skip (for dynamic mode) */
	int skip_block_num;
};

struct gsm_lchan {
	/* The TS that we're part of */
	struct gsm_bts_trx_ts *ts;
	/* The logical subslot number in the TS */
	uint8_t nr;
	char *name;

	char *last_error;

	struct osmo_fsm_inst *fi;
	struct osmo_fsm_inst *fi_rtp;
	struct osmo_mgcpc_ep_ci *mgw_endpoint_ci_bts;

	struct {
		/* The request as made by the caller, see lchan_activate().
		 * lchan->activate.info is treated immutable: remains unchanged throughout the Activation.
		 * The mutable versions are below: some values need automatic adjustments, in which case they are copied
		 * from immutable lchan->activate.info.* to lchan->activate.*, for example lchan->activate.ch_mode_rate
		 * is initially a copy of lchan->activate.info.ch_mode_rate, and is possibly adjusted afterwards. */
		struct lchan_activate_info info;

		struct channel_mode_and_rate ch_mode_rate;
		struct gsm48_multi_rate_conf mr_conf_filtered;
		bool activ_ack; /*< true as soon as RSL Chan Activ Ack is received */
		bool immediate_assignment_sent;
		/*! This flag ensures that when an lchan activation has succeeded, and we have already
		 * sent ACKs like Immediate Assignment or BSSMAP Assignment Complete, and if other errors
		 * occur later, e.g. during release, that we don't send a NACK out of context. */
		bool concluded;
		enum gsm0808_cause gsm0808_error_cause;
		/* Actually used TSC Set. */
		int tsc_set;
		/* Actually used TSC. */
		uint8_t tsc;
	} activate;

	struct {
		/* The request as made by the caller, see lchan_mode_modify().
		 * lchan->modify.info is treated immutable: remains unchanged throughout the Mode Modify.
		 * The mutable versions are below: some values need automatic adjustments, in which case they are copied
		 * from immutable lchan->modify.info.* to lchan->modify.*, for example lchan->modify.ch_mode_rate
		 * is initially a copy of lchan->modify.info.ch_mode_rate, and is possibly adjusted afterwards. */
		struct lchan_modify_info info;

		struct channel_mode_and_rate ch_mode_rate;
		struct gsm48_multi_rate_conf mr_conf_filtered;
		/* Actually used TSC Set. */
		int tsc_set;
		/* Actually used TSC. */
		uint8_t tsc;
		bool concluded;
	} modify;

	struct {
		/* If an event to release the lchan comes in while still waiting for responses, just mark this
		 * flag, so that the lchan will gracefully release at the next sensible junction. */
		bool requested;
		bool do_rr_release;
		enum gsm48_rr_cause rr_cause;
		bool last_eutran_plmn_valid;
		struct osmo_plmn_id last_eutran_plmn;

		/* There is an RSL error cause of value 0, so we need a separate flag. */
		bool in_error;
		/* RSL error code, RSL_ERR_* */
		uint8_t rsl_error_cause;

		/* If a release event is being handled, ignore other ricocheting release events until that
		 * release handling has concluded. */
		bool in_release_handler;
	} release;

	/* The logical channel type */
	enum gsm_chan_t type;
	/* Power levels for MS and BTS */
	uint8_t bs_power_db;
	uint8_t ms_power;
	/* Encryption information */
	struct gsm_encr encr;

	/* Established data link layer services */
	uint8_t sapis[8];

	struct {
		uint32_t bound_ip; /*< where the BTS receives RTP */
		uint16_t bound_port;
		uint32_t connect_ip; /*< where the BTS sends RTP to (MGW) */
		uint16_t connect_port;
		uint16_t conn_id;
		uint8_t rtp_payload;
		uint8_t rtp_payload2;
		uint8_t speech_mode;

		/* info we need to postpone the AoIP
		 * assignment completed message */
		struct {
			uint8_t rr_cause;
			bool valid;
		} ass_compl;

		struct {
			bool use;
			uint8_t local_cid;
			bool remote_cid_present;
			uint8_t remote_cid;
		} osmux;
	} abis_ip;

	/* At first, the Timing Advance from the initial Channel Request. Later, the Timing Advance value received from
	 * the most recent Measurement Report. */
	uint8_t last_ta;

	/* table of neighbor cell measurements */
	struct neigh_meas_proc neigh_meas[MAX_NEIGH_MEAS];

	/* cache of last measurement reports on this lchan */
	struct gsm_meas_rep meas_rep[MAX_MEAS_REP];
	int meas_rep_idx;
	int meas_rep_count;
	uint8_t meas_rep_last_seen_nr;

	/* GSM Random Access data */
	/* TODO: don't allocate this, rather keep an "is_present" flag */
	struct gsm48_req_ref *rqd_ref;

	struct gsm_subscriber_connection *conn;

	/* After the Channel Activation ACK or RSL Mode Modify ACK is received, this reflects the actually used
	 * channel_mode_and_rate. */
	struct channel_mode_and_rate current_ch_mode_rate;
	struct gsm48_multi_rate_conf current_mr_conf;

	/* Circuit-Switched TSC Set in use, or -1 if no specific TSC Set was requested. The valid range is 1-4 as
	 * described in the spec 3GPP TS 45.002. */
	int tsc_set;
	/* Training Sequence Code in use. The valid range is 0-7 as described in the spec 3GPP TS 45.002. */
	uint8_t tsc;

	struct {
		/* Whether this lchan represents a secondary "shadow" lchan to multiplex a second MS onto a primary
		 * "normal" lchan */
		bool is_secondary;

		/* Whether this lchan is activated/modified into a mode that allows VAMOS multiplexing at this moment */
		bool enabled;
	} vamos;

	/* dBm value of interference level as reported in the most recent Resource Indication, if any for this lchan. Or
	 * INTERF_DBM_UNKNOWN if this lchan was not included in the most recent Resource Indication.
	 * The range is typically -115 to -85 dBm, here stored 1:1 as a signed integer, to ease comparison. */
	int16_t interf_dbm;
	/* Actual reported interference band index, or INTERF_BAND_UNKNOWN if this lchan was not included in the most
	 * recent Resource Indication. */
	uint8_t interf_band;
	/* MS power control state */
	struct lchan_power_ctrl_state ms_power_ctrl;
	/* Timestamps and markers to track active state duration. */
	struct timespec active_start;
	struct timespec active_stored;
};

#define GSM_LCHAN_SI(lchan, i) (void *)((lchan)->si.buf[i][0])

void lchan_init(struct gsm_lchan *lchan, struct gsm_bts_trx_ts *ts, unsigned int nr);

void lchan_update_name(struct gsm_lchan *lchan);
uint64_t gsm_lchan_active_duration_ms(const struct gsm_lchan *lchan);

static inline char *gsm_lchan_name(const struct gsm_lchan *lchan)
{
	OSMO_ASSERT(lchan);
	return lchan->name;
}

struct gsm_lchan *gsm_lchan_vamos_to_primary(const struct gsm_lchan *lchan_vamos);
struct gsm_lchan *gsm_lchan_primary_to_vamos(const struct gsm_lchan *lchan_primary);

void lchan_update_ms_power_ctrl_level(struct gsm_lchan *lchan, int ms_power_dbm);

#define LOGPLCHAN(lchan, ss, level, fmt, args...) \
	LOGP(ss, level, "%s (ss=%d,%s) (%s) " fmt, \
	     lchan ? gsm_ts_and_pchan_name(lchan->ts) : "-", \
	     lchan ? lchan->nr : 0, \
	     lchan ? gsm_chan_t_name(lchan->type) : "-", \
	     bsc_subscr_name(lchan && lchan->conn ? lchan->conn->bsub : NULL), \
	     ## args)
