// ----------------------------------------------------------------------------
// arq_io.cxx
//
// support for ARQ server/client system such as pskmail and fl_arq
//
// Copyright (C) 2006-2010
//		Dave Freese, W1HKJ
// Copyright (C) 2008-2010
//		Stelios Bounanos, M0GLD
// Copyright (C) 2009-2010
//		John Douyere, VK2ETA
//
// 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 <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;

//======================================================================
// test code for pskmail eol issues

const char *asc[256] = {
	"<NUL>", "<SOH>", "<STX>", "<ETX>",
	"<EOT>", "<ENQ>", "<ACK>", "<BEL>",
	"<BS>",  "<TAB>", "<LF>",  "<VT>", 
	"<FF>",  "<CR>",  "<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();
	for (size_t i = 0; i < src.length(); i++) retstr.append(asc[(int)src[i]]);
	return retstr;
}

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

static string arqtext;
//string::iterator pText;
size_t pText;

bool arq_text_available = false;
string txstring;

extern void send0x06();
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("PTTTUNE") != string::npos) {
		int msecs = 100;
		if (src.length() > 7)
			sscanf( src.substr(7, src.length() - 7).c_str(), "%d", &msecs);
		REQ_SYNC(&PTT::set, push2talk, true);
		MilliSleep(msecs);
		REQ_SYNC(&PTT::set, push2talk, false);
		LOG_VERBOSE("%s", "ARQ ptt toggled");
		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) {
				while (trx_state != STATE_RX) MilliSleep(50);
				REQ_SYNC(init_modem_sync, mode_info[i].mode, 0);
LOG_INFO("ARQ new modem set to %s", mode_info[i].pskmail_name);
				break;
			}
		}
	}
}

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


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

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

	LOG_DEBUG("Arq text: %s", noctrl(toparse).c_str());

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

	while ( idxCmd != string::npos && idxCmdEnd != string::npos && idxCmdEnd > idxCmd ) {

		LOG_DEBUG("Cmd text: %s", toparse.substr(idxCmd, idxCmdEnd + 6).c_str());
		strCmdText = toparse.substr(idxCmd + 5, idxCmdEnd - idxCmd - 5);
		if (strCmdText == "server" && mailserver == false && mailclient == false) {
			mailserver = true;
			mailclient = false;
			string PskMailLogName = PskMailDir;
			PskMailLogName += "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_DEBUG("%s", "ARQ is set to pskmail server");
		} else if (strCmdText == "client" && mailclient == false && mailserver == false) {
			mailclient = true;
			mailserver = false;
			string PskMailLogName = PskMailDir;
			PskMailLogName += "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_DEBUG("%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_DEBUG("%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);
			}
		} 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);
			}
		} 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);
			}
		}

		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

bool bSend0x06 = false;

//-----------------------------------------------------------------------------
// SysV ARQ used only on Linux / Free-BSD or Unix type OS
//-----------------------------------------------------------------------------

#if !defined(__WOE32__) && !defined(__APPLE__)
void process_msgque()
{
	memset(txmsgst.buffer, 0, ARQBUFSIZ);
	int nbytes = msgrcv (txmsgid, (void *)&txmsgst, ARQBUFSIZ, 0, IPC_NOWAIT);
	if (nbytes > 0) {
		txstring.append(txmsgst.buffer);
		parse_arqtext(txstring);

		if (!bSend0x06 && arqtext.empty() && !txstring.empty()) {
			arqtext = txstring;
			if (mailserver && progdefaults.PSKmailSweetSpot)
				active_modem->set_freq(progdefaults.PSKsweetspot);
			pText = 0;
			arq_text_available = true;
			active_modem->set_stopflag(false);
			LOG_DEBUG("SYSV ARQ string: %s", arqtext.c_str());
			start_tx();
			txstring.clear();
		}
	}
}

bool SysV_arqRx()
{
   	txmsgid = msgget( (key_t) progdefaults.tx_msgid, 0666 );
   	if (txmsgid != -1) {
   		process_msgque();
   		return true;
   	}
	return false;
}
#endif

//-----------------------------------------------------------------------------
// 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() {
	string TLFfile;
	string TLFlogname;
	ifstream testFile;

	tlfio = mailserver = mailclient = false;

	TLFfile = PskMailDir;
	TLFfile += "TLFfldigi";

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

bool TLF_arqRx()
{
	time_t start_time, prog_time;
	static char mailline[1000];
	string sAutoFile = PskMailDir;
	sAutoFile += "gmfsk_autofile";

	ifstream autofile(sAutoFile.c_str());
	if(autofile) {
		arqtext = "";
		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 (arqtext.empty() && !txstring.empty()) {
			arqtext = txstring;
			if (mailserver && progdefaults.PSKmailSweetSpot)
				active_modem->set_freq(progdefaults.PSKsweetspot);
			pText = 0;
			arq_text_available = true;
			active_modem->set_stopflag(false);
			LOG_DEBUG("%s", arqtext.c_str());
			start_tx();
			txstring.clear();
		}

	}
	return true;
}

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

	ifstream autofile(sAutoFile.c_str());
	if(autofile) {
		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 = "\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
//-----------------------------------------------------------------------------

extern void arq_run(Socket s);
extern void arq_stop();

string errstring;
string cmdstring;
string response;
bool isTxChar = false;
bool isCmdChar = false;

bool isNotMULTIPSK = true;

static pthread_t* arq_socket_thread = 0;
ARQ_SOCKET_Server* ARQ_SOCKET_Server::inst = 0;

Socket arqclient;
bool isSocketConnected = false;

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 = "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))
#endif
				arq_run(inst->server_socket->accept());
			TEST_THREAD_CANCEL();
		}
		catch (const SocketException& e) {
			if (e.error() != EINTR) {
				errstring = e.what();
				LOG_ERROR("%s", errstring.c_str());
				break;
			}
		}
		catch (...) {
			break;
		}
	}
	arq_stop();
	inst->server_socket->close();
	return NULL;
}

void arq_run(Socket s)
{
	struct timeval t = { 0, 20000 };
	arqclient = s;
	arqclient.set_timeout(t);
	arqclient.set_nonblocking();
	isSocketConnected = true;
	arqmode = true;
}

void arq_stop()
{
	arqclient.close();
	isSocketConnected = false;
	arqmode = false;
}

void WriteARQsocket(unsigned char* data, size_t len)
{
	if (!isSocketConnected) return;
	try {
		arqclient.send(data, len);
	}
	catch (const SocketException& e) {
		LOG_ERROR("%s", e.what());
		arq_stop();
	}
}

bool Socket_arqRx()
{
	if (!isSocketConnected) return false;

	string instr;

	try {
		size_t n = arqclient.recv(instr);
		if ( n > 0)
			txstring.append(instr);

		if (!bSend0x06 && arqtext.empty() && !txstring.empty()) {
			arqtext = txstring;
			parse_arqtext(arqtext);
			if (!arqtext.empty()) {
				if (mailserver && progdefaults.PSKmailSweetSpot)
					active_modem->set_freq(progdefaults.PSKsweetspot);
				pText = 0;//arqtext.begin();
				arq_text_available = true;
				active_modem->set_stopflag(false);
				LOG_DEBUG("%s", arqtext.c_str());
				start_tx();
			}
			txstring.clear();
			cmdstring.clear();
			return true;
		}
		cmdstring.clear();
		return false;
	}
	catch (const SocketException& e) {
		arq_stop();
		return false;
	}
}

//-----------------------------------------------------------------------------
// Send ARQ characters to ARQ client
//-----------------------------------------------------------------------------
#if !defined(__WOE32__) && !defined(__APPLE__)
void WriteARQSysV(unsigned char data)
{
	rxmsgid = msgget( (key_t) progdefaults.rx_msgid, 0666);
	if ( rxmsgid != -1) {
		rxmsgst.msg_type = 1;
		rxmsgst.c = data;
		msgsnd (rxmsgid, (void *)&rxmsgst, 1, IPC_NOWAIT);
	}
}
#endif

void WriteARQ(unsigned char data)
{
	WriteARQsocket(&data, 1);
#if !defined(__WOE32__) && !defined(__APPLE__)
	WriteARQSysV(data);
#endif
}

//-----------------------------------------------------------------------------
// Write End of Transmit character to ARQ client
//-----------------------------------------------------------------------------

void send0x06()
{
	if (trx_state == STATE_RX) {
		bSend0x06 = false;
		WriteARQ(0x06);
	}
}

// ============================================================================
// Implementation using thread vice the fldigi timeout facility
// ============================================================================
static pthread_t arq_thread;
static pthread_mutex_t arq_mutex = PTHREAD_MUTEX_INITIALIZER;

static void *arq_loop(void *args);

static bool arq_exit = false;
static bool arq_enabled;

static void *arq_loop(void *args)
{
	SET_THREAD_ID(ARQ_TID);

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

		pthread_mutex_lock (&arq_mutex);

		if (bSend0x06)
			send0x06();

#if !defined(__WOE32__) && !defined(__APPLE__)
		// order of precedence; Socket, Wrap autofile, TLF autofile
		if (!Socket_arqRx())
			if (!SysV_arqRx())
				if (!WRAP_auto_arqRx())
					TLF_arqRx();
#else
		if (!Socket_arqRx())
			WRAP_auto_arqRx();
#endif
		pthread_mutex_unlock (&arq_mutex);
		MilliSleep(50);

	}
// exit the arq thread
	return NULL;
}

void arq_init()
{
	arq_enabled = false;

	txstring.clear();
	cmdstring.clear();

	if (!ARQ_SOCKET_Server::start( progdefaults.arq_address.c_str(), progdefaults.arq_port.c_str() ))
		return;

	if (pthread_create(&arq_thread, NULL, arq_loop, NULL) < 0) {
		LOG_ERROR("arq init: pthread_create failed");
		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;

	arq_exit = false;
}

char arq_get_char()
{
	char c = 0;
	pthread_mutex_lock (&arq_mutex);
	if (arq_text_available) {
		if (pText != arqtext.length())
			c = arqtext[pText++];
		else {
			arqtext.clear();
			pText = 0;
			bSend0x06 = true;
			arq_text_available = false;
			c = 0x03;
		}
	}
	pthread_mutex_unlock (&arq_mutex);
	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() {
	pthread_mutex_lock (&arq_mutex);
	arqtext.clear();
	pText = 0;
	arq_text_available = false;
	bSend0x06 = true;
	pthread_mutex_unlock (&arq_mutex);
}

// Special notification for PSKMAIL: new mode marked only, in following
// format: "<DC2><Mode:newmode>", with <DC2> = '0x12'.
void pskmail_notify_rsid(trx_mode mode)
{
	char buf[64];
	int n = snprintf(buf, sizeof(buf), "%c<Mode:%s>\n", 0x12, mode_info[mode].name);
	if (n > 0 && n < (int)sizeof(buf)) {
		WriteARQsocket((unsigned char*)buf, n);
#if !defined(__WOE32__) && !defined(__APPLE__)
		for (int ii=0; ii < n; ii++)
			WriteARQSysV((unsigned char)buf[ii]);
#endif
		ReceiveText->add(buf, FTextBase::CTRL);
	}
}

// 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)
{
	char buf[64];
	int n = snprintf(buf, sizeof(buf), "%c<s2n: %1.0f, %1.1f, %1.1f>",
			 0x12, s2n_ncount, s2n_avg, s2n_stddev);
	if (n > 0 && n < (int)sizeof(buf)) {
		WriteARQsocket((unsigned char*)buf, n);
#if !defined(__WOE32__) && !defined(__APPLE__)
		for (int ii=0; ii < n; ii++)
			WriteARQSysV((unsigned char)buf[ii]);
#endif
		ReceiveText->add(buf, FTextBase::CTRL);
	}
}

