/* RLC/MAC scheduler, 3GPP TS 44.060 */
/*
 * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 *
 * All Rights Reserved
 *
 * 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 <osmocom/core/linuxlist.h>
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/gsm0502.h>

#include <osmocom/gprs/rlcmac/rlcmac_private.h>
#include <osmocom/gprs/rlcmac/sched.h>
#include <osmocom/gprs/rlcmac/gre.h>
#include <osmocom/gprs/rlcmac/tbf_dl.h>
#include <osmocom/gprs/rlcmac/tbf_ul.h>
#include <osmocom/gprs/rlcmac/tbf_ul_ass_fsm.h>
#include <osmocom/gprs/rlcmac/types_private.h>
#include <osmocom/gprs/rlcmac/pdch_ul_controller.h>

struct tbf_sched_ctrl_candidates {
	struct gprs_rlcmac_dl_tbf *poll_dl_ack_final_ack; /* 8.1.2.2 1) */
	struct gprs_rlcmac_dl_tbf *poll_dl_ack; /* 8.1.2.2 7) */
	struct gprs_rlcmac_ul_tbf *poll_ul_ack_new_ul_tbf; /* 9.3.2.4.2  (answer with PKT RES REQ) */
	struct gprs_rlcmac_ul_tbf *poll_ul_ack; /* 11.2.2 (answer with PKT CTRL ACK) */
	struct gprs_rlcmac_ul_tbf *poll_ul_ass; /* (answer Pkt UL ASS with PKT CTRL ACK) */
	struct gprs_rlcmac_entity *poll_dl_ass; /* (answer Pkt DL ASS with PKT CTRL ACK) */
	struct gprs_rlcmac_ul_tbf *ul_ass;	/* PCU grants USF/SBA: transmit Pkt Res Req (2phase access)*/
};

uint32_t rrbp2fn(uint32_t cur_fn, uint8_t rrbp)
{
	uint32_t poll_fn;
	static const uint8_t rrbp_list[] = {
		13, /* GPRS_RLCMAC_RRBP_N_plus_13 */
		17, /* GPRS_RLCMAC_RRBP_N_plus_17_18 */
		21, /* GPRS_RLCMAC_RRBP_N_plus_21_22 */
		26, /* GPRS_RLCMAC_RRBP_N_plus_26 */
	};

	OSMO_ASSERT(rrbp < ARRAY_SIZE(rrbp_list));

	poll_fn = GSM_TDMA_FN_SUM(cur_fn, rrbp_list[rrbp]);
	if (!fn_valid(poll_fn)) {
		/* 17 -> 18, 21 -> 22: */
		GSM_TDMA_FN_INC(poll_fn);
		OSMO_ASSERT(fn_valid(poll_fn));
	}
	return poll_fn;
}

static void get_ctrl_msg_tbf_candidates(const struct gprs_rlcmac_rts_block_ind *bi,
					struct tbf_sched_ctrl_candidates *tbfs)
{

	struct gprs_rlcmac_entity *gre;
	struct gprs_rlcmac_dl_tbf *dl_tbf;
	struct gprs_rlcmac_ul_tbf *ul_tbf;
	struct gprs_rlcmac_pdch_ulc_node *node;

	node = gprs_rlcmac_pdch_ulc_get_node(g_rlcmac_ctx->sched.ulc[bi->ts], bi->fn);
	if (node) {
		switch (node->reason) {
		case GPRS_RLCMAC_PDCH_ULC_POLL_UL_ASS:
			/* Answer with ctrl ack generated by ul_tbf->ul_ass_fsm. */
			tbfs->poll_ul_ass = tbf_as_ul_tbf(node->tbf);
			break;
		case GPRS_RLCMAC_PDCH_ULC_POLL_DL_ASS:
			tbfs->poll_dl_ass = node->gre;
			break;
		case GPRS_RLCMAC_PDCH_ULC_POLL_UL_ACK:
			/* TS 44.060: 9.3.2.4.2 If the PACKET UPLINK ACK/NACK message
			* has the Final Ack Indicator bit set to '1' and the following
			* conditions are fulfilled: TBF Est field is set to '1'; the
			* mobile station has new data to transmit; the mobile station
			* has no other ongoing downlink TBFs, the mobile station shall
			* release the uplink TBF and may request the establishment of a
			* new TBF using one of the following procedures.
			* If Control Ack Type parameter in System Information indicates
			* acknowledgement is RLC/MAC control block, the mobile station
			* shall transmit the PACKET RESOURCE REQUEST message and start
			* timer T3168 for the TBF request. The mobile station shall use
			* the same procedures as are used for TBF establishment using two
			* phase access described in sub-clause 7.1.3 starting from the
			* point where the mobile station transmits the PACKET RESOURCE
			* REQUEST message. */
			ul_tbf = tbf_as_ul_tbf(node->tbf);
			if (gprs_rlcmac_ul_tbf_can_request_new_ul_tbf(ul_tbf))
				tbfs->poll_ul_ack_new_ul_tbf = ul_tbf;
			else
				tbfs->poll_ul_ack = ul_tbf;
			break;
		case GPRS_RLCMAC_PDCH_ULC_POLL_DL_ACK:
			dl_tbf = tbf_as_dl_tbf(node->tbf);
			/* 8.1.2.2 Polling for Packet Downlink Ack/Nack */
			if (gprs_rlcmac_tbf_dl_state(dl_tbf) == GPRS_RLCMAC_TBF_DL_ST_FINISHED)
				tbfs->poll_dl_ack_final_ack = dl_tbf;
			else
				tbfs->poll_dl_ack = dl_tbf;
			break;
		case GPRS_RLCMAC_PDCH_ULC_POLL_CELL_CHG_CONTINUE:
			/* TODO */
			break;
		}
		gprs_rlcmac_pdch_ulc_release_node(g_rlcmac_ctx->sched.ulc[bi->ts], node);
	}

	/* Iterate over UL TBFs: */
	llist_for_each_entry(gre, &g_rlcmac_ctx->gre_list, entry) {
		if (!gre->ul_tbf)
			continue;
		ul_tbf = gre->ul_tbf;
		if (gprs_rlcmac_tbf_ul_ass_rts(ul_tbf, bi))
			tbfs->ul_ass = ul_tbf;
	}

	/* TODO: Iterate over DL TBFs: */
}

static struct gprs_rlcmac_ul_tbf *find_requested_ul_tbf_for_data(const struct gprs_rlcmac_rts_block_ind *bi)
{
	struct gprs_rlcmac_entity *gre;
	llist_for_each_entry(gre, &g_rlcmac_ctx->gre_list, entry) {
		if (!gre->ul_tbf)
			continue;
		if (gprs_rlcmac_ul_tbf_data_rts(gre->ul_tbf, bi))
			return gre->ul_tbf;
	}
	return NULL;
}

static struct gprs_rlcmac_ul_tbf *find_requested_ul_tbf_for_dummy(const struct gprs_rlcmac_rts_block_ind *bi)
{
	struct gprs_rlcmac_entity *gre;
	llist_for_each_entry(gre, &g_rlcmac_ctx->gre_list, entry) {
		if (!gre->ul_tbf)
			continue;
		if (gprs_rlcmac_ul_tbf_dummy_rts(gre->ul_tbf, bi))
			return gre->ul_tbf;
	}
	return NULL;
}

/*! Select a CTRL message to transmit, based on different messages priority.
 * \param[in] bi  RTS block indication information.
 * \param[in] tbfs  TBF candidates having CTRL messages to send, filled in by get_ctrl_msg_tbf_candidates()
 * \param[out] need_block_conf  Whether we require Tx confirmation of this block from lower layers.
 */
static struct msgb *sched_select_ctrl_msg(const struct gprs_rlcmac_rts_block_ind *bi,
					  const struct tbf_sched_ctrl_candidates *tbfs,
					  bool *need_block_conf)
{
	struct msgb *msg = NULL;
	struct gprs_rlcmac_entity *gre;
	int rc;

	/* No TBF needs block conf by default: */
	*need_block_conf = false;

	/* 8.1.2.2 1) (EGPRS) PACKET DOWNLINK ACK/NACK w/ FinalAckInd=1 */
	if (tbfs->poll_dl_ack_final_ack) {
		LOGRLCMAC(LOGL_DEBUG, "(ts=%u,fn=%u,usf=%u) Tx DL ACK/NACK FinalAck=1\n",
			  bi->ts, bi->fn, bi->usf);
		msg = gprs_rlcmac_dl_tbf_create_pkt_dl_ack_nack(tbfs->poll_dl_ack_final_ack, bi);
		if (msg)
			return msg;
	}

	/* 8.1.2.2 5) Any other RLC/MAC control message, other than a (EGPRS) PACKET DOWNLINK ACK/NACK */
	if (tbfs->poll_ul_ack_new_ul_tbf) {
		LOGRLCMAC(LOGL_DEBUG, "(ts=%u,fn=%u,usf=%u) Tx Pkt Resource Request (UL ACK/NACK poll)\n",
			  bi->ts, bi->fn, bi->usf);
		gre = tbfs->poll_ul_ack_new_ul_tbf->tbf.gre;
		OSMO_ASSERT(gre->ul_tbf == tbfs->poll_ul_ack_new_ul_tbf);
		gre->ul_tbf = gprs_rlcmac_ul_tbf_alloc(gre);
		if (!gre->ul_tbf) {
			gprs_rlcmac_ul_tbf_free(tbfs->poll_ul_ack_new_ul_tbf);
			return NULL;
		}
		/* Prepare new UL TBF from old UL TBF: */
		rc = gprs_rlcmac_tbf_ul_ass_start_from_releasing_ul_tbf(gre->ul_tbf, tbfs->poll_ul_ack_new_ul_tbf);
		gprs_rlcmac_ul_tbf_free(tbfs->poll_ul_ack_new_ul_tbf); /* always free */
		if (rc < 0) {
			gprs_rlcmac_ul_tbf_free(gre->ul_tbf);
			return NULL;
		}
		/* New UL TBF is ready to send the Pkt Res Req: */
		OSMO_ASSERT(gprs_rlcmac_tbf_ul_ass_rts(gre->ul_tbf, bi));
		msg = gprs_rlcmac_tbf_ul_ass_create_rlcmac_msg(gre->ul_tbf, bi);
		if (msg)
			return msg;
	}
	if (tbfs->poll_ul_ack) {
		LOGRLCMAC(LOGL_DEBUG, "(ts=%u,fn=%u,usf=%u) Tx Pkt Control Ack (UL ACK/NACK poll)\n",
			  bi->ts, bi->fn, bi->usf);
		msg = gprs_rlcmac_gre_create_pkt_ctrl_ack(ul_tbf_as_tbf(tbfs->poll_ul_ack)->gre);
		/* Last UL message, freeing (after passing msg to lower layers) */
		return msg;
	}
	if (tbfs->poll_dl_ass) {
		LOGRLCMAC(LOGL_DEBUG, "(ts=%u,fn=%u,usf=%u) Tx Pkt Control Ack (DL ASS poll)\n",
			  bi->ts, bi->fn, bi->usf);
		msg = gprs_rlcmac_gre_create_pkt_ctrl_ack(tbfs->poll_dl_ass);
		if (msg)
			return msg;
	}
	if (tbfs->poll_ul_ass) {
		LOGRLCMAC(LOGL_DEBUG, "(ts=%u,fn=%u,usf=%u) Tx Pkt Control Ack (UL ASS poll)\n",
			  bi->ts, bi->fn, bi->usf);
		msg = gprs_rlcmac_tbf_ul_ass_create_rlcmac_msg(tbfs->poll_ul_ass, bi);
		if (msg)
			return msg;
	}
	if (tbfs->ul_ass) {
		msg = gprs_rlcmac_tbf_ul_ass_create_rlcmac_msg(tbfs->ul_ass, bi);
		if (msg)
			return msg;
	}

	/* 8.1.2.2 7) A (EGPRS) PACKET DOWNLINK ACK/NACK message not containing a Final Ack Indicator or a
	 * Channel Request Description IE */
	if (tbfs->poll_dl_ack) {
		LOGRLCMAC(LOGL_DEBUG, "(ts=%u,fn=%u,usf=%u) Tx DL ACK/NACK\n",
			  bi->ts, bi->fn, bi->usf);
		msg = gprs_rlcmac_dl_tbf_create_pkt_dl_ack_nack(tbfs->poll_dl_ack, bi);
		if (msg)
			return msg;
	}

	return NULL;
}

static struct msgb *sched_select_ul_data_msg(const struct gprs_rlcmac_rts_block_ind *bi)
{
	struct gprs_rlcmac_ul_tbf *ul_tbf;

	ul_tbf = find_requested_ul_tbf_for_data(bi);
	if (!ul_tbf) {
		LOGRLCMAC(LOGL_DEBUG, "(ts=%u,fn=%u,usf=%u) No Uplink TBF available to transmit RLC/MAC Ul Data Block\n",
			  bi->ts, bi->fn, bi->usf);
		return NULL;
	}
	return gprs_rlcmac_ul_tbf_data_create(ul_tbf, bi);
}

static struct msgb *sched_select_ul_dummy_ctrl_blk(const struct gprs_rlcmac_rts_block_ind *bi)
{
	struct gprs_rlcmac_ul_tbf *ul_tbf;

	ul_tbf = find_requested_ul_tbf_for_dummy(bi);
	if (!ul_tbf) {
		LOGRLCMAC(LOGL_DEBUG, "(ts=%u,fn=%u,usf=%u) No Uplink TBF available to transmit RLC/MAC Ul Dummy Ctrl Block\n",
			  bi->ts, bi->fn, bi->usf);
		return NULL;
	}

	return gprs_rlcmac_ul_tbf_dummy_create(ul_tbf);
}

static void rts_tick(const struct gprs_rlcmac_rts_block_ind *bi)
{
	struct gprs_rlcmac_entity *gre;

	llist_for_each_entry(gre, &g_rlcmac_ctx->gre_list, entry) {
		if (gre->ul_tbf && gprs_rlcmac_tbf_ul_ass_waiting_tbf_starting_time(gre->ul_tbf))
			gprs_rlcmac_tbf_ul_ass_fn_tick(gre->ul_tbf, bi->fn, bi->ts);
		if (gprs_rlcmac_tbf_start_pending(&gre->dl_tbf_dl_ass_fsm))
			gprs_rlcmac_tbf_start_fn_tick(&gre->dl_tbf_dl_ass_fsm, bi->fn, bi->ts);
	}
}

int gprs_rlcmac_rcv_rts_block(struct gprs_rlcmac_rts_block_ind *bi)
{
	struct msgb *msg = NULL;
	struct tbf_sched_ctrl_candidates tbf_cand = {0};
	bool need_block_conf;
	struct osmo_gprs_rlcmac_prim *rlcmac_prim_tx;
	int rc = 0;

	rts_tick(bi);

	get_ctrl_msg_tbf_candidates(bi, &tbf_cand);

	if ((msg = sched_select_ctrl_msg(bi, &tbf_cand, &need_block_conf)))
		goto tx_msg;

	if ((msg = sched_select_ul_data_msg(bi)))
		goto tx_msg;

	/* Lowest prio: send dummy control message (or nothing depending on EXT_UTBF_NODATA) */
	if ((msg = sched_select_ul_dummy_ctrl_blk(bi)))
		goto tx_msg;

	/* Nothing to transmit */
	goto ret_rc;

tx_msg:
	rlcmac_prim_tx = gprs_rlcmac_prim_alloc_l1ctl_pdch_data_req(bi->ts, bi->fn, msgb_data(msg), 0);
	rlcmac_prim_tx->l1ctl.pdch_data_req.data_len = msgb_length(msg);
	/* TODO: enable once we support conditional confirmation:
	 * rlcmac_prim_tx->l1ctl.pdch_data_req.needs_conf = need_block_conf;
	 */
	rc = gprs_rlcmac_prim_call_down_cb(rlcmac_prim_tx);
	msgb_free(msg);

ret_rc:
	gprs_rlcmac_pdch_ulc_expire_fn(g_rlcmac_ctx->sched.ulc[bi->ts], bi->fn);
	return rc;
}
