// ----------------------------------------------------------------------------
// Copyright (C) 2014
//              David Freese, W1HKJ
//
// This file is part of flrig.
//
// flrig 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 3 of the License, or
// (at your option) any later version.
//
// flrig 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 <cstdlib>
#include <cerrno>
#include <stdexcept>
#include <string>
#include <vector>
#include <queue>

//
// This file started with xml_io.cxx from flrig.
// Heavily modified for use by additional flradios acting as clients to the 1st flradio instance.
// Most code was removed, see flrig/src/support/xml_io.cxx for other possibilities.
//

#include "xml_io.h"

#include "debug.h"
#include "configuration.h"
#include "FreqControl.h"
#include "fl_digi.h"
#include "data_io.h"

#include "threads.h"
#include "XmlRpc.h"

#include "xmlrpc.h"
#include "soundconf.h"
#include "qrunner.h"

using namespace std;
using XmlRpc::XmlRpcValue;

pthread_mutex_t mutex_xmlrpc;

// Setting this displays the mode & bandwidth combo boxes,
//  BUT they are display only, no control from a hydra receive client (ie additional flradio).
// Set to '0' to save a few CPU cycles.
#define GET_EXTRA	0

#if GET_EXTRA
vector<string> mmodes;
void get_modes();
vector<string> bbws;
void get_bandwidths();
#endif // GET_EXTRA

struct FREQMODE {
	long freq;
	int  imode;
	int  iBW;
	int  src;
};

enum {
	UI,
	XML
};

FREQMODE vfoA = {14070000, 0, 0, UI};
FREQMODE vfo = {0, 0, 0, UI};
FREQMODE xmlvfo = vfoA;

static const double TIMEOUT = 1.0;

static const char* main_get_frequency   = "main.get_frequency";
static const char* rig_set_frequency    = "rig.set_frequency";

#if GET_EXTRA
static const char* rig_get_mode         = "rig.get_mode";
static const char* rig_get_modes        = "rig.get_modes";
static const char* rig_get_bandwidth    = "rig.get_bandwidth";
static const char* rig_get_bandwidths   = "rig.get_bandwidths";
#endif // GET_EXTRA

static XmlRpc::XmlRpcClient* client;
static XmlRpcValue* status_query;

#define RIG_UPDATE_INTERVAL  100	// milliseconds
#define CHECK_UPDATE_COUNT   (2000 / RIG_UPDATE_INTERVAL)

bool run_digi_loop = true;
bool fldigi_online = false;

int err_count = 5;
int ignore = 0;				// skip next "ignore" read loops
int try_count = CHECK_UPDATE_COUNT;


//=====================================================================
// socket ops
//=====================================================================

string xmlrpc_port = "";

void
open_rig_xmlrpc()
{
	int server_port = atoi(xmlrpc_port.c_str());
	client = new XmlRpc::XmlRpcClient(
				progdefaults.xmlrpc_address.c_str(), server_port);
	LOG_INFO("as client, %s:%d", progdefaults.xmlrpc_address.c_str(), server_port );
	fldigi_online = true;

	// XmlRpc::setVerbosity(5);

	// Create a value suitable for the system.multicall method, which
	// accepts a single argument: an array of struct { methodName, params }
	// where methodName is a string and params is an array of method
	// arguments.  The params member is mandatory so we use a single-element
	// array containing nil.

	XmlRpcValue nil(XmlRpc::nil);
	status_query = new XmlRpcValue;
	const char* status_methods[] = {
		main_get_frequency
#if GET_EXTRA
		, rig_get_mode, rig_get_bandwidth
#endif // GET_EXTRA
	};
	for (size_t i = 0; i < sizeof(status_methods)/sizeof(*status_methods); i++) {
		(*status_query)[0][i]["methodName"] = status_methods[i];
		(*status_query)[0][i]["params"][0] = nil;
	};

#if GET_EXTRA
	// Get modes & bandwidths from 1st instance of flradio.
	get_modes();
	get_bandwidths();
#endif // GET_EXTRA
}

void
close_rig_xmlrpc()
{
	guard_lock gl_xmlclose(&mutex_xmlrpc);

	delete client;
	client = NULL;
	delete status_query;
	status_query = NULL;
}

static inline void
execute(const char* name, const XmlRpcValue& param, XmlRpcValue& result)
{
	if (client)
		if (!client->execute(name, param, result, TIMEOUT))
			throw XmlRpc::XmlRpcException(name);
}


// This allows us to sync a combo box with flrig.
#if GET_EXTRA
static void
set_combo_contents(Fl_ComboBox* box, const vector<string>* items)
{
	box->clear();

	if (items->empty()) {
		box->add("");
		box->index(0);
		box->deactivate();
		return;
	}

	for (vector<string>::const_iterator i = items->begin(); i != items->end(); ++i) {
		box->add(i->c_str());
	}

	box->index(0);
	box->activate();
}
#endif // GET_EXTRA

#if GET_EXTRA
// We do this to sync the mode combo box with flrig.
// Does NOT allow us to change modes via this box on a xmlrpc client flradio.
void
get_modes()
{
	if (!fldigi_online) return;

	XmlRpcValue modes, res;
	try {
		execute(rig_get_modes, modes, res);
		ignore = 2;
		err_count = 5;
	} catch (const XmlRpc::XmlRpcException& e) {
		err_count--;
//		LOG_ERROR("err count %d: %s", err_count, e.getMessage().c_str());
	}

	guard_lock gl_xmloop(&mutex_xmlrpc);
	mmodes.reserve(res.size());

	// copy
	for (int i = 0; i < res.size(); i++)
		mmodes.push_back(res[i]);

	// Update its contents to match those of flrig.
	REQ_SYNC(set_combo_contents, qso_opMODE, &mmodes);

	// Deactivate it to prevent its use by an xmlrpc client.
	qso_opMODE->deactivate();
}

// We do this to sync the bandwidths combo box with flrig.
// Does NOT allow us to change modes via this box on a xmlrpc client flradio.
void
get_bandwidths()
{
	if (!fldigi_online) return;

	XmlRpcValue bws, res;
	try {
		execute(rig_get_bandwidths, bws, res);
		ignore = 2;
		err_count = 5;
	} catch (const XmlRpc::XmlRpcException& e) {
		err_count--;
//		LOG_ERROR("err count %d: %s", err_count, e.getMessage().c_str());
	}

	guard_lock gl_xmloop(&mutex_xmlrpc);
	bbws.reserve(res.size());

	// copy
	for (int i = 0; i < res.size(); i++)
		bbws.push_back(res[i]);

	// Update its contents to match those of flrig.
	REQ_SYNC(set_combo_contents, qso_opBW, &bbws);

	// Deactivate it to prevent its use by an xmlrpc client.
	qso_opBW->deactivate();
}
#endif // GET_EXTRA


// --------------------------------------------------------------------
// send functions
// --------------------------------------------------------------------

void
send_new_freq(long freq)
{
	if (!fldigi_online) return;
	try {
		xmlvfo.freq = freq;
		XmlRpcValue f((double)freq), res;
		execute(rig_set_frequency, f, res);
		ignore = 2;
		err_count = 5;
	} catch (const XmlRpc::XmlRpcException& e) {
		err_count--;
//		LOG_ERROR("err count %d: %s", err_count, e.getMessage().c_str());
	}
}


// --------------------------------------------------------------------
// receive functions
// --------------------------------------------------------------------

bool xmlvfo_changed = false;

static void
check_for_frequency_change(const XmlRpcValue& freq)
{
	double f = freq;
	long newfreq = (long)f;
	if (newfreq != xmlvfo.freq) {
		xmlvfo.freq = newfreq;
		xmlvfo_changed = true;
		wf->rfcarrier((long long)newfreq);
		show_frequency((long long)newfreq);
	}
}

#if GET_EXTRA
static bool xmlmode_changed = false;

static void
check_for_mode_change(const XmlRpcValue& new_mode)
{
	string smode = new_mode;
	xmlmode_changed = false;
	if (smode != mmodes[xmlvfo.imode]) {
		int imode = 0;
		while (!mmodes[imode].empty() && smode != mmodes[imode])
			imode++;
		if (!mmodes[imode].empty() && imode != xmlvfo.imode) {
			xmlvfo.imode = imode;
			xmlvfo_changed = true;
			xmlmode_changed = true;
			qso_opMODE->value(smode.c_str());
		}
	}
}

static void
check_for_bandwidth_change(const XmlRpcValue& new_bw)
{
	string sbw = new_bw;
	if (sbw != bbws[xmlvfo.iBW]) {
		int ibw = 0;
		while ((!bbws[ibw].empty()) && (sbw != bbws[ibw]))
			ibw++;
		if (!bbws[ibw].empty() && ibw != xmlvfo.iBW) {
			xmlvfo.iBW = ibw;
			xmlvfo_changed = true;
			qso_opBW->value(sbw.c_str());
		}
	}
}
#endif // GET_EXTRA

static void
get_fldigi_status()
{
	if (ignore) {
		--ignore;
		return;
	}

	XmlRpcValue status;
	XmlRpcValue param((int)0);
	string xmlcall;
	try {
		xmlcall = "system.multicall";
		execute(xmlcall.c_str(), *status_query, status);

		xmlvfo.src = XML;
		xmlvfo_changed = false;
		check_for_frequency_change(status[0][0]);
#if GET_EXTRA
		check_for_mode_change(status[1][0]);
		check_for_bandwidth_change(status[2][0]);
#endif // GET_EXTRA
		err_count = 5;
	} catch (const XmlRpc::XmlRpcException& e) {
		err_count--;
//		LOG_ERROR("err count %d: %s", err_count, e.getMessage().c_str());
		if (err_count <= 0)
			throw e;
	}
}

void*
digi_loop(void* d)
{
	SET_THREAD_ID(XMLRPC_TID);

#if !GET_EXTRA
	qso_opMODE->hide();
	qso_opBW->hide();
#endif // GET_EXTRA

	for (;;) {
		MilliSleep(RIG_UPDATE_INTERVAL);
		if (!run_digi_loop) break;
		try {
			if (fldigi_online) {
				guard_lock gl_xmloop(&mutex_xmlrpc);
				if (xmlvfo.freq != qsoFreqDisp->value()) {
					xmlvfo.freq = qsoFreqDisp->value();
					xmlvfo_changed = true;
					send_new_freq(xmlvfo.freq);
				}
				else {
					get_fldigi_status();
				}

			}
		} catch (const XmlRpc::XmlRpcException& e) {
			LOG_ERROR("%s", e.getMessage().c_str());
			fldigi_online = false;
			err_count = 5;
			try_count = CHECK_UPDATE_COUNT;
		}
	}

	SET_THREAD_CANCEL();

	return (NULL);
}

static pthread_t* client_thread;
static pthread_mutex_t* client_mutex;

// Adjust the xmlrpc_port address for use in a hydra system.
bool role_receive = false;
void
adjust_xmlrpc_port(void)
{
	int unit = 0;
	int pi = atoi(progdefaults.xmlrpc_port.c_str()) + progdefaults.SdrReceiver;

	if (strncmp(progdefaults.hydra_role.c_str(), "transmit", 8) == 0) {
		if (progdefaults.hydra_role[8] == ':')
			unit = atoi(&(progdefaults.hydra_role[9]));
		pi += unit;
		role_receive = false;

	}
	else if (strncmp(progdefaults.hydra_role.c_str(), "receive", 7) == 0) {
		pi += HYDRA_ROLE_XMLRPC_OFFSET;
		role_receive = true;

		// Hide dangerous controls.
		wf->xmtrcv->hide();
		btnTxRSID->hide();
		btnTune->hide();
		// XXX what else?
	}

	char ps[20];
	sprintf(ps, "%d", pi);

	xmlrpc_port += ps;
}

bool
xmlrpcClient(void)
{
	if (strncmp(progdefaults.hydra_role.c_str(), "receive", 7) == 0) {
		open_rig_xmlrpc();

		client_thread = new pthread_t;
		client_mutex = new pthread_mutex_t;
		pthread_mutex_init(client_mutex, NULL);
		if (pthread_create(client_thread, NULL, digi_loop, NULL) != 0) {
			throw runtime_error(strerror(errno));
			return (false);
		}

		return (true);
	}

	return (false);
}

bool
hydra_xmlrpc_port(void)
{
	if (progdefaults.btnAudioIOis == SND_IDX_SDR) {
		adjust_xmlrpc_port();

		LOG_INFO("#######################################################");
		if (XML_RPC_Server::start(progdefaults.xmlrpc_address.c_str(), xmlrpc_port.c_str())) {
			LOG_INFO("starting XMLRPC Server");
		}
		else if (xmlrpcClient()) {
			LOG_INFO("starting XMLRPC Client");
		}
		else {
			LOG_INFO("No XMLRPC Server/Client");
		}
		LOG_INFO(" dsp: %d, flrig server: %s flrig port: %s",
			 progdefaults.SdrReceiver, progdefaults.xmlrpc_address.c_str(), xmlrpc_port.c_str());
		LOG_INFO("#######################################################");

		return (true);
	}

	LOG_INFO("Traditional XMLRPC Server");
	return (false);
}
