// ----------------------------------------------------------------------------
// arq_io.cxx
//
// support for ARQ server/client system such as pskmail and fl_arq
//
// Copyright (C) 2006-2013
//		Dave Freese, W1HKJ
// Copyright (C) 2008-2013
//		Stelios Bounanos, M0GLD
// Copyright (C) 2009-2013
//		John Douyere, VK2ETA
// Copyright (c) 2013
//		Remi Chateauneu, F4ECW
//
// This file is part of fldigi.
//
// Fldigi 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.
//
// Fldigi 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 fldigi.  If not, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------


#include <config.h>

#ifdef __MINGW32__
#  include "compat.h"
#endif

#include <fstream>
#include <sstream>
#include <string>
#include <cstdlib>
#include <ctime>
#include <errno.h>

#include <sys/types.h>
#if !defined(__WOE32__) && !defined(__APPLE__)
#  include <sys/ipc.h>
#  include <sys/msg.h>
#endif

#include <signal.h>

#include "main.h"
#include "configuration.h"
#include "fl_digi.h"
#include "trx.h"
#include "arq_io.h"

#include "threads.h"
#include "socket.h"
#include "debug.h"
#include "qrunner.h"

#include <FL/Fl.H>
#include <FL/fl_ask.H>

LOG_FILE_SOURCE(debug::LOG_ARQCONTROL);

using namespace std;

// =====================================================================
static pthread_t arq_thread;
static pthread_mutex_t arq_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t arq_rx_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t tosend_mutex = PTHREAD_MUTEX_INITIALIZER;

static void *arq_loop(void *args);

static bool arq_exit = false;
static bool arq_enabled;
static bool abort_flag = false;

/// Any access to shared variables must be protected.
static string tosend = "";   // Protected by tosend_mutex
static string enroute = "";  // Protected by tosend_mutex

static string arqtext = "";  // Protected by arq_rx_mutex
static string txstring = ""; // Protected by arq_rx_mutex
static size_t pText;         // Protected by arq_rx_mutex
bool arq_text_available = false; // Protected by arq_rx_mutex
								 // Beware 'arq_text_available' is accessed by other modules.

// =====================================================================

static const char *asc[128] = {
	"<NUL>", "<SOH>", "<STX>", "<ETX>",
	"<EOT>", "<ENQ>", "<ACK>", "<BEL>",
	"<BS>",  "<TAB>", "\n",  "<VT>",
	"<FF>",  "",  "<SO>",  "<SI>",
	"<DLE>", "<DC1>", "<DC2>", "<DC3>",
	"<DC4>", "<NAK>", "<SYN>", "<ETB>",
	"<CAN>", "<EM>",  "<SUB>", "<ESC>",
	"<FS>",  "<GS>",  "<RS>",  "<US>",
	" ",     "!",     "\"",    "#",
	"$",     "%",     "&",     "\'",
	"(",     ")",     "*",     "+",
	",",     "-",     ".",     "/",
	"0",     "1",     "2",     "3",
	"4",     "5",     "6",     "7",
	"8",     "9",     ":",     ";",
	"<",     "=",     ">",     "?",
	"@",     "A",     "B",     "C",
	"D",     "E",     "F",     "G",
	"H",     "I",     "J",     "K",
	"L",     "M",     "N",     "O",
	"P",     "Q",     "R",     "S",
	"T",     "U",     "V",     "W",
	"X",     "Y",     "Z",     "[",
	"\\",    "]",     "^",     "_",
	"`",     "a",     "b",     "c",
	"d",     "e",     "f",     "g",
	"h",     "i",     "j",     "k",
	"l",     "m",     "n",     "o",
	"p",     "q",     "r",     "s",
	"t",     "u",     "v",     "w",
	"x",     "y",     "z",     "{",
	"|",     "}",     "~",     "<DEL>"
};

string noctrl(string src)
{
	static string retstr;
	retstr.clear();
	char hexstr[10];
	int c;
	for (size_t i = 0; i < src.length(); i++)  {
		c = src[i];
		if ( c > 0 && c < 128)
			retstr.append(asc[c]);
		else {
			snprintf(hexstr, sizeof(hexstr), "<%0X>", c & 0xFF);
			retstr.append(hexstr);
		}
	}
	return retstr;
}

//======================================================================

extern void parse_arqtext(string &toparse);

static void set_button(Fl_Button* button, bool value)
{
	button->value(value);
	button->do_callback();
}

void ParseMode(string src)
{
	if (src.find("XMTTUNE") != string::npos) {
		int msecs = 100;
		if (src.length() > 7) {
			int ret = sscanf( src.substr(7, src.length() - 7).c_str(), "%d", &msecs);
			if (ret != 1 || msecs < 10 || msecs > 20000) msecs = 100;
		}
		if (debug_pskmail)
			LOG_INFO("%s %5.2f sec", "ARQ tune on", msecs/1000.0);
		trx_tune();
		MilliSleep(msecs);
		if (debug_pskmail)
			LOG_INFO("%s", "ARQ tune off");
		trx_receive();
		return;
	}
	if (src.find("PTTTUNE") != string::npos) {
		int msecs = 100;
		if (src.length() > 7) {
			int ret = sscanf( src.substr(7, src.length() - 7).c_str(), "%d", &msecs);
			if (ret != 1 || msecs < 10 || msecs > 20000) msecs = 100;
		}
		if (debug_pskmail)
			LOG_INFO("%s %5.2f sec", "ARQ set ptt on", msecs/1000.0);
		push2talk->set(true);
		REQ(&waterfall::set_XmtRcvBtn, wf, true);
		MilliSleep(msecs);
		if (debug_pskmail)
			LOG_INFO("%s", "ARQ set ptt off");
		push2talk->set(false);
		REQ(&waterfall::set_XmtRcvBtn, wf, false);
		return;
	}
	for (size_t i = 0; i < NUM_MODES; ++i) {
		if (strlen(mode_info[i].pskmail_name) > 0) {
			if (src == mode_info[i].pskmail_name) {
				REQ_SYNC(init_modem_sync, mode_info[i].mode, 0);
				AbortARQ();
				WriteARQ('\002');
				if (debug_pskmail)
					LOG_INFO("Modem set to %s", mode_info[i].pskmail_name);
				break;
			}
		}
	}
}

void ParseRSID(string src)
{
	if (src == "ON") {
		if (debug_pskmail)
			LOG_INFO("%s", "RsID turned ON");
		REQ(set_button, btnRSID, 1);
	}
	if (src == "OFF") {
		if (debug_pskmail)
			LOG_INFO("%s", "RsID turned OFF");
		REQ(set_button, btnRSID, 0);
	}
}


void ParseTxRSID(string src)
{
	if (src == "ON") {
		if (debug_pskmail)
			LOG_INFO("%s", "TxRsID turned ON");
		REQ(set_button, btnTxRSID, 1);
	}
	if (src == "OFF") {
		if (debug_pskmail)
			LOG_INFO("%s", "TxRsID turned OFF");
		REQ(set_button, btnTxRSID, 0);
	}
}

void parse_arqtext(string &toparse)
{
	static	string strCmdText;
	static	string strSubCmd;
	unsigned long int idxCmd, idxCmdEnd, idxSubCmd, idxSubCmdEnd;

	if (toparse.empty()) return;

	idxCmd = toparse.find("<cmd>");
	idxCmdEnd = toparse.find("</cmd>");

	while ( idxCmd != string::npos && idxCmdEnd != string::npos && idxCmdEnd > idxCmd ) {
		LOG_INFO("Parsing: %s", noctrl(toparse.substr(idxCmd, idxCmdEnd - idxCmd + 6)).c_str());

		strCmdText = toparse.substr(idxCmd + 5, idxCmdEnd - idxCmd - 5);
		if (strCmdText == "server" && mailserver == false && mailclient == false) {
			mailserver = true;
			mailclient = false;
			string PskMailLogName;
			PskMailLogName.assign(PskMailDir);
			PskMailLogName.append("gMFSK.log");
			Maillogfile = new cLogfile(PskMailLogName.c_str());
			Maillogfile->log_to_file_start();
			REQ(set_button, wf->xmtlock, 1);
			if (progdefaults.PSKmailSweetSpot)
				active_modem->set_freq(progdefaults.PSKsweetspot);
			active_modem->set_freqlock(true);
			LOG_INFO("%s", "ARQ is set to pskmail server");
		} else if (strCmdText == "client" && mailclient == false && mailserver == false) {
			mailclient = true;
			mailserver = false;
			string PskMailLogName;
			PskMailLogName.assign(PskMailDir);
			PskMailLogName.append("gMFSK.log");
			Maillogfile = new cLogfile(PskMailLogName.c_str());
			Maillogfile->log_to_file_start();
			REQ(set_button, wf->xmtlock, 0);
			active_modem->set_freqlock(false);
			LOG_INFO("%s", "ARQ is set to pskmail client");
		} else if (strCmdText == "normal") {
			mailserver = false;
			mailclient = false;
			if (Maillogfile) {
				delete Maillogfile;
				Maillogfile = 0;
			}
			REQ(set_button, wf->xmtlock, 0);
			active_modem->set_freqlock(false);
			LOG_INFO("%s", "ARQ is reset to normal ops");
		} else if ((idxSubCmd = strCmdText.find("<mode>")) != string::npos) {
			idxSubCmdEnd = strCmdText.find("</mode>");
			if (	idxSubCmdEnd != string::npos &&
				idxSubCmdEnd > idxSubCmd ) {
				strSubCmd = strCmdText.substr(idxSubCmd + 6, idxSubCmdEnd - idxSubCmd - 6);
				ParseMode(strSubCmd);
				LOG_INFO("%s %s", "ARQ mode ", strSubCmd.c_str());
			}
		} else if ((idxSubCmd = strCmdText.find("<rsid>")) != string::npos) {
			idxSubCmdEnd = strCmdText.find("</rsid>");
			if (	idxSubCmdEnd != string::npos &&
				idxSubCmdEnd > idxSubCmd ) {
				strSubCmd = strCmdText.substr(idxSubCmd + 6, idxSubCmdEnd - idxSubCmd - 6);
				ParseRSID(strSubCmd);
				LOG_INFO("%s %s", "ARQ rsid ", strSubCmd.c_str());
			}
		} else if ((idxSubCmd = strCmdText.find("<txrsid>")) != string::npos) {
			idxSubCmdEnd = strCmdText.find("</txrsid>");
			if (	idxSubCmdEnd != string::npos &&
				idxSubCmdEnd > idxSubCmd ) {
				strSubCmd = strCmdText.substr(idxSubCmd + 8, idxSubCmdEnd - idxSubCmd - 8);
				ParseTxRSID(strSubCmd);
				LOG_INFO("%s %s", "ARQ txrsid ", strSubCmd.c_str());
			}
		} else if (strCmdText == "abort") {
			LOG_INFO("%s", "Abort current ARQ ops");
			abort_flag = true;
		}

		toparse.erase(idxCmd, idxCmdEnd - idxCmd + 6);
		while (toparse[0] == '\n' || toparse[0] == '\r') toparse.erase(0, 1);

		idxCmd = toparse.find("<cmd>");
		idxCmdEnd = toparse.find("</cmd>");
	}
}

#define TIMEOUT 180 // 3 minutes

//======================================================================
// Gmfsk ARQ file i/o used only on Linux
//======================================================================
// checkTLF
// look for files named
//	TLFfldigi ==> tlfio is true and
//			  ==> mailclient is true
// in $HOME

void checkTLF() {
	static	string TLFfile;
	static	string TLFlogname;
	ifstream testFile;

	tlfio = mailserver = mailclient = false;

	TLFfile.assign(PskMailDir);
	TLFfile.append("TLFfldigi");

	testFile.open(TLFfile.c_str());
	if (testFile.is_open()) {
		testFile.close();
		mailclient = true;
		tlfio = true;
		TLFlogname.assign(PskMailDir);
		TLFlogname.append("gMFSK.log");
		Maillogfile = new cLogfile(TLFlogname.c_str());
		Maillogfile->log_to_file_start();
	}
}

static bool TLF_arqRx()
{
	/// The mutex is automatically unlocked when returning.
#if defined(__WOE32__) || defined(__APPLE__)
	return false;
#else
	time_t start_time, prog_time;
	static char mailline[1000];
	static string sAutoFile("");
	sAutoFile.assign(PskMailDir);
	sAutoFile.append("gmfsk_autofile");

	ifstream autofile(sAutoFile.c_str());
	if(autofile) {
		time(&start_time);
		while (!autofile.eof()) {
			memset(mailline, 0, sizeof(mailline));
			autofile.getline(mailline, 998); // leave space for "\n" and null byte
			txstring.append(mailline);
			txstring.append("\n");
			time(&prog_time);
			if (prog_time - start_time > TIMEOUT) {
				LOG_ERROR("TLF file I/O failure");
				autofile.close();
				std::remove (sAutoFile.c_str());
				return false;
			}
		}
		autofile.close();
		std::remove (sAutoFile.c_str());

		parse_arqtext(txstring);

		if (abort_flag) {
			AbortARQ();
			abort_flag = false;
			return true;
		}

		if (arqtext.empty() && !txstring.empty()) {
			guard_lock arq_rx_lock(&arq_rx_mutex);
			arqtext = txstring;
			if (mailserver && progdefaults.PSKmailSweetSpot)
				active_modem->set_freq(progdefaults.PSKsweetspot);
			pText = 0;
			arq_text_available = true;
			active_modem->set_stopflag(false);
			start_tx();
			txstring.clear();
		}
	}
	return true;
#endif
}

//======================================================================
// Auto transmit of file contained in WRAP_auto_dir
//======================================================================
bool WRAP_auto_arqRx()
{
	time_t start_time, prog_time;
	static char mailline[1000];
	static string sAutoFile("");
	sAutoFile.assign(FLMSG_WRAP_auto_dir);
	sAutoFile.append("wrap_auto_file");

	ifstream autofile;
	autofile.open(sAutoFile.c_str());
	if (!autofile) {
		sAutoFile.assign(WRAP_auto_dir);
		sAutoFile.append("wrap_auto_file");
		autofile.open(sAutoFile.c_str());
	}
	if(autofile) {
		/// Mutex is unlocked when leaving the block.
		guard_lock arq_rx_lock(&arq_rx_mutex);
		txstring.clear();
		time(&start_time);
		while (!autofile.eof()) {
			memset(mailline,0,1000);
			autofile.getline(mailline, 998); // leave space for "\n" and null byte
			txstring.append(mailline);
			txstring.append("\n");
			time(&prog_time);
			if (prog_time - start_time > TIMEOUT) {
				LOG_ERROR("autowrap file I/O failure");
				autofile.close();
				std::remove (sAutoFile.c_str());
				return false;
			}
		}
		autofile.close();
		std::remove (sAutoFile.c_str());

		if (!txstring.empty()) {
			arqtext.assign("\n....start\n");
			arqtext.append(txstring);
			arqtext.append("\n......end\n");
			pText = 0;
			arq_text_available = true;
			LOG_DEBUG("%s", arqtext.c_str());
			start_tx();
			txstring.clear();
			return true;
		}
	}
	return false;
}

//======================================================================
// Socket ARQ i/o used on all platforms
//======================================================================

#define ARQLOOP_TIMING 100 // msec
#define CLIENT_TIMEOUT 5 // timeout after N secs

struct ARQCLIENT { Socket sock; time_t keep_alive; };
static string errstring;

static pthread_t* arq_socket_thread = 0;
ARQ_SOCKET_Server* ARQ_SOCKET_Server::inst = 0;
static std::vector<ARQCLIENT> arqclient; // Protected by arq_mutex

void arq_run(Socket);

ARQ_SOCKET_Server::ARQ_SOCKET_Server()
{
	server_socket = new Socket;
	arq_socket_thread = new pthread_t;
	run = true;
}

ARQ_SOCKET_Server::~ARQ_SOCKET_Server()
{
	run = false;
	if (arq_socket_thread) {
		CANCEL_THREAD(*arq_socket_thread);
		pthread_join(*arq_socket_thread, NULL);
		delete arq_socket_thread;
		arq_socket_thread = 0;
	}
	delete server_socket;
}

bool ARQ_SOCKET_Server::start(const char* node, const char* service)
{
	if (inst) return false;

	inst = new ARQ_SOCKET_Server;

	try {
		inst->server_socket->open(Address(node, service));
		inst->server_socket->bind();
#ifdef __WOE32__
		inst->server_socket->listen();
		inst->server_socket->set_timeout(0.1);
#endif
	}
	catch (const SocketException& e) {
		errstring.assign("Could not start ARQ server (");
		errstring.append(e.what()).append(")");
		if (e.error() == EADDRINUSE)
			errstring.append("\nMultiple instances of fldigi??");
		LOG_ERROR("%s", errstring.c_str());

		delete arq_socket_thread;
		arq_socket_thread = 0;
		delete inst;
		inst = 0;
		return false;
	}

	return !pthread_create(arq_socket_thread, NULL, thread_func, NULL);
}

void ARQ_SOCKET_Server::stop(void)
{
	// FILEME - uncomment when we have an ARQ_SOCKET_Server than can be
	// interrupted
	//	if (!inst)
	//		return;
	//	delete inst;
	//	inst = 0;
}

void* ARQ_SOCKET_Server::thread_func(void*)
{
	SET_THREAD_ID(ARQSOCKET_TID);

	SET_THREAD_CANCEL();

	// On POSIX we block indefinitely and are interrupted by a signal.
	// On woe32 we block for a short time and test for cancellation.
	while (inst->run) {
		try {
#ifdef __WOE32__
			if (inst->server_socket->wait(0))
				arq_run(inst->server_socket->accept());
#else
			arq_run(inst->server_socket->accept());
			TEST_THREAD_CANCEL();
#endif
		}
		catch (const SocketException& e) {
			if (e.error() != EINTR) {
				errstring = e.what();
				LOG_ERROR("%s", errstring.c_str());
				break;
			}
		}
		catch (...) {
			break;
		}
	}

	{
		/// Mutex is unlocked when leaving the block.
		guard_lock arq_lock(&arq_mutex);

		if (!arqclient.empty()) {
			vector<ARQCLIENT>::iterator p = arqclient.begin();
			while (p != arqclient.end()) {
				try {
					(*p).sock.close();
					arqclient.erase(p);
				}
				catch (...) {;}
				p++;
			}
		}
	}

	inst->server_socket->close();
	return NULL;
}

void arq_reset()
{
	/// Mutex is unlocked when returning from function
	guard_lock arq_rx_lock(&arq_rx_mutex);
	arqmode = mailserver = mailclient = false;
	txstring.clear();
	arqtext.clear();
	pText = 0;
}

void arq_run(Socket s)
{
	/// Mutex is unlocked when returning from function
	guard_lock arq_lock(&arq_mutex);
	struct timeval t = { 0, 20000 };
	s.set_timeout(t);
	s.set_nonblocking();
	ARQCLIENT client;
	client.sock = s;
	client.keep_alive = time(0);
	arqclient.push_back(client);
	arqmode = true;
	vector<ARQCLIENT>::iterator p = arqclient.begin();
	ostringstream outs;
	outs << "Clients: ";
	while (p != arqclient.end()) {
		outs << (*p).sock.fd() << " ";
		p++;
	}
	LOG_INFO("%s", outs.str().c_str());
}

void WriteARQsocket(unsigned char* data, size_t len)
{
	/// Mutex is unlocked when returning from function
	guard_lock arq_lock(&arq_mutex);
	if (arqclient.empty()) return;
	static string instr;
	instr.clear();

	string outs = "";
	for (unsigned int i = 0; i < len; i++)
		outs += asc[data[i] & 0x7F];
	LOG_INFO("%s", outs.c_str());

	vector<ARQCLIENT>::iterator p;
	for (p = arqclient.begin(); p < arqclient.end(); p++) {
		try {
			(*p).sock.wait(1);
			(*p).sock.send(data, len);
			(*p).keep_alive = time(0);
			p++;
		}
		catch (const SocketException& e) {
			LOG_INFO("closing socket fd %d %s", (*p).sock.fd(), e.what());
			try {
				(*p).sock.close();
			} catch (const SocketException& e) {
				LOG_ERROR("Socket error on # %d, %d: %s", (*p).sock.fd(), e.error(), e.what());
			}
			arqclient.erase(p);
		}
	}

	if (arqclient.empty()) arq_reset();
}

void test_arq_clients()
{
	/// Mutex is unlocked when returning from function
	guard_lock arq_lock(&arq_mutex);
	if (arqclient.empty()) return;
	static string instr;
	instr.clear();
	vector<ARQCLIENT>::iterator p;
	p = arqclient.begin();
	time_t now;
	while (p != arqclient.end()) {
		if (difftime(now = time(0), (*p).keep_alive) > CLIENT_TIMEOUT) {
			try {
				(*p).sock.wait(1);
				(*p).sock.send("\0", 1);
				(*p).keep_alive = now;
				p++;
			}
			catch (const SocketException& e) {
				LOG_INFO("socket %d timed out, error %d, %s", (*p).sock.fd(), e.error(), e.what());
				try {
					(*p).sock.close();
				} catch (const SocketException& e) {
					LOG_ERROR("Socket error on # %d, %d: %s", (*p).sock.fd(), e.error(), e.what());
				}
				arqclient.erase(p);
			}
		} else {
			p++;
		}
	}
	if (arqclient.empty()) arq_reset();
}

bool Socket_arqRx()
{
	{
		/// Mutex is unlocked when leaving block
		guard_lock arq_lock(&arq_mutex);
		if (arqclient.empty()) return false;

		static string instr;
		vector<ARQCLIENT>::iterator p = arqclient.begin();
		size_t n = 0;
		instr.clear();

		while (p != arqclient.end()) {
			try {
				(*p).sock.wait(0);
				n = (*p).sock.recv(instr);
				if ( n > 0) {
					txstring.append(instr);
					(*p).keep_alive = time(0);
				}
				p++;
			}
			catch (const SocketException& e) {
				txstring.clear();
				LOG_INFO("closing socket fd %d, %d: %s", (*p).sock.fd(), e.error(), e.what());
				try {
					(*p).sock.close();
				} catch (const SocketException& e) {
					LOG_ERROR("socket error on # %d, %d: %s", (*p).sock.fd(), e.error(), e.what());
				}
				arqclient.erase(p);
			}
		}
		if (arqclient.empty()) arq_reset();

		if(data_io_enabled != ARQ_IO) {
			txstring.clear();
			return true;
		}
	}

	if (!txstring.empty()) parse_arqtext(txstring);

	if (abort_flag) {
		AbortARQ();
		abort_flag = false;
		return true;
	}

	{
		/// Mutex is unlocked when leaving block
		guard_lock arq_rx_lock(&arq_rx_mutex);

		if (txstring.empty()) return false;

		if (arqtext.empty()) {
			arqtext.assign(txstring);
			pText = 0;
			if (mailserver && progdefaults.PSKmailSweetSpot)
				active_modem->set_freq(progdefaults.PSKsweetspot);
			start_tx();
		} else {
			arqtext.append(txstring);
			if (trx_state != STATE_TX) {
				if (debug_pskmail)
					LOG_INFO("%s","Restarting TX");
				start_tx();
			}
		}
		txstring.clear();

		arq_text_available = true;
		active_modem->set_stopflag(false);

	}
	return true;
}

//======================================================================
// Implementation using thread vice the fldigi timeout facility
//======================================================================

void WriteARQ(unsigned char data)
{
	if (active_modem->get_mode() == MODE_FSQ) return;
	guard_lock tosend_lock(&tosend_mutex);
	tosend += data;
}

void WriteARQ(const char *data)
{
	if (active_modem->get_mode() == MODE_FSQ) return;
	guard_lock tosend_lock(&tosend_mutex);
	tosend.append(data);
}
/*
static void arq_reset_buffers(void)
{
	{
		guard_lock tosend_lock(&tosend_mutex);
		guard_lock arq_lock(&arq_rx_mutex);
		arqtext.clear();
		txstring.clear();
		pText = 0;
		arq_text_available = false;
		enroute.clear();
		tosend.clear();
	}
}
*/
static void *arq_loop(void *args)
{
	SET_THREAD_ID(ARQ_TID);

	for (;;) {
		/* see if we are being canceled */
		if (arq_exit)
			break;

		test_arq_clients();

		{
			/// Mutex is unlocked when exiting block
			guard_lock tosend_lock(&tosend_mutex);
			enroute.clear();
			if (!tosend.empty()) {
				enroute = tosend;
				tosend.clear();
			}

			if (!enroute.empty()) {
				WriteARQsocket((unsigned char*)enroute.c_str(), enroute.length());
			}
		}

		// order of precedence; Socket, Wrap autofile, TLF autofile
		if (!Socket_arqRx())
			if (!WRAP_auto_arqRx())
				TLF_arqRx();

		MilliSleep(ARQLOOP_TIMING);

	}
	// exit the arq thread
	return NULL;
}

bool arq_state(void)
{
	return arq_enabled;
}

void arq_init()
{
	arq_enabled = false;

	txstring.clear();

	if (!ARQ_SOCKET_Server::start( progdefaults.arq_address.c_str(), progdefaults.arq_port.c_str() )) {
		arq_enabled = false;
		return;
	}

	if (pthread_create(&arq_thread, NULL, arq_loop, NULL) < 0) {
		LOG_ERROR("arq init: pthread_create failed");
		arq_enabled = false;
		return;
	}

	arq_enabled = true;
}

void arq_close(void)
{
	if (!arq_enabled) return;

	ARQ_SOCKET_Server::stop();

	// tell the arq thread to kill it self
	arq_exit = true;

	// and then wait for it to die
	pthread_join(arq_thread, NULL);
	arq_enabled = false;
	LOG_INFO("ARQ closed");
	if(data_io_enabled == ARQ_IO)
		data_io_enabled = DISABLED_IO ;
	arq_exit = false;
}

int arq_get_char()
{
	/// Mutex is unlocked when returning from function
	guard_lock arq_rx_lock(&arq_rx_mutex);
	int c = 0;
	if (arq_text_available) {
		if (!arqtext.empty() && pText != arqtext.length()) {
			c = arqtext[pText++] & 0xFF;
		} else {
			arqtext.clear();
			pText = 0;
			arq_text_available = false;
			c = GET_TX_CHAR_ETX;
		}
	}
	return c;
}

//======================================================================
// following function used if the T/R button is pressed to stop a transmission
// that is servicing the ARQ text buffer.  It allows the ARQ client to reset
// itself properly
//======================================================================
void AbortARQ() {
	/// Mutex is unlocked when returning from function
	guard_lock arq_lock(&arq_rx_mutex);
	arqtext.clear();
	txstring.clear();
	pText = 0;
	arq_text_available = false;
}

//======================================================================
// Special notification for PSKMAIL: new mode marked only, in following
// format: "<DC2><Mode:newmode>", with <DC2> = '0x12'.
//======================================================================
void pskmail_notify_rsid(trx_mode mode)
{
	static char buf[64];
	memset(buf, 0, sizeof(buf));
	int n = snprintf(buf, sizeof(buf),
					 "\x12<Mode:%s>\n",
					 mode_info[mode].name);
	if (n > 0 && n < (int)sizeof(buf)) {
		WriteARQ((const char *)buf);
		REQ(&FTextBase::addstr, ReceiveText, buf, FTextBase::CTRL);
		LOG_INFO("%s", buf);
	}
}

//======================================================================
// Special notification for PSKMAIL: signal to noise measured by decoder
// format "<DC2><s2n: CC, A.a, D.d>"
// where CC = count, A.a = average s/n, D.d = Std dev of s/n
//======================================================================
void pskmail_notify_s2n(double s2n_ncount, double s2n_avg, double s2n_stddev)
{
	static char buf[64];
	memset(buf, 0, sizeof(buf));
	int n = snprintf(buf, sizeof(buf),
					 "\x12<s2n: %1.0f, %1.1f, %1.1f>\n",
					 s2n_ncount, s2n_avg, s2n_stddev);
	if (n > 0 && n < (int)sizeof(buf)) {
		WriteARQ((const char *)buf);
		REQ(&FTextBase::addstr, ReceiveText, buf, FTextBase::CTRL);
		LOG_INFO("%s", buf);
	}
}

