/*
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Eric Wild <ewild@sysmocom.de>
*
* SPDX-License-Identifier: 0BSD
*
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOFTWARE.
*/
extern "C" {
#include <osmocom/core/application.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>

#include "shm.h"
#include "ipc_shm.h"
#include "ipc-driver-test.h"
}
#include "../uhd/UHDDevice.h"
#include "uhdwrap.h"

#include "Logger.h"
#include "Threads.h"
#include "Utils.h"

// no vty source for cfg params here, so we have to build our own
static struct trx_cfg actual_cfg = {};

int uhd_wrap::open()
{
	int rv = uhd_device::open();
	samps_per_buff_rx = rx_stream->get_max_num_samps();
	samps_per_buff_tx = tx_stream->get_max_num_samps();
	channel_count = usrp_dev->get_rx_num_channels();

	wrap_rx_buffs = std::vector<std::vector<short> >(channel_count, std::vector<short>(2 * samps_per_buff_rx));
	for (size_t i = 0; i < wrap_rx_buffs.size(); i++)
		wrap_rx_buf_ptrs.push_back(&wrap_rx_buffs[i].front());

	wrap_tx_buffs = std::vector<std::vector<short> >(channel_count, std::vector<short>(2 * 5000));
	for (size_t i = 0; i < wrap_tx_buffs.size(); i++)
		wrap_tx_buf_ptrs.push_back(&wrap_tx_buffs[i].front());

	return rv;
}

uhd_wrap::~uhd_wrap()
{
	//	drvtest::gshutdown = 1;
	//t->join();
}

size_t uhd_wrap::bufsizerx()
{
	return samps_per_buff_rx;
}

size_t uhd_wrap::bufsizetx()
{
	return samps_per_buff_tx;
}

int uhd_wrap::chancount()
{
	return channel_count;
}

int uhd_wrap::wrap_read(TIMESTAMP *timestamp)
{
	uhd::rx_metadata_t md;
	size_t num_rx_samps = rx_stream->recv(wrap_rx_buf_ptrs, samps_per_buff_rx, md, 0.1, true);
	*timestamp = md.time_spec.to_ticks(rx_rate);
	return num_rx_samps; //uhd_device::readSamples(bufs, len, overrun, timestamp, underrun);
}

extern "C" void *uhdwrap_open(struct ipc_sk_if_open_req *open_req)
{
	actual_cfg.num_chans = open_req->num_chans;
	actual_cfg.swap_channels = false;
	/* FIXME: this is actually the sps value, not the sample rate!
	 * sample rate is looked up according to the sps rate by uhd backend */
	actual_cfg.rx_sps = open_req->rx_sample_freq_num / open_req->rx_sample_freq_den;
	actual_cfg.tx_sps = open_req->tx_sample_freq_num / open_req->tx_sample_freq_den;

	/* FIXME: dev arg string* */
	/* FIXME: rx frontend bw? */
	/* FIXME: tx frontend bw? */
	switch (open_req->clockref) {
	case FEATURE_MASK_CLOCKREF_EXTERNAL:
		actual_cfg.clock_ref = ReferenceType::REF_EXTERNAL;
		break;
	case FEATURE_MASK_CLOCKREF_INTERNAL:
	default:
		actual_cfg.clock_ref = ReferenceType::REF_INTERNAL;
		break;
	}

	for (unsigned int i = 0; i < open_req->num_chans; i++) {
		actual_cfg.chans[i].rx_path = open_req->chan_info[i].tx_path;
		actual_cfg.chans[i].tx_path = open_req->chan_info[i].rx_path;
	}

	uhd_wrap *uhd_wrap_dev = new uhd_wrap(RadioDevice::NORMAL, &actual_cfg);
	uhd_wrap_dev->open();

	return uhd_wrap_dev;
}
extern "C" int32_t uhdwrap_get_bufsizerx(void *dev)
{
	uhd_wrap *d = (uhd_wrap *)dev;
	return d->bufsizerx();
}

extern "C" int32_t uhdwrap_get_timingoffset(void *dev)
{
	uhd_wrap *d = (uhd_wrap *)dev;
	return d->getTimingOffset();
}

extern "C" int32_t uhdwrap_read(void *dev, uint32_t num_chans)
{
	TIMESTAMP t;
	uhd_wrap *d = (uhd_wrap *)dev;

	if (num_chans != d->wrap_rx_buf_ptrs.size()) {
		perror("omg chans?!");
	}

	int32_t read = d->wrap_read(&t);

	/* multi channel rx on b210 will return 0 due to alignment adventures, do not put 0 samples into a ipc buffer... */
	if (read <= 0)
		return read;

	for (uint32_t i = 0; i < num_chans; i++) {
		ipc_shm_enqueue(ios_rx_from_device[i], t, read, (uint16_t *)&d->wrap_rx_buffs[i].front());
	}
	return read;
}

extern "C" int32_t uhdwrap_write(void *dev, uint32_t num_chans, bool *underrun)
{
	uhd_wrap *d = (uhd_wrap *)dev;

	uint64_t timestamp;
	int32_t len = -1;
	for (uint32_t i = 0; i < num_chans; i++) {
		len = ipc_shm_read(ios_tx_to_device[i], (uint16_t *)&d->wrap_tx_buffs[i].front(), 5000, &timestamp, 1);
		if (len < 0)
			return 0;
	}

	return d->writeSamples(d->wrap_tx_buf_ptrs, len, underrun, timestamp);
}

extern "C" double uhdwrap_set_freq(void *dev, double f, size_t chan, bool for_tx)
{
	uhd_wrap *d = (uhd_wrap *)dev;
	if (for_tx)
		return d->setTxFreq(f, chan);
	else
		return d->setRxFreq(f, chan);
}

extern "C" double uhdwrap_set_gain(void *dev, double f, size_t chan, bool for_tx)
{
	uhd_wrap *d = (uhd_wrap *)dev;
	//	if (for_tx)
	//		return d->setTxGain(f, chan);
	//	else
	return d->setRxGain(f, chan);
}

extern "C" double uhdwrap_set_txatt(void *dev, double a, size_t chan)
{
	uhd_wrap *d = (uhd_wrap *)dev;
	return d->setPowerAttenuation(a, chan);
}

extern "C" int32_t uhdwrap_start(void *dev, int chan)
{
	uhd_wrap *d = (uhd_wrap *)dev;
	return d->start();
}

extern "C" int32_t uhdwrap_stop(void *dev, int chan)
{
	uhd_wrap *d = (uhd_wrap *)dev;
	return d->stop();
}

extern "C" void uhdwrap_fill_info_cnf(struct ipc_sk_if *ipc_prim)
{
	struct ipc_sk_if_info_chan *chan_info;

	uhd::device_addr_t args("");
	uhd::device_addrs_t devs_found = uhd::device::find(args);
	if (devs_found.size() < 1) {
		std::cout << "\n No device found!";
		exit(0);
	}

	uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(devs_found[0]);
	auto rxchans = usrp->get_rx_num_channels();
	auto txchans = usrp->get_tx_num_channels();
	auto rx_range = usrp->get_rx_gain_range();
	auto tx_range = usrp->get_tx_gain_range();

	//auto nboards = usrp->get_num_mboards();
	auto refs = usrp->get_clock_sources(0);
	auto devname = usrp->get_mboard_name(0);

	ipc_prim->u.info_cnf.feature_mask = 0;
	if (std::find(refs.begin(), refs.end(), "internal") != refs.end())
		ipc_prim->u.info_cnf.feature_mask |= FEATURE_MASK_CLOCKREF_INTERNAL;
	if (std::find(refs.begin(), refs.end(), "external") != refs.end())
		ipc_prim->u.info_cnf.feature_mask |= FEATURE_MASK_CLOCKREF_EXTERNAL;

	// at least one duplex channel
	auto num_chans = rxchans == txchans ? txchans : 1;

	ipc_prim->u.info_cnf.iq_scaling_val_rx = 0.3;
	ipc_prim->u.info_cnf.iq_scaling_val_tx = 1;
	ipc_prim->u.info_cnf.max_num_chans = num_chans;
	OSMO_STRLCPY_ARRAY(ipc_prim->u.info_cnf.dev_desc, devname.c_str());
	chan_info = ipc_prim->u.info_cnf.chan_info;
	for (unsigned int i = 0; i < ipc_prim->u.info_cnf.max_num_chans; i++) {
		auto rxant = usrp->get_rx_antennas(i);
		auto txant = usrp->get_tx_antennas(i);
		for (unsigned int j = 0; j < txant.size(); j++) {
			OSMO_STRLCPY_ARRAY(chan_info->tx_path[j], txant[j].c_str());
		}
		for (unsigned int j = 0; j < rxant.size(); j++) {
			OSMO_STRLCPY_ARRAY(chan_info->rx_path[j], rxant[j].c_str());
		}
		chan_info->min_rx_gain = rx_range.start();
		chan_info->max_rx_gain = rx_range.stop();
		chan_info->min_tx_gain = tx_range.start();
		chan_info->max_tx_gain = tx_range.stop();
		chan_info->nominal_tx_power = 7.5; // FIXME: would require uhd dev + freq info
		chan_info++;
	}
}