#ifndef _GSM322_H
#define _GSM322_H

#include <osmocom/core/linuxlist.h>
#include <osmocom/core/timer.h>
#include <osmocom/gsm/gsm23003.h>

#include <osmocom/bb/common/sysinfo.h>

/* 4.3.1.1 List of states for PLMN selection process (automatic mode) */
#define GSM322_A0_NULL			0
#define	GSM322_A1_TRYING_RPLMN		1
#define	GSM322_A2_ON_PLMN		2
#define	GSM322_A3_TRYING_PLMN		3
#define	GSM322_A4_WAIT_FOR_PLMN		4
#define	GSM322_A5_HPLMN_SEARCH		5
#define	GSM322_A6_NO_SIM		6

/* 4.3.1.2 List of states for PLMN selection process (manual mode) */
#define GSM322_M0_NULL			0
#define	GSM322_M1_TRYING_RPLMN		1
#define	GSM322_M2_ON_PLMN		2
#define	GSM322_M3_NOT_ON_PLMN		3
#define	GSM322_M4_TRYING_PLMN		4
#define	GSM322_M5_NO_SIM		5

/* 4.3.2 List of states for cell selection process */
#define GSM322_C0_NULL			0
#define	GSM322_C1_NORMAL_CELL_SEL	1
#define	GSM322_C2_STORED_CELL_SEL	2
#define	GSM322_C3_CAMPED_NORMALLY	3
#define	GSM322_C4_NORMAL_CELL_RESEL	4
#define	GSM322_C5_CHOOSE_CELL		5
#define	GSM322_C6_ANY_CELL_SEL		6
#define	GSM322_C7_CAMPED_ANY_CELL	7
#define	GSM322_C8_ANY_CELL_RESEL	8
#define	GSM322_C9_CHOOSE_ANY_CELL	9
#define GSM322_CONNECTED_MODE_1		10
#define GSM322_CONNECTED_MODE_2		11
#define GSM322_PLMN_SEARCH		12
#define GSM322_HPLMN_SEARCH		13
#define GSM322_ANY_SEARCH		14

/* GSM 03.22 events */
#define	GSM322_EVENT_SWITCH_ON		1
#define	GSM322_EVENT_SWITCH_OFF		2
#define	GSM322_EVENT_SIM_INSERT		3
#define	GSM322_EVENT_SIM_REMOVE		4
#define	GSM322_EVENT_REG_SUCCESS	5
#define	GSM322_EVENT_REG_FAILED		6
#define	GSM322_EVENT_ROAMING_NA		7
#define	GSM322_EVENT_INVALID_SIM	8
#define	GSM322_EVENT_NEW_PLMN		9
#define	GSM322_EVENT_ON_PLMN		10
#define	GSM322_EVENT_PLMN_SEARCH_START	11
#define	GSM322_EVENT_PLMN_SEARCH_END	12
#define	GSM322_EVENT_USER_RESEL		13
#define	GSM322_EVENT_PLMN_AVAIL		14
#define	GSM322_EVENT_CHOOSE_PLMN	15
#define	GSM322_EVENT_SEL_MANUAL		16
#define	GSM322_EVENT_SEL_AUTO		17
#define	GSM322_EVENT_CELL_FOUND		18
#define	GSM322_EVENT_NO_CELL_FOUND	19
#define	GSM322_EVENT_LEAVE_IDLE		20
#define	GSM322_EVENT_RET_IDLE		21
#define	GSM322_EVENT_CELL_RESEL		22
#define	GSM322_EVENT_SYSINFO		23
#define	GSM322_EVENT_HPLMN_SEARCH	24

enum {
	PLMN_MODE_MANUAL,
	PLMN_MODE_AUTO
};

/* node for each PLMN */
struct gsm322_plmn_list {
	struct llist_head	entry;
	struct osmo_plmn_id	plmn;
	uint8_t			rxlev; /* rx level in range format */
	uint8_t			cause; /* cause value, if PLMN is not allowed */
};

/* node for each forbidden LA */
struct gsm322_la_list {
	struct llist_head	entry;
	struct osmo_location_area_id lai;
	uint8_t			cause;
};

/* node for each BA-List */
struct gsm322_ba_list {
	struct llist_head	entry;
	struct osmo_plmn_id	plmn;
	/* Band allocation for 1024+299 frequencies.
	 * First bit of first index is frequency 0.
	 */
	uint8_t			freq[128+38];
};

#define GSM322_CS_FLAG_SUPPORT	0x01 /* frequency is supported by radio */
#define GSM322_CS_FLAG_BA	0x02 /* frequency is part of the current ba */
#define GSM322_CS_FLAG_POWER	0x04 /* frequency was power scanned */
#define GSM322_CS_FLAG_SIGNAL	0x08 /* valid signal detected */
#define GSM322_CS_FLAG_SYSINFO	0x10 /* complete sysinfo received */
#define GSM322_CS_FLAG_BARRED	0x20 /* cell is barred */
#define GSM322_CS_FLAG_FORBIDD	0x40 /* cell in list of forbidden LAs */
#define GSM322_CS_FLAG_TEMP_AA	0x80 /* if temporary available and allowable */

/* Cell selection list */
struct gsm322_cs_list {
	uint8_t			flags; /* see GSM322_CS_FLAG_* */
	uint8_t			rxlev; /* rx level range format */
	struct gsm48_sysinfo	*sysinfo;
};

/* PLMN search process */
struct gsm322_plmn {
	struct osmocom_ms	*ms;
	int			state; /* GSM322_Ax_* or GSM322_Mx_* */

	struct llist_head	event_queue; /* event messages */
	struct llist_head	sorted_plmn; /* list of sorted PLMN */
	struct llist_head	forbidden_la; /* forbidden LAs */

	struct osmo_timer_list	timer;

	int			plmn_curr; /* current index in sorted_plmn */
	struct osmo_plmn_id	plmn; /* current network selected */
};

/* state of CCCH activation */
#define GSM322_CCCH_ST_IDLE	0	/* no connection */
#define GSM322_CCCH_ST_INIT	1	/* initialized */
#define GSM322_CCCH_ST_SYNC	2	/* got sync */
#define GSM322_CCCH_ST_DATA	3	/* receiving data */

/* neighbour cell info list entry */
struct gsm322_neighbour {
	struct llist_head	entry;
	struct gsm322_cellsel	*cs;
	uint16_t		arfcn; /* ARFCN identity of that neighbour */

	uint8_t			state; /* GSM322_NB_* */
	time_t			created; /* when was this neighbour created */
	time_t			when; /* when did we sync / read */
	int16_t			rxlev_sum_dbm; /* sum of received levels */
	uint8_t			rxlev_count; /* number of received levels */
	int8_t			rla_c_dbm; /* average of the receive level */
	uint8_t			c12_valid; /* both C1 and C2 are calculated */
	int16_t			c1, c2, crh;
	uint8_t			checked_for_resel;
	uint8_t			suitable_allowable;
	uint8_t			prio_low;
};

#define GSM322_NB_NEW		0	/* new NB instance */
#define GSM322_NB_NOT_SUP	1	/* ARFCN not supported */
#define GSM322_NB_RLA_C		2	/* valid measurement available */
#define GSM322_NB_NO_SYNC	3	/* cannot sync to neighbour */
#define GSM322_NB_NO_BCCH	4	/* sync */
#define GSM322_NB_SYSINFO	5	/* sysinfo */

struct gsm48_sysinfo;
/* Cell selection process */
struct gsm322_cellsel {
	struct osmocom_ms	*ms;
	int			state; /* GSM322_Cx_* */

	struct llist_head	event_queue; /* event messages */
	struct llist_head	ba_list; /* BCCH Allocation per PLMN */
	struct gsm322_cs_list	list[1024+299];
					/* cell selection list per frequency. */
	/* scan and tune state */
	struct osmo_timer_list	timer; /* cell selection timer */
	struct osmo_plmn_id	plmn; /* current network to search for */
	uint8_t			powerscan; /* currently scanning for power */
	uint8_t			ccch_state; /* special state of current ccch */
	uint32_t		scan_state; /* special state of current scan */
	uint16_t		arfcn; /* current tuned idle mode arfcn */
	int			arfci; /* list index of frequency above */
	uint8_t			ccch_mode; /* curren CCCH_MODE_* */
	uint8_t			sync_retries; /* number retries to sync */
	uint8_t			sync_pending; /* to prevent double sync req. */
	struct gsm48_sysinfo	*si; /* current sysinfo of tuned cell */
	uint8_t			tuned; /* if a cell is selected */
	struct osmo_timer_list	any_timer; /* restart search 'any cell' */

	/* serving cell */
	uint8_t			selected; /* if a cell is selected */
	uint16_t		sel_arfcn; /* current selected serving cell! */
	struct gsm48_sysinfo	sel_si; /* copy of selected cell, will update */
	struct osmo_cell_global_id sel_cgi;

	/* cell re-selection */
	struct llist_head	nb_list; /* list of neighbour cells */
	uint16_t		last_serving_arfcn; /* the ARFCN of last cell */
	uint8_t			last_serving_valid; /* there is a last cell */
	struct gsm322_neighbour	*neighbour; /* when selecting neighbour cell */
	time_t			resel_when; /* timestamp of last re-selection */
	int8_t			nb_meas_set;
	int16_t			rxlev_sum_dbm; /* sum of received levels */
	uint8_t			rxlev_count; /* number of received levels */
	int8_t			rla_c_dbm; /* average of received level */
	uint8_t			c12_valid; /* both C1 and C2 values are
						calculated */
	int16_t			c1, c2;
	uint8_t			prio_low;
};

/* GSM 03.22 message */
struct gsm322_msg {
	int			msg_type;
	struct osmo_plmn_id	plmn;
	uint8_t			sysinfo; /* system information type */
	uint8_t			same_cell; /* select same cell when RET_IDLE */
	uint8_t			reject; /* location update reject cause */
	uint8_t			limited; /* trigger search for limited serv. */
};

#define	GSM322_ALLOC_SIZE	sizeof(struct gsm322_msg)
#define GSM322_ALLOC_HEADROOM	0

uint16_t index2arfcn(int index);
int arfcn2index(uint16_t arfcn);
int gsm322_init(struct osmocom_ms *ms);
int gsm322_exit(struct osmocom_ms *ms);
struct msgb *gsm322_msgb_alloc(int msg_type);
int gsm322_plmn_sendmsg(struct osmocom_ms *ms, struct msgb *msg);
int gsm322_cs_sendmsg(struct osmocom_ms *ms, struct msgb *msg);
int gsm322_c_event(struct osmocom_ms *ms, struct msgb *msg);
int gsm322_plmn_dequeue(struct osmocom_ms *ms);
int gsm322_cs_dequeue(struct osmocom_ms *ms);
int gsm322_add_forbidden_la(struct osmocom_ms *ms, const struct osmo_location_area_id *lai, uint8_t cause);
int gsm322_del_forbidden_la(struct osmocom_ms *ms, const struct osmo_location_area_id *lai);
int gsm322_is_forbidden_la(struct osmocom_ms *ms, const struct osmo_location_area_id *lai);
int gsm322_dump_sorted_plmn(struct osmocom_ms *ms);
int gsm322_dump_cs_list(struct gsm322_cellsel *cs, uint8_t flags,
			void (*print)(void *, const char *, ...), void *priv);
int gsm322_dump_forbidden_la(struct osmocom_ms *ms,
			void (*print)(void *, const char *, ...), void *priv);
int gsm322_dump_ba_list(struct gsm322_cellsel *cs, const struct osmo_plmn_id *plmn,
			void (*print)(void *, const char *, ...), void *priv);
int gsm322_dump_nb_list(struct gsm322_cellsel *cs,
                        void (*print)(void *, const char *, ...), void *priv);
void start_cs_timer(struct gsm322_cellsel *cs, int sec, int micro);
void start_loss_timer(struct gsm322_cellsel *cs, int sec, int micro);
const char *get_a_state_name(int value);
const char *get_m_state_name(int value);
const char *get_cs_state_name(int value);
int gsm322_l1_signal(unsigned int subsys, unsigned int signal,
		     void *handler_data, void *signal_data);

int gsm322_meas(struct osmocom_ms *ms, uint8_t rx_lev);

char *gsm_print_rxlev(uint8_t rxlev);


#endif /* _GSM322_H */
