//======================================================================
// flamp xml_io.cxx
//
// copyright 2012, W1HKJ
//
// This file is part of FLAMP.
//
// This 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.
//
// This software 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/>.
//
// xmlrpc interface to fldigi
//
// fetches current list of modem types from fldigi
// fetches current modem in use in fldigi
// sets fldigi modem-by-name when required
//
//======================================================================

#include <stdio.h>

#include <cstdlib>
#include <string>
#include <vector>
#include <queue>

#include <iostream>
#include <errno.h>

#include "flamp.h"
#include "flamp_dialog.h"
#include "dialogs.h"
#include "xml_io.h"
#include "XmlRpc.h"
#include "status.h"
#include "debug.h"
#include "threads.h"

using XmlRpc::XmlRpcValue;

double xmlrpc_timeout = INIT_XMLRPC_TIMEOUT;

// these are get only
static const char* modem_get_name		     = "modem.get_name";
static const char* modem_get_io_names	     = "modem.get_io_names";

// these are set only
static const char* modem_set_by_name	     = "modem.set_by_name";
static const char* text_clear_tx		     = "text.clear_tx";
static const char* text_add_tx			     = "text.add_tx";
static const char* text_clear_rx		     = "text.clear_rx";
static const char* fldigi_get_version		 = "fldigi.version";
static const char* main_get_trx_state	     = "main.get_trx_state";
static const char* main_tx				     = "main.tx";
static const char* main_tune			     = "main.tune";
static const char* main_rx				     = "main.rx";
static const char* main_abort			     = "main.abort";
static const char* main_get_rsid             = "main.get_rsid";
static const char* main_set_rsid             = "main.set_rsid";
static const char* main_get_char_rates       = "main.get_char_rates";
static const char* main_get_tx_timing        = "main.get_tx_timing";
static const char* main_get_char_timing      = "main.get_char_timing";
static const char* io_in_use                 = "io.in_use";
static const char* io_enable_arq             = "io.enable_arq";
static const char* io_enable_kiss            = "io.enable_kiss";

static XmlRpc::XmlRpcClient* client = 0;

#define XMLRPC_UPDATE_INTERVAL  200
#define XMLRPC_UPDATE_AFTER_WRITE 1000
#define XMLRPC_RETRY_INTERVAL 2000

extern int errno;

size_t fldigi_state = FLDIGI_OFFLINE;
static bool modem_list_received = false;


//=====================================================================
// socket ops
//=====================================================================
int update_interval = XMLRPC_UPDATE_INTERVAL;

std::string xmlcall = "";

void open_xmlrpc()
{
	guard_lock xmlrpc_client(&mutex_xmlrpc);

	std::string addr;
	std::string port;

	// Check if address/port passed via command line

	if(progStatus.user_xmlrpc_addr.size())
		addr.assign(progStatus.user_xmlrpc_addr);
	else
		addr.assign(progStatus.xmlrpc_addr);

	if(progStatus.user_xmlrpc_port.size())
		port.assign(progStatus.user_xmlrpc_port);
	else
		port.assign(progStatus.xmlrpc_port);

	int server_port = atoi(port.c_str());

	client = new XmlRpc::XmlRpcClient( addr.c_str(), server_port );

	//	XmlRpc::setVerbosity(5); // 0...5
}

void close_xmlrpc()
{
	guard_lock xmlrpc_client(&mutex_xmlrpc);

	delete client;
	client = NULL;
}

static inline void execute(const char* name, const XmlRpcValue& param, XmlRpcValue& result)
{
	if (client) {
		if (!client->execute(name, param, result, xmlrpc_timeout)) {
			xmlrpc_errno = errno;

			if(client->isFault())
				LOG_DEBUG("Server fault response!");

			throw XmlRpc::XmlRpcException(name);
		}
	}
	xmlrpc_errno = errno;
}

void set_xmlrpc_timeout(double value)
{
	guard_lock xmlrpc_client(&mutex_xmlrpc);

	if(value < DEFAULT_XMLRPC_TIMEOUT) return;
	xmlrpc_timeout = value;
}

void set_xmlrpc_timeout_default(void)
{
	xmlrpc_timeout = DEFAULT_XMLRPC_TIMEOUT;
}


// --------------------------------------------------------------------
// send functions
// --------------------------------------------------------------------
//extern std::string g_modem;

void send_new_modem(std::string modem)
{
	guard_lock xmlrpc_client(&mutex_xmlrpc);

	try {
		XmlRpcValue mode(modem), res;
		execute(modem_set_by_name, mode, res);
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
	}
	
	update_interval = XMLRPC_UPDATE_AFTER_WRITE;
	
	// Give FLDIGI time to switch modems.
	MilliSleep(150);
}

/** ********************************************************
 *
 ***********************************************************/
void send_clear_tx(void)
{
	guard_lock xmlrpc_client(&mutex_xmlrpc);

	try {
		XmlRpcValue res;
		execute(text_clear_tx, 0, res);
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
	}
	update_interval = XMLRPC_UPDATE_AFTER_WRITE;
}

/** ********************************************************
 *
 ***********************************************************/
void send_clear_rx(void)
{
	guard_lock xmlrpc_client(&mutex_xmlrpc);
	try {
		XmlRpcValue res;
		execute(text_clear_rx, 0, res);
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
	}
	update_interval = XMLRPC_UPDATE_AFTER_WRITE;
}

/** ********************************************************
 *
 ***********************************************************/
void send_report(std::string report)
{
	guard_lock xmlrpc_client(&mutex_xmlrpc);
	try {
		XmlRpcValue res, xml_str = report;
		execute(text_clear_tx, 0, res);
		execute(text_add_tx, xml_str, res);
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
	}
	update_interval = XMLRPC_UPDATE_AFTER_WRITE;
}

/** ********************************************************
 *
 ***********************************************************/
void send_tx(void)
{
	guard_lock xmlrpc_client(&mutex_xmlrpc);
	try {
		XmlRpcValue res;
		execute(main_tx, 0, res);
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
	}
	update_interval = XMLRPC_UPDATE_AFTER_WRITE;
}

/** ********************************************************
 *
 ***********************************************************/
void send_rx(void)
{
	guard_lock xmlrpc_client(&mutex_xmlrpc);
	try {
		XmlRpcValue res;
		execute(main_rx, 0, res);
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
	}
	update_interval = XMLRPC_UPDATE_AFTER_WRITE;
}

/** ********************************************************
 *
 ***********************************************************/
void set_rsid(void)
{
	guard_lock xmlrpc_client(&mutex_xmlrpc);
	try {
		XmlRpcValue res;
		execute(main_set_rsid, 0, res);
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
	}
	update_interval = XMLRPC_UPDATE_AFTER_WRITE;
}

/** ********************************************************
 *
 ***********************************************************/
void send_abort(void)
{
	guard_lock xmlrpc_client(&mutex_xmlrpc);
	try {
		XmlRpcValue res;
		execute(main_abort, 0, res);
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
	}
	update_interval = XMLRPC_UPDATE_AFTER_WRITE;
}

/** ********************************************************
 *
 ***********************************************************/
void send_tune(void)
{
	guard_lock xmlrpc_client(&mutex_xmlrpc);
	try {
		XmlRpcValue res;
		execute(main_tune, 0, res);
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
	}
	update_interval = XMLRPC_UPDATE_AFTER_WRITE;
}

/** ********************************************************
 *
 ***********************************************************/
std::string get_tx_timing(std::string data)
{
	XmlRpcValue status;
	XmlRpcValue xmlData((void *)data.c_str(), data.size());
	static std::string response;

	guard_lock xmlrpc_client(&mutex_xmlrpc);
	try {
		execute(main_get_tx_timing, xmlData, status);
		std::string resp = status;
		response = resp;
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
	}
	update_interval = XMLRPC_UPDATE_AFTER_WRITE;

	return response;
}

/** ********************************************************
 *
 ***********************************************************/
std::string get_char_timing(int character)
{
	guard_lock xmlrpc_client(&mutex_xmlrpc);

	XmlRpcValue status;
	static std::string response;
	char buff[20];

	memset(buff, 0, sizeof(buff));

	snprintf(buff, sizeof(buff) - 1, "%d", character);

	std::string data;
	data.assign(buff);

	XmlRpcValue xmlData((void *) data.c_str(), data.size());

	try {
		execute(main_get_char_timing, xmlData, status);
		std::string resp = status;
		response = resp;
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
	}

	update_interval = XMLRPC_UPDATE_AFTER_WRITE;

	return response;
}

/** ********************************************************
 *
 ***********************************************************/
void enable_arq(void)
{
	guard_lock xmlrpc_client(&mutex_xmlrpc);
	try {
		XmlRpcValue res;
		execute(io_enable_arq, 0, res);
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
	}
	update_interval = XMLRPC_UPDATE_AFTER_WRITE;
}

void enable_kiss(void)
{
	guard_lock xmlrpc_client(&mutex_xmlrpc);
	try {
		XmlRpcValue res;
		execute(io_enable_kiss, 0, res);
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
	}
	update_interval = XMLRPC_UPDATE_AFTER_WRITE;
}

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

/** ********************************************************
 *
 ***********************************************************/
void set_offline_state(void)
{
	modem_list_received = 0;
	fldigi_state = FLDIGI_OFFLINE;
}

/** ********************************************************
 * fldigi_online()
 * returns either FLDIGI_OFFLINE or FLDIGI_ONLINE
 * depending on whether the get version query receives a
 * response from the target fldigi instance
 ***********************************************************/
int fldigi_online()
{
	XmlRpcValue status;
	XmlRpcValue query;

	guard_lock lock(&mutex_xmlrpc);
	try {
		execute(fldigi_get_version, query, status);
		fldigi_state |= FLDIGI_XMLRPC_ONLINE;
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
		set_offline_state();
	}

	return fldigi_state;
}

/** ********************************************************
 *
 ***********************************************************/
std::string get_io_mode(void)
{
	XmlRpcValue status;
	XmlRpcValue query;
	static std::string response;

	guard_lock xmlrpc_client(&mutex_xmlrpc);
	try {
		execute(io_in_use, query, status);
		std::string resp = status;
		response = resp;
		fldigi_state |= FLDIGI_XMLRPC_ONLINE;
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
		response = "NIL";
		set_offline_state();
	}

	xmlrpc_timeout = DEFAULT_XMLRPC_TIMEOUT;

	return response;
}

/** ********************************************************
 *
 ***********************************************************/
static void set_combo(void *str)
{
	std::string s = (char *)str;

	if(progStatus.use_header_modem != 0) return;

	if (s != cbo_modes->value() && valid_mode_check(s)) {
		cbo_modes->value(s.c_str());
		progStatus.selected_mode = cbo_modes->index();
		g_modem.assign(cbo_modes->value());
		cbo_modes->do_callback();
	}
}

/** ********************************************************
 *
 ***********************************************************/
std::string get_rsid_state(void)
{
	XmlRpcValue status;
	XmlRpcValue query;
	static std::string response;

	guard_lock xmlrpc_client(&mutex_xmlrpc);
	try {
		execute(main_get_rsid, query, status);
		std::string resp = status;
		response = resp;
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
	}

	return response;
}

/** ********************************************************
 *
 ***********************************************************/
void connected_to_fldigi(void *state)
{
	if (state) {
		box_not_connected->hide();
		box_connected->show();
	} else {
		box_not_connected->show();
		box_connected->hide();
	}
	box_not_connected->redraw();
	box_connected->redraw();
}

/** ********************************************************
 *
 ***********************************************************/
std::string get_trx_state()
{
	XmlRpcValue status;
	XmlRpcValue query;
	static std::string response;

	guard_lock xmlrpc_client(&mutex_xmlrpc);
	try {
		execute(main_get_trx_state, query, status);
		std::string resp = status;
		response = resp;
		fldigi_state |= FLDIGI_XMLRPC_ONLINE;
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
		response = "ABORT";
		// This function is used to test if FLDIGI is off/on line.
		set_offline_state();
	}

	return response;
}

/** ********************************************************
 *
 ***********************************************************/
std::string get_char_rates()
{
	XmlRpcValue status;
	XmlRpcValue query;
	static std::string response;

	guard_lock xmlrpc_client(&mutex_xmlrpc);
	try {
		execute(main_get_char_rates, query, status);
		std::string resp = status;
		response = resp;
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
	}

	return response;
}

/** ********************************************************
 *
 ***********************************************************/
static void get_fldigi_modem()
{
	if (!progStatus.sync_mode_fldigi_flamp) return;
	
	XmlRpcValue status;
	XmlRpcValue query;
	static std::string response;

	guard_lock xmlrpc_client(&mutex_xmlrpc);
	try {
		execute(modem_get_name, query, status);
		std::string resp = status;
		response = resp;

		if (!response.empty()) {
			Fl::awake(set_combo, (void *)response.c_str());
		}
	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
	}
}

/** ********************************************************
 *
 ***********************************************************/
static void get_fldigi_modems()
{
	XmlRpcValue status;
	XmlRpcValue query;
	static std::string response;
	static std::string tmp;
	static std::string fldigi_modes;

	fldigi_modes.clear();

	guard_lock xmlrpc_client(&mutex_xmlrpc);
	try {

		execute(modem_get_io_names, query, status);

		for (int i = 0; i < status.size(); i++) {
			tmp = (std::string)status[i];
			fldigi_modes.append(tmp).append("|");
		}

		update_cbo_modes(fldigi_modes);

		modem_list_received = true;

	} catch (const XmlRpc::XmlRpcException& e) {
		LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
		if(e.getMessage().find("expected an array") == std::string::npos)
			set_offline_state();
	}
}

/** ********************************************************
 *
 ***********************************************************/
void * xmlrpc_loop(void *d)
{
	if (!client) open_xmlrpc();
	size_t state = 0;
	static size_t old_state = FLDIGI_OFFLINE;
	
	for (;;) {
		try {
			state = (fldigi_online() & FLDIGI_XMLRPC_ONLINE);
			
			if (state && !modem_list_received)
				get_fldigi_modems();
			else if (state)
				get_fldigi_modem();
			else
				set_offline_state();
				
			state = fldigi_state == FLDIGI_ONLINE ? 1 : 0;

			if(old_state != state) 
				Fl::awake(connected_to_fldigi, (void *)state);

			old_state = state;
			
		} catch (const XmlRpc::XmlRpcException& e) {
			LOG_ERROR("%s xmlrpc_errno = %d", e.getMessage().c_str(), xmlrpc_errno);
		}

		MilliSleep(XMLRPC_UPDATE_INTERVAL);
	}
	return NULL;
}
