// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2021 MediaTek Inc.
 */

#include "alp_rdt.h"
#include "alp_lls.h"
#include "ip_v4.h"
#include "udp.h"

#include "bits_op.h"
#include "atsc30_log.h"
#include "atsc30_utils.h"
#include "net_buf.h"

//-------------------------------------------------------------------------------------------------
//  Debug Constants and Macros
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Constants
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Macros
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Structs and Enums
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Functions Prototype
//-------------------------------------------------------------------------------------------------
static void _dump_stream_info(const alp_stream_t *const strm);
static void _dump_info_ext(const alp_stream_info_ext_t *ext_info);

//-------------------------------------------------------------------------------------------------
//  Variables
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Internal Functions
//-------------------------------------------------------------------------------------------------
static bool _udp_ip_match_with_mask(
		const network_udp_ip_t *const pkt_udpip,
		const alp_stream_t *const strm)
{
	if (((strm->src_addr_mask & strm->src_addr)
			== (strm->src_addr_mask & pkt_udpip->src_addr))
		&& ((strm->dst_addr_mask & strm->dst_addr)
			== (strm->dst_addr_mask & pkt_udpip->dst_addr))
		&& ((strm->src_port_mask & strm->src_port)
			== (strm->src_port_mask & pkt_udpip->src_port))
		&& ((strm->dst_port_mask & strm->dst_port)
			== (strm->dst_port_mask & pkt_udpip->dst_port)))
		return true;

	return false;
}

static inline bool alp_filter_exactly_match(
		const alp_stream_t *const src,
		const alp_stream_t *const dst)
{
	if ((src != NULL)
		&& (dst != NULL)
		&& (src->src_addr == dst->src_addr)
		&& (src->dst_addr == dst->dst_addr)
		&& (src->src_port == dst->src_port)
		&& (src->dst_port == dst->dst_port)
		&& (src->plp_id == dst->plp_id))
		return true;

	return false;
}

static void _dump_stream_info(const alp_stream_t *const strm)
{
	if (ATSC30_NULL_PTR_CHECK(strm))
		return;

	ALP_PARSER_LOG_DEBUG("stream info\n");

	ALP_PARSER_LOG_DEBUG(
		"src_ip: %s(mask 0x%08x)\n",
		network_inet_ntoa(strm->src_addr),
		inet_ntoh32(strm->src_addr_mask));

	ALP_PARSER_LOG_DEBUG(
		"dst_ip: %s(mask0x%08x)\n",
		network_inet_ntoa(strm->dst_addr),
		inet_ntoh32(strm->dst_addr_mask));

	ALP_PARSER_LOG_DEBUG(
		"UDP: src_port:%05d(mask 0x%04x), dst_port %05d(mask0x%04x)\n",
		inet_ntoh16(strm->src_port),
		inet_ntoh16(strm->src_port_mask),
		inet_ntoh16(strm->dst_port),
		inet_ntoh16(strm->dst_port_mask));

	ALP_PARSER_LOG_DEBUG(
		"state %d, tag 0x%08x\n",
		strm->state,
		strm->tag_id);

	ALP_PARSER_LOG_DEBUG(
		"plp(id[%d], %s match), comp(%s, cid(%d), sid(%s, val %d)\n\n",
		strm->plp_id,
		strm->ignore_plp_id ? "ignore" : "Need",
		(strm->compress_flag ? "YES" : "NO"),
		strm->context_id,
		(strm->SID_flag ? "YES" : "NO"),
		strm->SID);
}

static void _dump_info_ext(const alp_stream_info_ext_t *ext_info)
{
	if (ATSC30_NULL_PTR_CHECK(ext_info))
		return;

	ALP_PARSER_LOG_DEBUG("stream info ext\n");

	ALP_PARSER_LOG_DEBUG("single_plp_flag: %s\n",
		ext_info->single_plp_flag ? "YES" : "NO");
	ALP_PARSER_LOG_DEBUG("plp_id %d\n",
		ext_info->plp_id);

	ALP_PARSER_LOG_DEBUG("compressed: %s\n",
		ext_info->compressed ? "YES" : "NO");

	ALP_PARSER_LOG_DEBUG("sid(%s, val %d)\n",
		ext_info->sid_flag ? "YES" : "NO",
		ext_info->sid);
}

//-------------------------------------------------------------------------------------------------
//  Public Functions
//-------------------------------------------------------------------------------------------------
u8 alp_strm_mgr_find_stream_use_udpip(
		const alp_stream_management_t *const mgr,
		const network_udp_ip_t udpip,
		const alp_stream_info_ext_t ext_info)
{
	u8 strm_idx = MAX_SUPPORT_STREAM_NUMBER;
	u8 k = 0;
	u8 i = 0;
	const alp_stream_t *strms = NULL;

	if (ATSC30_NULL_PTR_CHECK(mgr))
		return MAX_SUPPORT_STREAM_NUMBER;
	if ((ext_info.plp_id >= MAX_PLP_NUM) && (!ext_info.single_plp_flag))
		return MAX_SUPPORT_STREAM_NUMBER;

	for (i = 0;
		(i < mgr->total_capacity) && (k <= mgr->valid_count);
		i++) {
		strms = &mgr->strms[i];
		if (strms->state != ALP_STRM_ENABLE)
			continue;
		k++;

		if ((!strms->ignore_plp_id)
			&& (!ext_info.single_plp_flag)
			&& (ext_info.plp_id != strms->plp_id))
			continue;

		if (!_udp_ip_match_with_mask(&udpip, strms))
			continue;

		// TODO: strms->SID_flag != ext_info.sid_flag
		if (strms->SID_flag && ext_info.sid_flag
			&& (strms->SID != ext_info.sid))
			continue;

		strm_idx = i;

		ALP_PARSER_LOG_DEBUG(
			"rohc filter match stream idx %d\n",
			strm_idx);
		_dump_stream_info(strms);
		_dump_info_ext(&ext_info);

		break;
	}

	return strm_idx;
}

u8 alp_strm_find_stream_use_cid(
		const alp_stream_management_t *const mgr,
		const u8 cid,
		const alp_stream_info_ext_t ext_info)
{
	u8 strm_idx = MAX_SUPPORT_STREAM_NUMBER;
	u8 k = 0;
	u8 i = 0;
	const alp_stream_t *strms = NULL;

	if (ATSC30_NULL_PTR_CHECK(mgr))
		return MAX_SUPPORT_STREAM_NUMBER;
	if (cid >= MAX_CTX_NUM_PER_PLP)
		return MAX_SUPPORT_STREAM_NUMBER;
	if ((ext_info.plp_id >= MAX_PLP_NUM) && (!ext_info.single_plp_flag))
		return MAX_SUPPORT_STREAM_NUMBER;

	for (i = 0;
		(i < mgr->total_capacity) && (k < mgr->valid_count);
		i++) {
		strms = &mgr->strms[i];
		if (strms->state != ALP_STRM_ENABLE)
			continue;
		k++;

		if ((!strms->ignore_plp_id)
			&& (!ext_info.single_plp_flag)
			&& (ext_info.plp_id != strms->plp_id))
			continue;

		if (!strms->compress_flag)
			continue;
		if (strms->context_id != cid)
			continue;

		// TODO: strms->SID_flag != ext_info.sid_flag
		if (strms->SID_flag
			&& ext_info.sid_flag
			&& (strms->SID != ext_info.sid))
			continue;

		strm_idx = i;

		ALP_PARSER_LOG_DEBUG(
			"rohc filter match stream idx %d\n",
			strm_idx);
		_dump_stream_info(strms);
		_dump_info_ext(&ext_info);

		break;
	}

	return strm_idx;
}

alp_stream_ID alp_strm_mgr_set_stream(
		alp_stream_management_t *const mgr,
		const alp_stream_t *const new_strm)
{
	bool already_exist = false;
	alp_stream_ID match_id = INVALID_ALP_STREAM_ID;
	alp_stream_ID i = 0;
	alp_stream_t *strm_x = NULL;

	if (ATSC30_NULL_PTR_CHECK(mgr))
		return INVALID_ALP_STREAM_ID;
	if (ATSC30_NULL_PTR_CHECK(new_strm))
		return INVALID_ALP_STREAM_ID;
	if (mgr->total_capacity > MAX_SUPPORT_STREAM_NUMBER)
		return INVALID_ALP_STREAM_ID;

	for (i = 0; i < mgr->total_capacity; i++) {
		strm_x = &mgr->strms[i];

		if (strm_x->state == ALP_STRM_INVALID) {
			if (match_id >= mgr->total_capacity)
				match_id = i;
		} else if (alp_filter_exactly_match(strm_x, new_strm)) {
			match_id = i;
			already_exist = true;

			break;
		}
	}

	if (new_strm->state == ALP_STRM_INVALID) {
		if (already_exist) {
			if (ALP_PARSER_VERIFY(match_id >= mgr->total_capacity))
				return INVALID_ALP_STREAM_ID;
			mgr->strms[match_id].state = ALP_STRM_INVALID;
			mgr->valid_count--;

			ALP_PARSER_LOG_INFO(
				"remove filter[%d]\n",
				match_id);
		}
	} else if (new_strm->state == ALP_STRM_DISABLE) {
		if (already_exist) {
			if (ALP_PARSER_VERIFY(match_id >= mgr->total_capacity))
				return INVALID_ALP_STREAM_ID;
			mgr->strms[match_id].state = ALP_STRM_DISABLE;

			ALP_PARSER_LOG_INFO(
				"disable filter[%d]\n",
				match_id);
		}
	} else if (match_id < mgr->total_capacity) {
		strm_x = &mgr->strms[match_id];

		memcpy(strm_x, new_strm, sizeof(alp_stream_t));

		// stream mask 0xff --> check matach, 0x00--> dont check match
		// strm_x->src_addr_mask = ~new_strm->src_addr_mask;
		// strm_x->dst_addr_mask = ~new_strm->dst_addr_mask;
		// strm_x->src_port_mask = ~new_strm->src_port_mask;
		// strm_x->dst_port_mask = ~new_strm->dst_port_mask;

		if (!already_exist)
			mgr->valid_count++;

		ALP_PARSER_LOG_INFO(
			"%s filter[%d]\n",
			already_exist ? "update" : "new",
			match_id);
		_dump_stream_info(strm_x);
	} else {
		ALP_PARSER_LOG_ENHANCED_WARN(
			"WARNING: no free slot for new strm_x\n");

		return INVALID_ALP_STREAM_ID;
	}

	return match_id;
}

const alp_stream_t *alp_strm_mgr_get_stream(
		const alp_stream_management_t *const mgr,
		alp_stream_ID strm_id)
{
	if (ATSC30_NULL_PTR_CHECK(mgr))
		return NULL;

	if (strm_id >= MAX_SUPPORT_STREAM_NUMBER)
		return NULL;
	else if (mgr->strms[strm_id].state == ALP_STRM_ENABLE)
		return &mgr->strms[strm_id];

	return NULL;
}

bool alp_strm_mgr_update_stream_state(
		alp_stream_management_t *const mgr,
		alp_stream_ID strm_id,
		ALP_STREAM_STATE_T state)
{
	alp_stream_t *strm_x = NULL;

	if (ATSC30_NULL_PTR_CHECK(mgr))
		return false;

	if ((strm_id >= mgr->total_capacity)
		|| (strm_id >= MAX_SUPPORT_STREAM_NUMBER))
		return true;
	else if (mgr->strms[strm_id].state == ALP_STRM_INVALID) {
		ALP_PARSER_LOG_WARN(
			"strm[%d], invalid, cannot update state to %d\n",
			strm_id, state);

		return true;
	}

	strm_x = &mgr->strms[strm_id];

	switch (state) {
	case ALP_STRM_ENABLE:
		if (strm_x->state == ALP_STRM_DISABLE) {
			ALP_PARSER_LOG_DEBUG(
				"strm[%d], disable->enable\n",
				strm_id);
			strm_x->state = ALP_STRM_ENABLE;
		}
		break;

	case ALP_STRM_DISABLE:
		if (strm_x->state == ALP_STRM_ENABLE) {
			ALP_PARSER_LOG_DEBUG(
				"strm[%d], enable->disable\n",
				strm_id);
			strm_x->state = ALP_STRM_DISABLE;
		}
		break;

	case ALP_STRM_INVALID:
		mgr->valid_count--;
		strm_x->state = ALP_STRM_INVALID;
		ALP_PARSER_LOG_DEBUG(
			"strm[%d], invalidate\n",
			strm_id);
		break;

	default:
		break;
	}

	return true;
}

u64 alp_strm_mgr_get_tag(
		const alp_stream_management_t *const mgr,
		const u8 strm_idx)
{
	u32 tag = ALP_STREAM_INVALID_TAG;

	if (ATSC30_NULL_PTR_CHECK(mgr))
		return ALP_STREAM_INVALID_TAG;

	if ((strm_idx < MAX_SUPPORT_STREAM_NUMBER)
		&& (mgr->strms[strm_idx].state == ALP_STRM_ENABLE))
		tag = mgr->strms[strm_idx].tag_id;

	return tag;
}

void alp_strm_mgr_get_udpip(
		const alp_stream_management_t *const mgr,
		network_udp_ip_t *const udpip,
		const u8 strm_idx)
{
	if (ATSC30_NULL_PTR_CHECK(mgr))
		return;
	if (ATSC30_NULL_PTR_CHECK(udpip))
		return;

	if ((strm_idx < MAX_SUPPORT_STREAM_NUMBER)
		&& (mgr->strms[strm_idx].state == ALP_STRM_ENABLE)) {
		udpip->src_addr = mgr->strms[strm_idx].src_addr;
		udpip->dst_addr = mgr->strms[strm_idx].dst_addr;
		udpip->src_port = mgr->strms[strm_idx].src_port;
		udpip->dst_port = mgr->strms[strm_idx].dst_port;
	} else
		memset(udpip, 0, sizeof(network_udp_ip_t));
}

bool alp_strm_mgr_init(alp_stream_management_t *const mgr)
{
	int i = 0;

	if (ATSC30_NULL_PTR_CHECK(mgr))
		return false;

	mgr->total_capacity = MAX_SUPPORT_STREAM_NUMBER;
	mgr->valid_count   = 0;

	for (; i < MAX_SUPPORT_STREAM_NUMBER; i++)
		mgr->strms[i].state = ALP_STRM_INVALID;

	return true;
}

bool alp_strm_mgr_get_SID(
		const alp_stream_management_t *const mgr,
		u8  *const SID,
		u8 strm_idx)
{
	bool sid_flag = false;

	if (ATSC30_NULL_PTR_CHECK(mgr))
		return false;
	if (ATSC30_NULL_PTR_CHECK(SID))
		return false;

	if ((strm_idx < MAX_SUPPORT_STREAM_NUMBER)
		&& (mgr->strms[strm_idx].state == ALP_STRM_ENABLE)) {
		*SID = mgr->strms[strm_idx].SID;
		sid_flag = mgr->strms[strm_idx].SID_flag;
	}

	return sid_flag;
}

u8 alp_strm_mgr_get_valid_count(
		const alp_stream_management_t *const mgr,
		bool ignore_plp_id,
		u8 plp_id)
{
	u8 i = 0;
	u8 valid_count = 0;

	if (ATSC30_NULL_PTR_CHECK(mgr))
		return 0;

	for (i = 0; i < MAX_SUPPORT_STREAM_NUMBER; i++)
		if (mgr->strms[i].state != ALP_STRM_INVALID) {
			if (mgr->strms[i].ignore_plp_id && ignore_plp_id)
				valid_count++;
			else if (mgr->strms[i].plp_id == ignore_plp_id)
				valid_count++;
		}

	if (ignore_plp_id)
		ALP_PARSER_LOG_DEBUG(
			"Valid count of ignore_plp_id = %d\n",
			valid_count);
	else
		ALP_PARSER_LOG_DEBUG(
			"Valid count of plp_id %d = %d\n",
			plp_id,
			valid_count);

	return valid_count;
}

void alp_strm_mgr_dump_all_strm(const alp_stream_management_t *const mgr)
{
	u8 i = 0;

	ALP_PARSER_LOG_DEBUG(
		"Stream info valid cnt(%d)\n",
		mgr->valid_count);

	for (i = 0; i < MAX_SUPPORT_STREAM_NUMBER; i++)
		if (mgr->strms[i].state != ALP_STRM_INVALID) {
			ALP_PARSER_LOG_DEBUG("^^^^^^^ UDP_IP Stream (#%d) ^^^^^^^\n", i);
			_dump_stream_info(&mgr->strms[i]);
		}
}
