/*
 * Copyright 2015 Ettus Research LLC
 * Copyright 2018 Ettus Research, a National Instruments Company
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 * 丁劲犇修改 2021
 */

#include <uhd/types/tune_request.hpp>
#include <uhd/usrp/multi_usrp.hpp>
#include <uhd/utils/safe_main.hpp>
#include <uhd/utils/thread.hpp>
#include <chrono>
#include <complex>
#include <csignal>
#include <fstream>
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include <mutex>
using uhd::tune_request_t;
using uhd::tx_streamer;
using uhd::usrp::multi_usrp;
using uhd::rx_streamer;
using std::thread;
using std::cerr;
using std::endl;
using std::string;
using std::vector;
using std::shared_ptr;


static std::atomic<bool> stop_signal_called (false);
void sigint_handler(int code){
	(void)code;
	stop_signal_called = true;
}
/*!
 * \brief The tag_channelOptions struct
 * 通道配置参数
 */
const double sprate_all = 61.44e6/8;

struct tag_channelOptions{
	string type = "sc16";		//样点类型，为上位机上类型: fc64, fc32, or sc16
	string subdev="";			//通道，A:A, A:B两个通道，对应B210左侧、右侧接口.一般默认配置，用信道号channels选取接口。
	string ant = "TX/RX";		//天线选取，B210 有 TX/RX 或者 RX2两个
	string wirefmt;			//内部类型 (sc8 or sc16)，是片上处理的类型
	vector<size_t> channels {0};//通道号,可以设置0,1之类的。默认subdev时，0=A:A，1=A:B，subdev被修改，则采取修改后的编号
	size_t spb	=	10000;			//缓冲大小，太小会丢包，太大会超时
	double rate = sprate_all;		//采样率，单位Hz
	double freq = 1.0e9;			//射频频率，单位Hz
	double gain = 55;				//射频增益，单位dB
	double bw   = rate;				//滤波带宽，默认为采样窗口
	double lo_offset = sprate_all/2;//LO偏移，单位 Hz (缺省)
	bool   int_n_mod = false;		//int-n 模式（本例不配置）
	bool   docheck = true;			//在开始前执行检查
	double setup_time = 1.0;		//rx配置检查时间，可选。
};
//通道检查函数
bool check_tx_status(const string & ref, multi_usrp::sptr usrp,const tag_channelOptions & op);
bool check_rx_status(const string & ref, multi_usrp::sptr usrp,const tag_channelOptions & op);

/*!
 * 范例吞吐函数,使用环形队列保持跟随收发
 */
template <class FMT = short>
void do_io(
		const tag_channelOptions & oprx,
		const tag_channelOptions & optx,
		rx_streamer::sptr rx,
		tx_streamer::sptr tx)
{
	if (oprx.channels.size()>1 || optx.channels.size()>1)
	{
		cerr << "multi channels IO is not suitable for this simple demo."<<endl;
		return;
	}
	//初始化队列
	vector< shared_ptr< FMT > > vec_buffer;
	vector< int > vec_buffersz;
	const int bufsz = 65536;
	for (int i=0;i<bufsz;++i)
	{
		vec_buffer.push_back(shared_ptr< FMT > (new FMT[oprx.spb * 2]));
		vec_buffersz.push_back(0);
	}
	//收发计数
	long long  rx_count (0);
	long long curr_sec = 0;
	double curr_frag = 0;
	std::mutex mutex_tm;
	//接收线程
	auto thcall_rx = [&]()->void{
		uhd::rx_metadata_t md_rx;
		uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS);
		stream_cmd.num_samps  = size_t(oprx.spb);
		stream_cmd.stream_now = true;
		stream_cmd.time_spec  = uhd::time_spec_t();
		rx->issue_stream_cmd(stream_cmd);
		while (!stop_signal_called)
		{
			vec_buffersz[rx_count % bufsz] = rx->recv((void *)(vec_buffer[rx_count % bufsz].get()),oprx.spb,md_rx,0.1,false);
			//md_rx可以读取时戳
			mutex_tm.lock();
			curr_sec = md_rx.time_spec.get_full_secs();
			curr_frag = md_rx.time_spec.get_frac_secs();
			mutex_tm.unlock();
			++rx_count;
			if (md_rx.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT)
				fputs("Time out.",stderr);
			else if (md_rx.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW)
				fputs("Over flow",stderr);
			else if (md_rx.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE)
				cerr << "Receiver error: "<< md_rx.strerror() << endl ;
		}
	};
	//发射线程
	auto thcall_tx = [&]()->void{
		uhd::tx_metadata_t md_tx;
		md_tx.end_of_burst   = false;
		md_tx.start_of_burst = false;
		const int total_blocks_needed = oprx.rate / oprx.spb + 1;
		while (!stop_signal_called)
		{
			long long send_sec = 0;
			double send_frag = 0;
			if (curr_sec % 5!=4 || rx_count < total_blocks_needed)
			{
				std::this_thread::sleep_for(std::chrono::milliseconds(10));
				continue;
			}
			mutex_tm.lock();
			send_sec  = curr_sec;
			send_frag = curr_frag;
			mutex_tm.unlock();

			md_tx.start_of_burst = true;
			md_tx.end_of_burst   = false;
			md_tx.has_time_spec = true;
			md_tx.time_spec = uhd::time_spec_t(send_sec+1,0);
			fprintf(stderr,"\nSending burst...\n");
			for (int i=0;i<total_blocks_needed;++i)
			{
				long long tx_count = rx_count -total_blocks_needed + i;
				const size_t samples_sent = tx->send((void *)(vec_buffer[tx_count % bufsz].get()),vec_buffersz[tx_count % bufsz],md_tx,2);
				md_tx.start_of_burst = false;
				md_tx.end_of_burst   = i==total_blocks_needed-2?true:false;
				md_tx.has_time_spec = false;
				if (samples_sent != optx.spb)
					cerr<<"The tx_stream timed out sending " << optx.spb << " samples ("  << samples_sent << " sent)." << endl;
			}
			fprintf(stderr,"\nSending burst finished.\n");
		}
	};

	//启动线程
	thread rx_thread(thcall_rx);
	thread tx_thread(thcall_tx);
	cerr<<"Press ^C to Stop."<<endl;
	//主线程不断打印状态
	while (!stop_signal_called)
	{
		cerr<<"\nRX" << rx_count<< " Sec " << curr_sec<<"."<<curr_frag<<"   \r";
		std::this_thread::sleep_for(std::chrono::milliseconds(1000));
	}
	//退出
	rx_thread.join();
	tx_thread.join();

}


int UHD_SAFE_MAIN(int /*argc*/, char* /*argv*/[])
{
	//1.创建一个设备，可以不填写，则使用第一台设备。
	string args ("");
	//2.设置时钟参考 (internal, external, mimo)
	string ref = "internal";
	cerr << "Creating the usrp device with: "  << args  <<"..."<< endl;
	multi_usrp::sptr usrp = multi_usrp::make(args);
	usrp->set_clock_source(ref,multi_usrp::ALL_MBOARDS);

	//3.配置发射
	tag_channelOptions tx_op;
	tx_op.freq = 200e6;
	tx_op.bw = 400e3;
	tx_op.gain=70;
	tx_op.channels[0] = 0;
	//3.1子设备配置（默认）,一般不动它。
	if (tx_op.subdev.size())
		usrp->set_tx_subdev_spec(tx_op.subdev, multi_usrp::ALL_MBOARDS);
	cerr << "TX Using Device: " << usrp->get_pp_string() << endl;
	//3.2速率配置
	cerr << "Setting TX Rate: " <<  (tx_op.rate / 1e6) << "Msps..." << endl;
	usrp->set_tx_rate(tx_op.rate, multi_usrp::ALL_CHANS);
	cerr << "Actual TX Rate: " << usrp->get_tx_rate(tx_op.channels[0]) / 1e6 << "Msps..." << endl;
	//3.3中心频率配置
	cerr << "Setting TX Freq: "  << (tx_op.freq / 1e6) <<"MHz..." << endl;
	cerr << "Setting TX LO Offset: " << (tx_op.lo_offset / 1e6) << "MHz..." <<endl;
	tune_request_t tune_request_tx = tune_request_t(tx_op.freq, tx_op.lo_offset);
	if (tx_op.int_n_mod)
		tune_request_tx.args = uhd::device_addr_t("mode_n=integer");
	usrp->set_tx_freq(tune_request_tx,tx_op.channels[0]);

	cerr << "Actual TX Freq: " <<  (usrp->get_tx_freq(tx_op.channels[0]) / 1e6) << "MHz..." << endl;
	//3.4增益配置
	cerr << "Setting TX Gain: " << tx_op.gain <<" dB..." << endl;
	usrp->set_tx_gain(tx_op.gain,tx_op.channels[0]);
	cerr << "Actual TX Gain: " << usrp->get_tx_gain(tx_op.channels[0]) << " dB..." << endl;
	//3.5模拟前端滤波器带宽配置
	cerr << "Setting TX Bandwidth: " << (tx_op.bw / 1e6) << "MHz..."  << endl;
	usrp->set_tx_bandwidth(tx_op.bw,tx_op.channels[0]);
	cerr << "Actual TX Bandwidth: " << usrp->get_tx_bandwidth(tx_op.channels[0]) / 1e6 << "MHz..." << endl;
	//3.6指定天线
	if (tx_op.ant.size())
		usrp->set_tx_antenna(tx_op.ant,tx_op.channels[0]);

	//4.配置接收
	tag_channelOptions rx_op;
	rx_op.ant = "RX2";
	rx_op.bw = 400e3;
	rx_op.freq = 105.3e6;
	rx_op.gain = 50;
	rx_op.channels[0] = 1;
	//4.1 子设备
	if (rx_op.subdev.size())
		usrp->set_rx_subdev_spec(rx_op.subdev,multi_usrp::ALL_MBOARDS);
	cerr << "RX Using Device: " << usrp->get_pp_string() << endl;
	//4.2 采样率
	cerr << "Setting RX Rate: " <<  (rx_op.rate / 1e6) << "Msps..." << endl;
	usrp->set_rx_rate(rx_op.rate,multi_usrp::ALL_CHANS);
	cerr << "Actual RX Rate: " << usrp->get_rx_rate(rx_op.channels[0]) / 1e6 << "Msps..." << endl;
	//4.3 中心频率
	cerr << "Setting RX Freq: "  << (rx_op.freq / 1e6) <<"MHz..." << endl;
	cerr << "Setting RX LO Offset: " << (rx_op.lo_offset / 1e6) << "MHz..." <<endl;
	tune_request_t tune_request_rx = tune_request_t(rx_op.freq, rx_op.lo_offset);
	if (rx_op.int_n_mod)
		tune_request_rx.args = uhd::device_addr_t("mode_n=integer");
	usrp->set_rx_freq(tune_request_rx,rx_op.channels[0]);
	cerr << "Actual RX Freq: " <<  (usrp->get_rx_freq(rx_op.channels[0]) / 1e6) << "MHz..." << endl;
	//4.4 增益
	cerr << "Setting RX Gain: " << rx_op.gain <<" dB..." << endl;
	usrp->set_rx_gain(rx_op.gain,rx_op.channels[0]);
	cerr << "Actual RX Gain: " << usrp->get_rx_gain(rx_op.channels[0]) << " dB..." << endl;
	//4.5 前端模拟滤波带宽
	cerr << "Setting RX Bandwidth: " << (rx_op.bw / 1e6) << "MHz..."  << endl;
	usrp->set_rx_bandwidth(rx_op.bw,rx_op.channels[0]);
	cerr << "Actual RX Bandwidth: " << usrp->get_rx_bandwidth(rx_op.channels[0]) / 1e6 << "MHz..." << endl;
	//4.6 选择天线
	if (rx_op.ant.size())
		usrp->set_rx_antenna(rx_op.ant,rx_op.channels[0]);

	//5 检查状态
	if (tx_op.docheck)	check_tx_status(ref,usrp,tx_op);
	if (rx_op.docheck)	check_rx_status(ref,usrp,rx_op);

	//6.创建流对象实例
	//6.1 发射
	uhd::stream_args_t stream_args_tx(tx_op.type, tx_op.wirefmt);
	stream_args_tx.channels = tx_op.channels;
	tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args_tx);
	// 6.2 接收
	uhd::stream_args_t stream_args_rx(rx_op.type, rx_op.wirefmt);
	stream_args_rx.channels             = rx_op.channels;
	rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args_rx);

	//开始收发循环
	do_io<short>(rx_op,tx_op,rx_stream,tx_stream);

	// finished
	cerr << endl << "Done!" << endl << endl;

	return EXIT_SUCCESS;
}


bool check_tx_status(const string & ref, multi_usrp::sptr usrp,const tag_channelOptions & op)
{
	// Check Ref and LO Lock detect
	vector<string> sensor_names;
	for (size_t c :op.channels)
	{
		sensor_names = usrp->get_tx_sensor_names(c);
		if (std::find(sensor_names.begin(), sensor_names.end(), "lo_locked")
				!= sensor_names.end()) {
			uhd::sensor_value_t lo_locked = usrp->get_tx_sensor("lo_locked",op.channels[0]);
			cerr <<"Checking TX: "<< lo_locked.to_pp_string() << std::endl;
			UHD_ASSERT_THROW(lo_locked.to_bool());
		}

	}
	sensor_names = usrp->get_mboard_sensor_names(0);
	if ((ref == "mimo")
			and (std::find(sensor_names.begin(), sensor_names.end(), "mimo_locked")
				 != sensor_names.end())) {
		uhd::sensor_value_t mimo_locked = usrp->get_mboard_sensor("mimo_locked",0);
		std::cerr << "Checking TX: "<< mimo_locked.to_pp_string()  << std::endl;
		UHD_ASSERT_THROW(mimo_locked.to_bool());
	}
	if ((ref == "external")
			and (std::find(sensor_names.begin(), sensor_names.end(), "ref_locked")
				 != sensor_names.end())) {
		uhd::sensor_value_t ref_locked = usrp->get_mboard_sensor("ref_locked", 0);
		std::cout << "Checking TX: %s ..."<< ref_locked.to_pp_string()  << std::endl;
		UHD_ASSERT_THROW(ref_locked.to_bool());
	}
	return true;
}
typedef std::function<uhd::sensor_value_t(const string&)> get_sensor_fn_t;
bool check_locked_sensor(vector<string> sensor_names,
						 const char* sensor_name,
						 get_sensor_fn_t get_sensor_fn,
						 double setup_time)
{
	if (std::find(sensor_names.begin(), sensor_names.end(), sensor_name)
			== sensor_names.end())
		return false;

	auto setup_timeout = std::chrono::steady_clock::now()
			+ std::chrono::milliseconds(int64_t(setup_time * 1000));
	bool lock_detected = false;

	std::cerr << "Checking RX Waiting for: " << sensor_name;
	std::cerr.flush();

	while (true) {
		if (lock_detected and (std::chrono::steady_clock::now() > setup_timeout)) {
			std::cerr << " locked." << std::endl;
			break;
		}
		if (get_sensor_fn(sensor_name).to_bool()) {
			std::cerr << "+";
			std::cerr.flush();
			lock_detected = true;
		} else {
			if (std::chrono::steady_clock::now() > setup_timeout) {
				std::cerr << std::endl;
				std::cerr << "timed out waiting for consecutive locks on sensor : "<<sensor_name;
				return false;
			}
			std::cerr.flush();
		}
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
	std::cout << std::endl;
	return true;
}

bool check_rx_status(const string & ref, multi_usrp::sptr usrp,const tag_channelOptions & op)
{
	// check Ref and LO Lock detect
	for (size_t c :op.channels)
	{
		check_locked_sensor(usrp->get_rx_sensor_names(c),
							"lo_locked",
							[usrp, op, c](const string& sensor_name) {
			return usrp->get_rx_sensor(sensor_name, c);
		},
		op.setup_time);
	}
	if (ref == "mimo") {
		check_locked_sensor(usrp->get_mboard_sensor_names(0),
							"mimo_locked",
							[usrp](const string& sensor_name) {
			return usrp->get_mboard_sensor(sensor_name);
		},
		op.setup_time);
	}
	if (ref == "external") {
		check_locked_sensor(usrp->get_mboard_sensor_names(0),
							"ref_locked",
							[usrp](const string& sensor_name) {
			return usrp->get_mboard_sensor(sensor_name);
		},
		op.setup_time);
	}
	return true;
}
