/*
 * (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 * All Rights Reserved.
 *
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 *
 * SPDX-License-Identifier: GPL-2.0+
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <osmocom/core/utils.h>
#include <osmocom/core/fsm.h>

#include <osmocom/upf/up_peer.h>

enum up_peer_fsm_state {
	UP_PEER_ST_NOT_ASSOCIATED,
	UP_PEER_ST_ASSOCIATED,
	UP_PEER_ST_GRACEFUL_RELEASE,
};

static const struct value_string up_peer_fsm_event_names[] = {
	OSMO_VALUE_STRING(UP_PEER_EV_RX_ASSOC_SETUP_REQ),
	OSMO_VALUE_STRING(UP_PEER_EV_RX_ASSOC_UPD_REQ),
	OSMO_VALUE_STRING(UP_PEER_EV_RX_SESSION_EST_REQ),
	OSMO_VALUE_STRING(UP_PEER_EV_HEARTBEAT_FAILURE),
	{}
};

static struct osmo_fsm up_peer_fsm;

static const struct osmo_tdef_state_timeout up_peer_fsm_timeouts[32] = {
	[UP_PEER_ST_GRACEFUL_RELEASE] = { .T = -21 },
};

/* Transition to a state, using the T timer defined in up_peer_fsm_timeouts.
 * Assumes local variable fi exists. */
#define up_peer_fsm_state_chg(state) \
	osmo_tdef_fsm_inst_state_chg(fi, state, \
				     up_peer_fsm_timeouts, \
				     g_upf_tdefs, \
				     5)

struct up_peer *up_peer_alloc(struct osmo_fsm_inst *parent_fi, uint32_t parent_event_term)
{
	struct up_peer *up_peer;

	struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc_child(&up_peer_fsm, parent_fi, parent_event_term);
	OSMO_ASSERT(fi);

	up_peer = talloc(fi, struct up_peer);
	OSMO_ASSERT(up_peer);
	fi->priv = up_peer;
	*up_peer = (struct up_peer){
		.fi = fi,
	};

	return up_peer;
}

static int up_peer_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
	//struct up_peer *up_peer = fi->priv;
	/* Return 1 to terminate FSM instance, 0 to keep running */
	return 1;
}

static void up_peer_not_associated_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
	//struct up_peer *up_peer = fi->priv;

	switch (event) {

	case UP_PEER_EV_RX_ASSOC_SETUP_REQ:
		// FIXME
		break;

	default:
		OSMO_ASSERT(false);
	}
}

static void up_peer_associated_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
	//struct up_peer *up_peer = fi->priv;
	// FIXME
}

static void up_peer_associated_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
	//struct up_peer *up_peer = fi->priv;

	switch (event) {

	case UP_PEER_EV_RX_ASSOC_UPD_REQ:
		// FIXME
		break;

	case UP_PEER_EV_RX_SESSION_EST_REQ:
		// FIXME
		break;

	case UP_PEER_EV_HEARTBEAT_FAILURE:
		// FIXME
		break;

	default:
		OSMO_ASSERT(false);
	}
}

static void up_peer_graceful_release_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
	//struct up_peer *up_peer = fi->priv;
	// FIXME
}

static void up_peer_graceful_release_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
	//struct up_peer *up_peer = fi->priv;

	switch (event) {

	case UP_PEER_EV_HEARTBEAT_FAILURE:
		// FIXME
		break;

	default:
		OSMO_ASSERT(false);
	}
}

#define S(x)    (1 << (x))

static const struct osmo_fsm_state up_peer_fsm_states[] = {
	[UP_PEER_ST_NOT_ASSOCIATED] = {
		.name = "not_associated",
		.in_event_mask = 0
			| S(UP_PEER_EV_RX_ASSOC_SETUP_REQ)
			,
		.out_state_mask = 0
			| S(UP_PEER_ST_ASSOCIATED)
			,
		.action = up_peer_not_associated_action,
	},
	[UP_PEER_ST_ASSOCIATED] = {
		.name = "associated",
		.in_event_mask = 0
			| S(UP_PEER_EV_RX_ASSOC_UPD_REQ)
			| S(UP_PEER_EV_RX_SESSION_EST_REQ)
			| S(UP_PEER_EV_HEARTBEAT_FAILURE)
			,
		.out_state_mask = 0
			| S(UP_PEER_ST_GRACEFUL_RELEASE)
			,
		.onenter = up_peer_associated_onenter,
		.action = up_peer_associated_action,
	},
	[UP_PEER_ST_GRACEFUL_RELEASE] = {
		.name = "graceful_release",
		.in_event_mask = 0
			| S(UP_PEER_EV_HEARTBEAT_FAILURE)
			,
		.out_state_mask = 0
			,
		.onenter = up_peer_graceful_release_onenter,
		.action = up_peer_graceful_release_action,
	},
};

static struct osmo_fsm up_peer_fsm = {
	.name = "up_peer",
	.states = up_peer_fsm_states,
	.num_states = ARRAY_SIZE(up_peer_fsm_states),
	.log_subsys = DSESSION,
	.event_names = up_peer_fsm_event_names,
	.timer_cb = up_peer_fsm_timer_cb,
};

static __attribute__((constructor)) void up_peer_fsm_register(void)
{
	OSMO_ASSERT(osmo_fsm_register(&up_peer_fsm) == 0);
}
