#pragma once

#include <stdint.h>
#include <stdbool.h>
#include <osmocom/core/select.h>
#include <osmocom/core/hashtable.h>
#include <osmocom/core/isdnhdlc.h>
#include <osmocom/core/write_queue.h>
#include <osmocom/vty/command.h>

enum {
	ISDNTAP_NODE = _LAST_OSMOVTY_NODE + 1,
	LINE_NODE,
};

/* number of octets in an information field */
#define Q921_N201	260
#define Q921_ADDR_SIZE	2
#define Q921_CTRL_SIZE	2
#define Q921_FCS_SIZE	2

#define HDLC_FRAME_SIZE	(Q921_N201 + Q921_ADDR_SIZE + Q921_CTRL_SIZE + Q921_FCS_SIZE)


struct isdntap_line;
struct isdntap_driver;

enum isdntap_ts_mode {
	E1_TS_TRACE_MODE_NONE,
	E1_TS_TRACE_MODE_RAW,		/* RAW bitstream */
	E1_TS_TRACE_MODE_HDLC,		/* generic HDLC mode */
	E1_TS_TRACE_MODE_ISDN_D,	/* ISDN D-Channel (Q.921 + Q.931) */
};

/* one "complete" HDLC decoder state */
struct isdntap_hdlc_state {
	struct osmo_isdnhdlc_vars vars;
	uint8_t out[HDLC_FRAME_SIZE];
};

/* isdntap state for one timeslot */
struct isdntap_ts {
	struct isdntap_line *line;	/* back-pointer */
	uint8_t num;			/* timeslot number */
	enum isdntap_ts_mode mode;
	uint8_t gsmtap_subtype;		// GSMTAP_E1T1_LAPD
	struct {
		/* two soft-HDLC instances, one for each direction */
		struct isdntap_hdlc_state state[2];
	} hdlc;

	union {
		struct {
			struct osmo_fd rx;	/* RX-mirror file descriptor */
			struct osmo_fd tx;	/* TX-mirror file descriptor */
			int channo;		/* DAHDI channel number for this TS */
		} dahdi;
	} drvdata;

	union {
		struct {
			struct osmo_wqueue rx;
			struct osmo_wqueue tx;
		} file;
	} output;
};

/* isdntap state for one line/span */
struct isdntap_line {
	struct llist_head list;		/* member in list of all isdntap_lines */
	const char *name;		/* human-readable name */
	bool local_side_is_network;	/* are we network (true) or user (false) ? */
	struct isdntap_instance *inst;	/* back-pointer */
	void *priv; 		/* private back-pointer (e.g. to e1d line) */

	struct isdntap_ts ts[32];	/* per-timeslot state */

	/* signalling state */
	struct {
	} q921;
	struct {
		DECLARE_HASHTABLE(call_state, 8);
	} q931;

	struct rate_ctr_group *ctrs;

	const struct isdntap_driver *driver;

	union {
		struct {
			char *name;
			char *spantype;
			int spanno;
			int basechan;
			int channels;
		} dahdi;
	} drvdata;
};

struct isdntap_driver {
	const char *name;
	int (*line_open)(struct isdntap_line *line);
	void (*line_close)(struct isdntap_line *line);
	int (*ts_open)(struct isdntap_ts *ts);
	void (*ts_close)(struct isdntap_ts *ts);
};

/* drivers feed data into the isdntap core via this API */
int isdntap_ts_rx_dchan(struct isdntap_ts *ts, const uint8_t *buf, size_t len, bool is_rx);
int isdntap_ts_rx_bchan(struct isdntap_ts *ts, const uint8_t *buf, size_t len, bool is_rx);

/* DAHDI driver */
extern const struct isdntap_driver dahdi_driver;

struct isdntap_instance {
	struct llist_head lines;
	struct gsmtap_inst *gti;	/* gsmtap instance through which we log */
	struct {
		char *output_path;
		struct {
			char *remote_host;
		} gsmtap;
	} cfg;
};

struct isdntap_line *isdntap_line_find(struct isdntap_instance *itd, const char *name);
struct isdntap_line *isdntap_line_new(struct isdntap_instance *itd, const char *name);
void isdntap_line_free(struct isdntap_line *line);

void isdntap_vty_init(struct isdntap_instance *itd);

int isdntap_ts_start_bchan(struct isdntap_ts *ts, const char *call_label);
void isdntap_ts_stop_bchan(struct isdntap_ts *ts);
