/*
 * octoi.c
 *
 * (C) 2022 by Harald Welte <laforge@osmocom.org>
 *
 * All Rights Reserved
 *
 * 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.
 */

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

#include "e1oip.h"
#include "octoi.h"
#include "octoi_sock.h"
#include "frame_fifo.h"

struct octoi_daemon *g_octoi;

static struct octoi_client *client4account(struct octoi_account *acc)
{
	struct octoi_client *clnt;

	llist_for_each_entry(clnt, &g_octoi->clients, list) {
		if (clnt->cfg.account == acc)
			return clnt;
	}

	return NULL;
}

int octoi_vty_go_parent(struct vty *vty)
{
	struct octoi_account *acc;
	struct octoi_client *clnt;

	switch (vty->node) {
	case OCTOI_ACCOUNT_NODE:
		vty->node = OCTOI_SRV_NODE;
		vty->index = g_octoi->server;
		break;
	case OCTOI_CLNT_ACCOUNT_NODE:
		acc = vty->index;
		vty->node = OCTOI_CLNT_NODE;
		vty->index = client4account(acc);
		break;
	case OCTOI_CLNT_NODE:
		clnt = vty->index;
		/* check if we have a (new?) line for this client */
		if (g_octoi->ops->client_updated)
			g_octoi->ops->client_updated(clnt);
		vty->node = CONFIG_NODE;
		vty->index = NULL;
		break;
	default:
		vty->node = CONFIG_NODE;
		vty->index = NULL;
		break;
	}
	return 0;
}

void octoi_init(void *ctx, void *priv, const struct octoi_ops *ops)
{
	OSMO_ASSERT(!g_octoi);

	OSMO_ASSERT(osmo_fsm_register(&octoi_server_fsm) == 0);
	OSMO_ASSERT(osmo_fsm_register(&octoi_client_fsm) == 0);

	g_octoi = talloc_zero(ctx, struct octoi_daemon);
	OSMO_ASSERT(g_octoi);

	g_octoi->priv = priv;
	g_octoi->ops = ops;
	INIT_LLIST_HEAD(&g_octoi->clients);

	octoi_server_vty_init();
	octoi_client_vty_init();
}

/* resolve the octoi_peer for a specified octoi_client */
struct octoi_peer *octoi_client_get_peer(struct octoi_client *clnt)
{
	return octoi_sock_client_get_peer(clnt->sock);
}


/*! E1 interface has received some E1 frames, forward in E1->IP direction.
 *  \param[in] peer The peer for which E1 frames were received
 *  \param[in] buf Buffer holding the just-received E1 frames
 *  \param[in] ftr Number of 32-byte frames in buf */
void octoi_peer_e1o_in(struct octoi_peer *peer, const uint8_t *buf, int ftr)
{
	struct e1oip_line *iline = peer->iline;
	int rc;

	if (!peer->tdm_permitted)
		return;

	rc = frame_fifo_in_multi(&iline->e1o.fifo, buf, ftr);
	if (rc < ftr)
		iline_ctr_add(iline, LINE_CTR_E1oIP_E1O_OVERFLOW, ftr - rc);

	iline_stat_set(iline, LINE_STAT_E1oIP_E1O_FIFO, frame_fifo_frames(&iline->e1o.fifo));
}

/*! E1 interface needs to transmit some E1 frames, E1<-IP direction.
 *  \param[in] peer The peer from which E1 frames are needed
 *  \param[in] buf Caller-provided output buffer to which frames are written.
 *  \param[in] fts Number of 32-byte frames to be written to buf. */
void octoi_peer_e1t_out(struct octoi_peer *peer, uint8_t *buf, int fts)
{
	struct e1oip_line *iline = peer->iline;
	int rc;

	if (!peer->tdm_permitted)
		return;

	if (!iline->e1t.primed_rx_tdm) {
		if (frame_rifo_frames(&iline->e1t.rifo) > iline->cfg.prefill_frame_count)
			iline->e1t.primed_rx_tdm = true;
		return;
	}

	for (int i = 0; i < fts; i++) {
		uint8_t *cur = buf + BYTES_PER_FRAME*i;
		rc = frame_rifo_out(&iline->e1t.rifo, cur);
		if (rc == -1) {
			iline_ctr_add(iline, LINE_CTR_E1oIP_SUBSTITUTED, 1);
			/* substitute with last received frame */
			memcpy(cur, iline->e1t.last_frame, BYTES_PER_FRAME);
		} else if (rc == -2) {
			iline_ctr_add(iline, LINE_CTR_E1oIP_UNDERRUN, 1);
			/* substitute with all-FF frame */
			memset(cur, 0xff, BYTES_PER_FRAME);
		}
	}
	iline_stat_set(iline, LINE_STAT_E1oIP_E1T_FIFO, frame_rifo_depth(&iline->e1t.rifo));
}