// ----------------------------------------------------------------------------
// logsupport.cxx
//
// Copyright (C) 2006-2010
//		Dave Freese, W1HKJ
// Copyright (C) 2008-2009
//		Stelios Bounanos, M0GLD
//
// 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>

#include <string>
#include <cctype>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fstream>
#include <set>
#include <sstream>
#include <vector>
#include <algorithm>

#include "main.h"
#include "trx.h"
#include "debug.h"
#include "macros.h"
#include "status.h"
#include "date.h"

#include "logger.h"
#include "n3fjp_logger.h"
#include "adif_io.h"
#include "textio.h"
#include "logbook.h"
#include "logsupport.h"
#include "rigsupport.h"
#include "fd_logger.h"

#include "fl_digi.h"
#include "confdialog.h"
#include "fileselect.h"
#include "configuration.h"
#include "ui_colors.h"
#include "main.h"
#include "locator.h"
#include "icons.h"
#include "gettext.h"
#include "util.h"
#include "qrunner.h"
#include "flmisc.h"

#include "network.h"

#include "timeops.h"

#include "strutil.h"

#include <FL/filename.H>
#include <FL/fl_ask.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Text_Display.H>
#include <FL/Fl_Text_Editor.H>
#include <FL/Fl_Button.H>

extern std::vector<int> lotw_recs_sent;

cQsoDb		qsodb;
cAdifIO		adifFile;
cTextFile	txtFile;

std::string		logbook_filename;

sorttype lastsort = SORTDATE;
bool callfwd = true;
bool modefwd = true;
bool freqfwd = true;

int editNbr = 0;

void restore_sort();
void addBrowserRow(cQsoRec *, int);
void adjustBrowser(bool keep_pos = false);

// convert to and from "00:00:00" <=> "000000"
const char *timeview(const char *s)
{
	static char ds[9];
	int len = strlen(s);
	strcpy(ds, "00:00:00");
	if (len < 4)
		return ds;
	ds[0] = s[0]; ds[1] = s[1];
	ds[3] = s[2]; ds[4] = s[3];
	if (len < 6)
		return ds;
	ds[6] = s[4];
	ds[7] = s[5];
	return ds;
}

const char *timestring(const char *s)
{
	static char ds[7];
	int len = strlen(s);
	if (len <= 4) return s;
	ds[0] = s[0]; ds[1] = s[1];
	ds[2] = s[3]; ds[3] = s[4];
	if (len < 8) {
		ds[4] = ds[5] = '0';
		ds[6] = 0;
		return ds;
	}
	ds[4] = s[6];
	ds[5] = s[7];
	ds[6] = 0;
	return ds;
}

const char *timeview4(const char *s)
{
	static char ds[6];
	int len = strlen(s);
	strcpy(ds, "00:00");
	if (len < 5)
		return ds;
	ds[0] = s[0]; ds[1] = s[1];
	ds[3] = s[2]; ds[4] = s[3];
	return ds;
}

const char *time4(const char *s)
{
	static char ds[5];
	int len = strlen(s);
	if (len <= 4)
		return ds;
	memset(ds, 0, 5);
	strncpy(ds, s, 4);
	return ds;
}

void Export_CSV()
{
	if (chkExportBrowser->nchecked() == 0) return;

	cQsoRec *rec;

	std::string title = _("Export to CSV file");
	std::string filters = "CSV\t*.csv";
#ifdef __APPLE__
	filters.append("\n");
#endif
	const char* p = FSEL::saveas( title.c_str(), filters.c_str(), "export.csv");

	if (!p) return;
	if (!*p) return;

	for (int i = 0; i < chkExportBrowser->FLTK_nitems(); i++) {
		if (chkExportBrowser->checked(i + 1)) {
			rec = qsodb.getRec(i);
			rec->putField(EXPORT, "E");
			qsodb.qsoUpdRec (i, rec);
		}
	}
	std::string sp = p;
	if (sp.find(".csv") == std::string::npos) sp.append(".csv");
	txtFile.writeCSVFile(sp.c_str(), &qsodb);
}

void Export_TXT()
{
	if (chkExportBrowser->nchecked() == 0) return;

	cQsoRec *rec;
	std::string title = _("Export to fixed field text file");
	std::string filters = "TEXT\t*.txt";
#ifdef __APPLE__
	filters.append("\n");
#endif
	const char* p = FSEL::saveas( title.c_str(), filters.c_str(), "export.txt");

	if (!p) return;
	if (!*p) return;

	for (int i = 0; i < chkExportBrowser->FLTK_nitems(); i++) {
		if (chkExportBrowser->checked(i + 1)) {
			rec = qsodb.getRec(i);
			rec->putField(EXPORT, "E");
			qsodb.qsoUpdRec (i, rec);
		}
	}
	std::string sp = p;
	if (sp.find(".txt") == std::string::npos) sp.append(".txt");
	txtFile.writeTXTFile(p, &qsodb);
}

void Export_ADIF()
{
	if (chkExportBrowser->nchecked() == 0) return;

	cQsoRec *rec;

	std::string title = _("Export to ADIF file");
	std::string filters;
	std::string defname;
	filters.assign("ADIF\t*.").append(ADIF_SUFFIX);
#ifdef __APPLE__
	filters.append("\n");
#endif
	defname.assign("export.").append(ADIF_SUFFIX);
	const char* p = FSEL::saveas( title.c_str(), filters.c_str(), defname.c_str());

	if (!p) return;
	if (!*p) return;

	for (int i = 0; i < chkExportBrowser->FLTK_nitems(); i++) {
		if (chkExportBrowser->checked(i + 1)) {
			rec = qsodb.getRec(i);
			rec->putField(EXPORT, "E");
			qsodb.qsoUpdRec (i, rec);
		}
	}
	std::string sp = p;
	std::string temp = ".";
	temp.append(ADIF_SUFFIX);

	if (sp.find(temp) == std::string::npos) sp.append(temp);
	adifFile.writeFile (sp.c_str(), &qsodb);
}

// refresh_logbook_dialog ONLY called as an Fl::awake process
// insures that logbook dialog LOTWSDATE field is updated by
// FLTK UI thread.
// called by Export_LOTW() and saveRecord()

static std::string lotwsdate;
void refresh_logbook_dialog(void *)
{
	inpLOTWsentdate_log->value(lotwsdate.c_str());
	inpLOTWsentdate_log->redraw();
}

void Export_LOTW()
{
	if (chkExportBrowser->nchecked() == 0) return;

	cQsoRec *rec;

	if (str_lotw.empty())
		str_lotw = "Fldigi LoTW upload file\n<ADIF_VER:5>3.1.2\n<EOH>\n";
	std::string adifrec;

	for (int i = 0; i < chkExportBrowser->FLTK_nitems(); i++) {
		if (chkExportBrowser->checked(i + 1)) {
			rec = qsodb.getRec(i);
			rec->putField(EXPORT, "E");
			rec->putField(LOTWSDATE, zdate());
			qsodb.qsoUpdRec (i, rec);
			lotw_recs_sent.push_back(i);
			if (i == editNbr) {
				lotwsdate = rec->getField(LOTWSDATE);
				Fl::awake(refresh_logbook_dialog);
			}
			adifrec = lotw_rec(*rec);
			if (adifrec.empty()) {
				LOG_INFO("%s", "Invalid LOTW record");
			} else
				str_lotw.append(adifrec);
		}
	}
}

Fl_Double_Window *lotw_review_dialog = 0;
static Fl_Text_Buffer *buff = 0;
static Fl_Text_Editor *disp = 0;
static Fl_Button *lotw_close_review = 0;
static Fl_Button *lotw_save_review = 0;
static Fl_Button *lotw_clear_review = 0;

void cb_lotw_close_review(Fl_Button *, void *)
{
	lotw_review_dialog->hide();
	delete lotw_review_dialog;
	lotw_review_dialog = 0;
	lotw_close_review = 0;
	buff = 0;
	disp = 0;
}

void cb_lotw_save_review(Fl_Button *, void *)
{
	str_lotw = buff->text();
}

void cb_lotw_clear_review(Fl_Button *, void *)
{
	buff->text("");
}

void cb_review_lotw()
{
	if (str_lotw.empty()) return;

	if (!lotw_review_dialog) {
		lotw_review_dialog = new Fl_Double_Window(50,50, 640, 400, _("LoTW Review"));
		lotw_review_dialog->begin();

		buff = new Fl_Text_Buffer();
		disp = new Fl_Text_Editor(4, 4, 632, 364);
		disp->textfont(FL_SCREEN);
		disp->buffer(buff); // attach text buffer to display widget
		lotw_close_review = new Fl_Button(576, 372, 60, 24, _("Close"));
		lotw_close_review->callback((Fl_Callback *)cb_lotw_close_review);

		lotw_clear_review = new Fl_Button(4, 372, 60, 24, _("Clear"));
		lotw_clear_review->callback((Fl_Callback *)cb_lotw_clear_review);

		lotw_save_review = new Fl_Button(lotw_review_dialog->w()/2-30, 372, 60, 24, _("Save"));
		lotw_save_review->callback((Fl_Callback *)cb_lotw_save_review);

		lotw_review_dialog->end();
	}
	buff->text(str_lotw.c_str());

	lotw_review_dialog->show();
}

void cb_send_lotw()
{
	send_to_lotw(NULL);
}

static savetype export_to = ADIF;

void Export_log()
{
	if (export_to == LOTW) Export_LOTW();
	else if (export_to == ADIF) Export_ADIF();
	else if (export_to == CSV) Export_CSV();
	else Export_TXT();
}

void saveLogbook(bool force)
{
	if (!force && !qsodb.isdirty()) return;
	if (!force && progdefaults.NagMe)
		if (!fl_choice2(_("Save changed Logbook?"), _("No"), _("Yes"), NULL))
			return;

	qsodb.isdirty(0);
	restore_sort();

	if (force)
		adifFile.writeLog (logbook_filename.c_str(), &qsodb, true);
	else
		adifFile.writeLog (logbook_filename.c_str(), &qsodb);

}

static void dxcc_entity_cache_clear(void);
static void dxcc_entity_cache_add(cQsoRec* r);
static void dxcc_entity_cache_rm(cQsoRec* r);
static void dxcc_entity_cache_add(cQsoDb& db);

void cb_mnuNewLogbook(Fl_Menu_* m, void* d){
	saveLogbook(true);

	std::string title = _("Create new logbook file");
	std::string filter;
	filter.assign("ADIF\t*.").append(ADIF_SUFFIX);
#ifdef __APPLE__
	filter.append("\n");
#endif

	logbook_filename = LogsDir;
	logbook_filename.append("newlog.").append(ADIF_SUFFIX);

	const char* p = FSEL::saveas( title.c_str(), filter.c_str(), logbook_filename.c_str());
	if (!p) return;
	if (!*p) return;

	std::string temp = p;
	std::string suffix = ".";
	suffix.append(ADIF_SUFFIX);
	if (temp.find(suffix) == std::string::npos) temp.append(suffix);

	FILE *testopen = fl_fopen(temp.c_str(), "r");
	if (testopen) {
		std::string warn = logbook_filename;
		int ans = fl_choice2(
					_("%s exists, overwrite?"),
					_("No"), _("Yes"), NULL,
					temp.c_str());
		if (!ans) return;
		fclose(testopen);
	}

	progdefaults.logbookfilename = logbook_filename = temp;

	dlgLogbook->label(fl_filename_name(logbook_filename.c_str()));
	txtLogFile->value(logbook_filename.c_str());
	txtLogFile->redraw();

	progdefaults.changed = true;
	qsodb.deleteRecs();
	dxcc_entity_cache_clear();
	wBrowser->clear();
	clearRecord();
	qsodb.isdirty(1);
	saveLogbook();
}

void adif_read_OK()
{
	if (qsodb.nbrRecs() == 0)
		adifFile.writeFile(logbook_filename.c_str(), &qsodb);
	dxcc_entity_cache_clear();
	dxcc_entity_cache_add(qsodb);
	qsodb.isdirty(0);
	restore_sort();
	activateButtons();
	loadBrowser();
}

void cb_mnuOpenLogbook(Fl_Menu_* m, void* d)
{
	std::string title = _("Open logbook file");
	std::string filter;
	filter.assign("ADIF file\t*.{adi,adif}");
#ifdef __APPLE__
	filter.append("\n");
#endif

	std::string deffilename = LogsDir;
	deffilename.append(fl_filename_name(logbook_filename.c_str()));

	const char* p = FSEL::select( title.c_str(), filter.c_str(), deffilename.c_str());
	if (!p) return;
	if (!*p) return;

	saveLogbook(true);

	qsodb.deleteRecs();

	logbook_filename = p;
	progdefaults.logbookfilename = logbook_filename;
	progdefaults.changed = true;

	adifFile.readFile (logbook_filename.c_str(), &qsodb);
	dlgLogbook->label(fl_filename_name(logbook_filename.c_str()));

	txtLogFile->value(logbook_filename.c_str());
	txtLogFile->redraw();

	qsodb.isdirty(0);

}

void cb_mnuSaveLogbook(Fl_Menu_*m, void* d) {
	std::string title = _("Save logbook file");
	std::string filter;
	filter.assign("ADIF\t*.").append(ADIF_SUFFIX);
#ifdef __APPLE__
	filter.append("\n");
#endif
	std::string deffilename = LogsDir;
	deffilename.append(fl_filename_name(logbook_filename.c_str()));

	const char* p = FSEL::saveas( title.c_str(), filter.c_str(), deffilename.c_str());

	if (!p) return;
	if (!*p) return;

	logbook_filename = p;
	std::string temp = ".";
	temp.append(ADIF_SUFFIX);
	if (logbook_filename.find(temp) == std::string::npos)
		logbook_filename.append(temp);

	progdefaults.logbookfilename = logbook_filename;
	progdefaults.changed = true;

	dlgLogbook->label(fl_filename_name(logbook_filename.c_str()));
	txtLogFile->value(logbook_filename.c_str());
	txtLogFile->redraw();

//	cQsoDb::reverse = false;
//	qsodb.SortByDate(progdefaults.sort_date_time_off);

	qsodb.isdirty(0);
	restore_sort();
	adifFile.writeLog (logbook_filename.c_str(), &qsodb);

}

//======================================================================
// separate thread for performing the database merger
//
// thread 'merge_thread' is instantiated for a database file merger
// either on failure or successful merger the thread signals the main
// UI thread to close the merge_thread and release all of it's resources
//
// merge_thread is not re-entrant.  Only a single instance of the thread
// is allowed.
//
// the user will be notified if an attempt is made to start a new merger
// while one is already in progress.
//
//======================================================================

pthread_t* MERGE_thread = 0;
pthread_mutex_t MERGE_mutex = PTHREAD_MUTEX_INITIALIZER;

static std::string mrg_fname;
static std::string disptxt;
static bool   abort_merger;
static int num_merge_recs;
static float read_secs;

static void merge_announce_1(void *)
{
	static char announce[500];
	snprintf(announce, sizeof(announce),
		"Merging records:\n   File: %s\n", mrg_fname.c_str());
	ReceiveText->addstr(announce);
	ReceiveText->redraw();
	Fl::flush();
}

static void merge_announce_2(void *)
{
	static char announce[500];
	snprintf(announce, sizeof(announce),
		"   Read %d records in %4.1f seconds\n   Merging ... please wait",
		num_merge_recs, read_secs );
	ReceiveText->addstr(announce);
	ReceiveText->redraw();
	Fl::flush();
}

void close_MERGE_thread (void *)
{
	ENSURE_THREAD(FLMAIN_TID);

	if (!MERGE_thread) return;

	pthread_mutex_lock(&MERGE_mutex);
	abort_merger = true;
	pthread_mutex_unlock(&MERGE_mutex);

	pthread_join(*MERGE_thread, NULL);

	delete MERGE_thread;

	MERGE_thread = 0;
	abort_merger = false;

	qsodb.isdirty(1);
	saveLogbook(true); // force the save independent of user settings
	loadBrowser();

	ReceiveText->addstr(disptxt.c_str());
	ReceiveText->redraw();

}

std::string adif_record(cQsoRec *rec)
{
	static char recfield[200];
	static std::string record;
	static std::string sFld;

	record.clear();
	sFld.clear();

	for (int j = 0; fields[j].type != NUMFIELDS; j++) {
		if (strcmp(fields[j].name,"MYXCHG") == 0) continue;
		if (strcmp(fields[j].name,"XCHG1") == 0) continue;
		sFld = rec->getField(fields[j].type);
		if (!sFld.empty()) {
			memset(recfield, 0, 200);
			snprintf(recfield, sizeof(recfield),
				"<%s:%lu>",
				fields[j].name,
				(unsigned long int)sFld.length());
			record.append(recfield).append(sFld);
		}
	}
	record.append("<EOR>\n");
	return record;
}

std::string last_adif_record()
{
	if (qsodb.nbrRecs() < 1) return "NONE";
	cQsoRec *rec = qsodb.getRec(qsodb.nbrRecs() - 1);
	return adif_record(rec);
}

// entire logbook in adif standard format

std::string all_adif_records()
{
	cQsoRec *rec;
	std::string records;

	records.assign("Fldigi logbook records\n<ADIF_VER:5>3.1.2\n<EOH>\n");

	for (int i = 0; i < qsodb.nbrRecs(); i++) {
		rec = qsodb.getRec(i);
		records.append(adif_record(rec));
	}
	records.append("<EOH>\n");

	return records;
}

static void *merge_thread(void *args)
{
	SET_THREAD_ID(ADIF_MERGE_TID);

	static char msg1[200];

	sorttype orig_sort = lastsort;

	int     orig_reverse = cQsoDb::reverse;

	cQsoDb::reverse = false;

	cQsoDb *db = &qsodb;
	cQsoDb *mrgdb = new cQsoDb;
	cQsoDb *merge_dups = new cQsoDb;
	cQsoDb *orig_dups = new cQsoDb;
	cQsoDb *copy = new cQsoDb(db);

	std::string mergedir;
	std::string mrg_dups_name;
	std::string orig_dups_name;
	std::string lg_recs_name;
	std::string fname;
	size_t pname;

	cQsoRec *lastrec = 0;
	cQsoRec *rec_n;
	cQsoRec *rec_m;

	int N;
	int M;
	int n = 0;
	int m = 0;
	int cmp = 0;
	int cmp2 = 0;

	int merged = 0;
	int merge_duplicates = 0;
	int orig_duplicates = 0;

	struct timespec t0, t1, t2;
	float  merger_time = 0;

	Fl::awake(merge_announce_1);

#ifdef _POSIX_MONOTONIC_CLOCK
	clock_gettime(CLOCK_MONOTONIC, &t0);
#else
	clock_gettime(CLOCK_REALTIME, &t0);
#endif

LOG_INFO("MERGE: adifFile.do_readfile(%s)", mrg_fname.c_str());
	adifFile.do_readfile (mrg_fname.c_str(), mrgdb);

#ifdef _POSIX_MONOTONIC_CLOCK
	clock_gettime(CLOCK_MONOTONIC, &t1);
#else
	clock_gettime(CLOCK_REALTIME, &t1);
#endif

	N = copy->nbrRecs();
	M = mrgdb->nbrRecs();

	if (M == 0) {
		disptxt.assign("\n================================================\n");
		disptxt.append("Merge file contains no records\n");
		disptxt.append("\n================================================");
		LOG_INFO("%s", disptxt.c_str());
		disptxt.append("\n");
		goto exit_merge_thread;
	}

	read_secs = t1.tv_sec - t0.tv_sec + (t1.tv_nsec- t0.tv_nsec)/1e9;
	num_merge_recs = M;

	Fl::awake(merge_announce_2);

	disptxt.assign("\n================================================\n");

	copy->SortByCall();
	mrgdb->SortByCall();

	for (int i = 0; i < M; i++) {
		mrgdb->getRec(i)->checkBand();
		mrgdb->getRec(i)->checkDateTimes();
	}

//writeLog("copy.adi", copy);
//writeLog("mrgdb.adi", mrgdb);

	db->clearDatabase();

	for (;;) {

		pthread_mutex_lock(&MERGE_mutex);
		if (abort_merger) goto abort;
		pthread_mutex_unlock(&MERGE_mutex);

		rec_n = copy->getRec(n);
		rec_m = mrgdb->getRec(m);

		if (N == 0) {
			if (m == M) break;
			if (lastrec == 0) {
				db->qsoNewRec(lastrec = rec_m);
				merged++;
			} else {
				cmp = comparebycall(lastrec, rec_m);
				if (cmp != 0) {
					db->qsoNewRec(lastrec = rec_m);
					merged++;
				} else {
					merge_dups->qsoNewRec(rec_m);
					merge_duplicates++;
				}
			}
			m++;
			continue;
		}

		if (n == N) {
			if (m == M) break;
			cmp = comparebycall(lastrec, rec_m);
			if (cmp == 0) {
				merge_dups->qsoNewRec(rec_m);
				merge_duplicates++;
			} else {
				db->qsoNewRec(lastrec = rec_m);
				merged++;
			}
			m++;
			continue;
		}
		if (m == M) {
			if (n == N) break;
			cmp = comparebycall(lastrec, rec_n);
			if (cmp == 0) {
				orig_dups->qsoNewRec(rec_n);
				orig_duplicates++;
			} else {
				db->qsoNewRec(lastrec = rec_n);
			}
			n++;
			continue;
		}

		if (lastrec == 0) {
			cmp = comparebycall(rec_n, rec_m);
			if (cmp < 0) {
				db->qsoNewRec(lastrec = rec_n);
				n++;
				continue;
			}
			if (cmp == 0) {
				db->qsoNewRec(lastrec = rec_n);
				n++;
				merge_dups->qsoNewRec(rec_m);
				m++;
				merge_duplicates++;
				continue;
			} // cmp > 0
			db->qsoNewRec(lastrec = rec_m);
			m++;
		} else { // lastrec exists
			cmp = comparebycall(rec_n, rec_m);
			if (cmp == 0) {
				merge_dups->qsoNewRec(rec_m);
				merge_duplicates++;
				m++;
				cmp2 = comparebycall(lastrec, rec_n);
				if (cmp2 == 0) {
					orig_dups->qsoNewRec(rec_n);
					orig_duplicates++;
				} else
					db->qsoNewRec(lastrec = rec_n);
				n++;
				continue;
			}
			if (cmp < 0) {
				cmp2 = comparebycall(lastrec, rec_n);
				if (cmp2 == 0) {
					orig_dups->qsoNewRec(rec_n);
					orig_duplicates++;
				} else
					db->qsoNewRec(lastrec = rec_n);
				n++;
				continue;
			} // cmp > 0
			cmp2 = comparebycall(lastrec, rec_m);
			if (cmp2 == 0) {
				merge_dups->qsoNewRec(rec_m);
				merge_duplicates++;
			} else if (cmp2 < 0) {
				db->qsoNewRec(lastrec = rec_m);
				merged++;
			}
			m++;
		}
	}

	mergedir = logbook_filename;

	fname = fl_filename_name(mergedir.c_str());
	pname = mergedir.find(fname);
	mergedir.erase(pname);

	if (db->nbrRecs())
		db->SortByCall();

	if (merged > 0) {
		snprintf(msg1, sizeof(msg1), "Merged %d records\n", merged);
		disptxt.append(msg1);
	}

	if (merge_duplicates) {
		merge_dups->SortByCall();
		mrg_dups_name = mergedir;
		mrg_dups_name.append("merge_file_dups");
#ifdef __WIN32__
		mrg_dups_name.append(".adi");
#else
		mrg_dups_name.append(".adif");
#endif
		adifFile.writeLog (mrg_dups_name.c_str(), merge_dups, true);

		snprintf(msg1, sizeof(msg1), "Found %d duplicate records\n", merge_duplicates);
		disptxt.append(msg1);
		snprintf(msg1, sizeof(msg1), "Duplicate's saved in %s\n", mrg_dups_name.c_str());
		disptxt.append(msg1);
	}

	if (orig_duplicates) {
		orig_dups->SortByCall();
		orig_dups_name = mergedir;
		orig_dups_name.append("original_file_dups");
#ifdef __WIN32__
		orig_dups_name.append(".adi");
#else
		orig_dups_name.append(".adif");
#endif
		adifFile.writeLog (orig_dups_name.c_str(), orig_dups, true);

		snprintf(msg1,sizeof(msg1), "Original database had %d duplicates\n", orig_duplicates);
		disptxt.append(msg1);
		snprintf(msg1, sizeof(msg1), "Duplicate's saved in %s\n", orig_dups_name.c_str());
		disptxt.append(msg1);
	}

#ifdef _POSIX_MONOTONIC_CLOCK
	clock_gettime(CLOCK_MONOTONIC, &t2);
#else
	clock_gettime(CLOCK_REALTIME, &t2);
#endif

	merger_time = t2.tv_sec - t0.tv_sec + (t2.tv_nsec- t2.tv_nsec)/1e9;

	snprintf(msg1, sizeof(msg1), "Merger took %4.1f seconds\n", merger_time);
	disptxt.append(msg1);

	disptxt.append("================================================");
	LOG_INFO("%s", disptxt.c_str());
	disptxt.append("\n");

exit_merge_thread:

	delete mrgdb;
	delete orig_dups;
	delete merge_dups;
	delete copy;

	lastsort = orig_sort;
	cQsoDb::reverse = orig_reverse;

	Fl::awake(close_MERGE_thread);

	return NULL;

abort:

	pthread_mutex_unlock(&MERGE_mutex);

	disptxt.assign("Merger aborted");

	delete mrgdb;
	delete orig_dups;
	delete merge_dups;
	delete copy;

	lastsort = orig_sort;
	cQsoDb::reverse = orig_reverse;

	Fl::awake(close_MERGE_thread);

	return NULL;
}

static notify_dialog *alert_window = 0;
void cb_mnuMergeADIF_log(Fl_Menu_* m, void* d) {

	ENSURE_THREAD(FLMAIN_TID);

	if (MERGE_thread) {
		if (!alert_window) alert_window = new notify_dialog;
		alert_window->notify(_("Database merger in progress"), 5.0);
		REQ(show_notifier, alert_window);
		return;
	}

	const char* p = FSEL::select(
			_("Merge ADIF file"),
			"ADIF\t*.{adi,adif}",
			LogsDir.c_str());

	fl_digi_main->redraw();
	Fl::flush();

	if (!p) return;
	if (!*p) return;

	mrg_fname = p;

	abort_merger = false;

	MERGE_thread = new pthread_t;
	if (pthread_create(MERGE_thread, NULL, merge_thread, NULL) != 0) {
		LOG_PERROR("pthread_create");
		return;
	}
	MilliSleep(10);

}
//======================================================================

static std::string lotw_download_name = "";
static cQsoDb *lotw_db = 0;

extern Fl_Button *btn_view_unmatched;

void verify_lotw(void *)
{
	lotw_db = new cQsoDb;
LOG_INFO("VERIFY_LOTW: adifFile.do_readfile(%s", lotw_download_name.c_str());
	adifFile.do_readfile (lotw_download_name.c_str(), lotw_db);

	std::stringstream ss_note;

	if (lotw_db->nbrRecs() == 0) {
		LOG_INFO("%s", _("No records in lotw download file"));
	} else {

		std::string report_fname = LoTWDir;
		report_fname.append("unverified.txt");
		std::ofstream report_file(report_fname.c_str());

		int matchrec;
		cQsoRec *qrec, *lrec;
		int nverified = 0;
		int unverified = 0;
		std::string date;
		std::string qdate;

		for (int i = 0; i < lotw_db->nbrRecs(); i++) {
			lrec = lotw_db->getRec(i);
			date = lrec->getField(QSLRDATE);
			matchrec = qsodb.matched( lrec );
			if (matchrec != -1) {
				qrec = qsodb.getRec(matchrec);
				qdate = qrec->getField(LOTWRDATE);
				if (date != qdate) {
					qrec->putField(STATE, lrec->getField(STATE));
					qrec->putField(GRIDSQUARE, lrec->getField(GRIDSQUARE));
					qrec->putField(CQZ, lrec->getField(CQZ));
					qrec->putField(COUNTRY, lrec->getField(COUNTRY));
					qrec->putField(CNTY, lrec->getField(CNTY));
					qrec->putField(DXCC, lrec->getField(DXCC));
					qrec->putField(DXCC, lrec->getField(DXCC));
					qrec->putField(LOTWRDATE, lrec->getField(QSLRDATE));
				}
				nverified++;
			} else {
				unverified++;
				report_file << lrec->getField(CALL) << ", "
							<< lrec->getField(QSO_DATE) << ", "
							<< lrec->getField(TIME_ON) << ", "
							<< lrec->getField(FREQ) << ", "
							<< lrec->getField(BAND) << ", "
							<< lrec->getField(ADIF_MODE) << "\n";
			}
		}
		report_file.close();
		if (!unverified) remove(report_fname.c_str());

		ss_note	<< "LoTW download contains " << lotw_db->nbrRecs() << " records\n\n"
				<< nverified << " verified\n";
		if (unverified) {
			ss_note	<< unverified << " unverified\n\n"
					<< "Check file " << report_fname;

			btn_view_unmatched->activate();

			if (nverified) qsodb.isdirty(1);
			LOG_INFO("%d records verified", nverified);
			LOG_INFO("%d records unverified", unverified);
		}
		if (!alert_window) alert_window = new notify_dialog;
		alert_window->notify(ss_note.str().c_str(), 15.0);
		REQ(show_notifier, alert_window);
	}
	delete lotw_db;
}

static Fl_Window       *unmatched_viewer = (Fl_Window *)0;
static Fl_Text_Display *viewer = (Fl_Text_Display *)0;
static Fl_Text_Buffer  *buffer = (Fl_Text_Buffer *)0;
static Fl_Button       *close_viewer = (Fl_Button *)0;

void cb_close_viewer(Fl_Button *, void *) {
	if (unmatched_viewer) {
		unmatched_viewer->hide();
		delete unmatched_viewer;
		unmatched_viewer = 0;
	}
}

void cb_btn_view_unmatched(Fl_Button *, void *) {
	btn_view_unmatched->deactivate();
	if (!unmatched_viewer) {
		unmatched_viewer = new Fl_Window(100,100, 400, 500, _("Unmatched LoTW Records"));
		viewer = new Fl_Text_Display(5, 5, 390, 470, "");
		buffer = new Fl_Text_Buffer(8192);
		viewer->buffer(buffer);
		viewer->textfont(progdefaults.LOGBOOKtextfont);
		viewer->textsize(progdefaults.LOGBOOKtextsize);
		close_viewer = new Fl_Button(320, 477, 75, 20, _("Close"));
		close_viewer->callback((Fl_Callback *)cb_close_viewer);
		unmatched_viewer->end();
		unmatched_viewer->resizable(viewer);
	}
	std::string report_fname = LoTWDir;
	report_fname.append("unverified.txt");
	std::ifstream report_file(report_fname.c_str());
	if (report_file) {
		char linebuff[1025];
		viewer->buffer()->text("");
		while (!report_file.eof()) {
			report_file.getline(linebuff, 1024);
			strcat(linebuff, "\n");
			viewer->insert(linebuff);
		}
		report_file.close();
		unmatched_viewer->show();
	}
}

void cb_btn_verify_lotw(Fl_Button *, void *) {

	std::string deffname = LoTWDir;
	deffname.append("lotwreport.adi");

	std::ifstream f(deffname.c_str());

	if (!f) {
		std::string alert = _("\
Could not find LoTW report file.\n\n\
Download from ARRL's LoTW page after logging in at:\n\n\
https://lotw.arrl.org/lotwuser/default\n\n\
Store the report file to the fldigi LOTW folder,\n\n\
naming the file 'lotwreport.adi'");
		if (!alert_window) alert_window = new notify_dialog;
		alert_window->notify(alert.c_str(), 20);
		REQ(show_notifier, alert_window);
		return;
	}

	f.close();
	lotw_download_name = deffname;
	Fl::awake(verify_lotw);
}

void cb_export_date_select() {
	if (qsodb.nbrRecs() == 0) return;
	int start = atoi(inp_export_start_date->value());
	int stop = atoi(inp_export_stop_date->value());

	chkExportBrowser->check_none();

	if (!start || !stop) return;
	int chkdate;

	if (!btn_export_by_date->value()) return;

	cQsoRec *rec;
	for (int i = 0; i < qsodb.nbrRecs(); i++) {
		rec = qsodb.getRec (i);
		chkdate = atoi(rec->getField(progdefaults.sort_date_time_off ? QSO_DATE_OFF : QSO_DATE));
		if (chkdate >= start && chkdate <= stop)
			chkExportBrowser->checked(i+1, 1);
	}

	chkExportBrowser->redraw();
}

inline const char *szfreq(const char *freq)
{
	static char szf[11];
	float f = atof(freq);
	snprintf(szf, sizeof(szf), "%10.6f", f);
	return szf;
}

void cb_Export_log() {
	if (qsodb.nbrRecs() == 0) return;
	cQsoRec *rec;
	char line[80];
	chkExportBrowser->clear();
#ifdef __APPLE__
	chkExportBrowser->textfont(FL_SCREEN_BOLD);
	chkExportBrowser->textsize(12);
#else
	chkExportBrowser->textfont(FL_COURIER);
	chkExportBrowser->textsize(12);
#endif
	for( int i = 0; i < qsodb.nbrRecs(); i++ ) {
		rec = qsodb.getRec (i);
		snprintf(line,sizeof(line),"%8s %4s %-10s %-10s %-s %-s",
			rec->getField(QSO_DATE),
			rec->getField((export_to == LOTW ? TIME_ON : TIME_OFF) ),
			rec->getField(CALL),
			szfreq(rec->getField(FREQ)),
			adif2export(rec->getField(ADIF_MODE)).c_str(),
			adif2submode(rec->getField(ADIF_MODE)).c_str()
		);
		chkExportBrowser->add(line);
	}
	cb_export_date_select();
	wExport->show();
}

void cb_mnuExportADIF_log(Fl_Menu_* m, void* d) {
	export_to = ADIF;
	cb_Export_log();
}

void cb_mnuExportCSV_log(Fl_Menu_* m, void* d) {
	export_to = CSV;
	cb_Export_log();
}

void cb_btnExportLoTW() {
	export_to = LOTW;
	cb_Export_log();
}

void cb_mnuExportTEXT_log(Fl_Menu_* m, void *d) {
	export_to = TEXT;
	cb_Export_log();
}


void cb_mnuShowLogbook(Fl_Menu_* m, void* d)
{
	dlgLogbook->resize(
		progStatus.logbook_x, progStatus.logbook_y,
		progStatus.logbook_w, progStatus.logbook_h);

	dlgLogbook->show();
}

enum State {VIEWREC, NEWREC};
static State logState = VIEWREC;

void activateButtons()
{
	if (logState == NEWREC) {
		bNewSave->label(_("Save"));
		bUpdateCancel->label(_("Cancel"));
		bUpdateCancel->activate();
		bDelete->deactivate ();
		bClearLogbook->deactivate();
		bSearchNext->deactivate ();
		bSearchPrev->deactivate ();
		inpDate_log->take_focus();
		return;
	}
	bNewSave->label(_("New"));
	bUpdateCancel->label(_("Update"));
	bClearLogbook->activate();
	if (qsodb.nbrRecs() > 0) {
		bDelete->activate();
		bUpdateCancel->activate();
		bSearchNext->activate ();
		bSearchPrev->activate ();
		wBrowser->take_focus();
	} else {
		bDelete->deactivate();
		bUpdateCancel->deactivate();
		bSearchNext->deactivate();
		bSearchPrev->deactivate();
	}
}

void cb_btnNewSave(Fl_Button* b, void* d) {
	if (logState == VIEWREC) {
		logState = NEWREC;
		clearRecord();
		activateButtons();
	} else {
		saveRecord();
		qsodb.SortByDate(progdefaults.sort_date_time_off);
		loadBrowser();
		logState = VIEWREC;
		activateButtons();
	}
}

void cb_btnUpdateCancel(Fl_Button* b, void* d) {
	if (logState == NEWREC) {
		logState = VIEWREC;
		activateButtons ();
	} else {
		updateRecord();
		wBrowser->take_focus();
	}
}

void cb_btnDelete(Fl_Button* b, void* d) {
	deleteRecord();
	wBrowser->take_focus();
}

void cb_btnClearLogbook(Fl_Button* b, void* d) {
	if (fl_choice2(_("Really clear all records from this logbook?"),
		       _("Yes"), _("No"), NULL))
		return;

	qsodb.deleteRecs();
	dxcc_entity_cache_clear();
	wBrowser->clear();
	clearRecord();
	adjustBrowser(false);
	activateButtons();
	adifFile.writeFile(logbook_filename.c_str(), &qsodb);
	wBrowser->take_focus();
}

void restore_sort()
{
	switch (lastsort) {
	case SORTCALL :
		cQsoDb::reverse = callfwd;
		qsodb.SortByCall();
		break;
	case SORTDATE :
		cQsoDb::reverse = progStatus.logbook_reverse;
		qsodb.SortByDate(progdefaults.sort_date_time_off);
		break;
	case SORTFREQ :
		cQsoDb::reverse = freqfwd;
		qsodb.SortByFreq();
		break;
	case SORTMODE :
		cQsoDb::reverse = modefwd;
		qsodb.SortByMode();
		break;
	default: break;
	}
}

void cb_SortByCall (void) {
	if (lastsort == SORTCALL)
		callfwd = !callfwd;
	else {
		callfwd = false;
		lastsort = SORTCALL;
	}
	cQsoDb::reverse = callfwd;
	qsodb.SortByCall();
	loadBrowser();
}

void cb_SortByDate (void) {
	if (lastsort == SORTDATE)
		progStatus.logbook_reverse = !progStatus.logbook_reverse;
	else {
		lastsort = SORTDATE;
	}
	cQsoDb::reverse = progStatus.logbook_reverse;
	qsodb.SortByDate(progdefaults.sort_date_time_off);
	loadBrowser();
}

void reload_browser()
{
	qsodb.SortByDate(progdefaults.sort_date_time_off);
	loadBrowser();
}

void cb_SortByMode (void) {
	if (lastsort == SORTMODE)
		modefwd = !modefwd;
	else {
		modefwd = false;
		lastsort = SORTMODE;
	}
	cQsoDb::reverse = modefwd;
	qsodb.SortByMode();

	loadBrowser();
}

void cb_SortByFreq (void) {
	if (lastsort == SORTFREQ)
		freqfwd = !freqfwd;
	else {
		freqfwd = false;
		lastsort = SORTFREQ;
	}
	cQsoDb::reverse = freqfwd;
	qsodb.SortByFreq();

	loadBrowser();
}

void show_dup(void *dup)
{
	int cdup = 0;
	if (dup == (void *)1) cdup = 1;
	if (dup == (void *)2) cdup = 2;

	Fl_Color call_clr = RGBCOLOR( LOGGINGcolor );
	Fl_Color dup_clr = RGBCOLOR( dup_color );
	Fl_Color pdup_clr = RGBCOLOR( possible_dup_color );

	inpCall1->color(cdup == 1 ? dup_clr : cdup == 2 ? pdup_clr : call_clr);
	inpCall2->color(cdup == 1 ? dup_clr : cdup == 2 ? pdup_clr : call_clr);
	inpCall3->color(cdup == 1 ? dup_clr : cdup == 2 ? pdup_clr : call_clr);
	inpCall4->color(cdup == 1 ? dup_clr : cdup == 2 ? pdup_clr : call_clr);

	inpCall1->redraw();
	inpCall2->redraw();
	inpCall3->redraw();
	inpCall4->redraw();
}

void DupCheck()
{
	size_t dup = 0;

	if (n3fjp_connected) {
		show_dup(0);
		n3fjp_dupcheck();
		return;
	}

	if ( FD_logged_on && strlen(inpCall->value()) > 2)
		dup = FD_dupcheck();
	else if ( progdefaults.xml_logbook)
		dup = xml_check_dup();
	else {
// check for call only for possible dup returns 2 if possible
		dup = qsodb.duplicate(inpCall->value());
		if (dup && qsodb.duplicate(
				inpCall->value(),
				zdate(), ztime(), progdefaults.timespan, progdefaults.duptimespan,
				inpFreq->value(), progdefaults.dupband,
				inpState->value(), progdefaults.dupstate,
				mode_info[active_modem->get_mode()].adif_name, progdefaults.dupmode,
				inpXchgIn->value(), progdefaults.dupxchg1 ) )
			dup = 1;
	}
	show_dup((void*)dup);
}

cQsoRec* SearchLog(const char *callsign)
{
	if (progdefaults.xml_logbook)
		return search_fllog(callsign);

	size_t len = strlen(callsign);
	char* re = new char[len + 3];
	snprintf(re, len + 3, "^%s$", callsign);

	int row = 0, col = 2;
	return wBrowser->search(row, col, !cQsoDb::reverse, re) ? qsodb.getRec(row) : 0;
}

void SearchLastQSO(const char *callsign)
{
	if (n3fjp_connected) {
		n3fjp_get_record(callsign);
		return;
	}

	size_t len = strlen(callsign);

	if (len < 3)
		return;

	if (progdefaults.xml_logbook) {
		if(xml_get_record(callsign))
			return;
	}

	Fl::focus(inpCall);

	char* re = new char[len + 3];
	snprintf(re, len + 3, "^%s$", callsign);

	int row = 0, col = 2;
	if (wBrowser->search(row, col, !cQsoDb::reverse, re)) {
		wBrowser->GotoRow(row);
		inpName->value(inpName_log->value());
		inpQTH->value(inpQth_log->value());
		inpLoc1->value(inpLoc_log->value());
		inpLoc1->position (0);
		inpState->value(inpState_log->value());
		inpState->position (0);
		inpVEprov->value(inpVE_Prov_log->value ());
		inpVEprov->position (0);
		inpCounty->value(inpCNTY_log->value ());
		inpCounty->position (0);
		cboCountry->value(inpCountry_log->value ());
		inpSearchString->value(callsign);
		inpNotes->value(inpNotes_log->value ());
		if (inpLoc->value()[0]) {
			double lon1, lat1, lon2, lat2;
			double azimuth, distance;
			char szAZ[4];
			if ( QRB::locator2longlat(&lon1, &lat1, progdefaults.myLocator.c_str()) == QRB::QRB_OK &&
				 QRB::locator2longlat(&lon2, &lat2, inpLoc->value()) == QRB::QRB_OK &&
				 QRB::qrb(lon1, lat1, lon2, lat2, &distance, &azimuth) == QRB::QRB_OK ) {
				snprintf(szAZ,sizeof(szAZ),"%0.f", azimuth);
				inpAZ->value(szAZ);
				inpAZ->position (0);
			} else
				inpAZ->value("");
		} else
			inpAZ->value("");
	} else {
		clear_log_fields();
	}
	delete [] re;
}

void cb_search(Fl_Widget* w, void*)
{
	const char* str = inpSearchString->value();
	if (!*str)
		return;

	bool rev = w == bSearchPrev;
	int col = 2, row = wBrowser->value() + (rev ? -1 : 1);
	row = WCLAMP(row, 0, wBrowser->rows() - 1);

	if (wBrowser->search(row, col, rev, str))
		wBrowser->GotoRow(row);
	wBrowser->take_focus();
}

int log_search_handler(int)
{
	if (!(Fl::event_state() & FL_CTRL))
		return 0;

	switch (Fl::event_key()) {
	case 's':
		bSearchNext->do_callback();
		break;
	case 'r':
		bSearchPrev->do_callback();
		break;
	default:
		return 0;
	}
	return 1;
}

void cb_btnRetrieve(Fl_Button* b, void* d)
{
	double drf  = atof(inpFreq_log->value());
	if (!drf) return;

	unsigned long long rf1, rf;
	int audio;
	rf1 = drf * 1e6;
	rf = rf1 / 10000ULL;	// Round down
	rf *= 10000ULL;			// to nearest 10 kHz
	audio = rf1 - rf;
// try to keep within normal xcvr bw, 500 - 3000 Hz
	while (audio > 3000) {
		audio -= 3000;
		rf += 3000;
	}
	if (audio < 500) {
		audio += 500;
		rf -= 500;
	}
	qsy(rf, audio);

	std::string mode_name = inpMode_log->value();
	trx_mode m;
	for (m = 0; m < NUM_MODES; m++)
		if (mode_name == mode_info[m].adif_name)
			break;
	// do we have a valid modem?
	if (m < NUM_MODES && active_modem->get_mode() != mode_info[m].mode)
			init_modem(mode_info[m].mode);

	const cQsoRec *qsoPtr = qsodb.getRec(editNbr);
	inpCall->value(qsoPtr->getField(CALL));
	inpName->value (qsoPtr->getField(NAME));

	sDate_on = sDate_off = zdate();
	sTime_on = sTime_off = ztime();

	inpTimeOn->value(inpTimeOff->value());
	inpTimeOn1->value(inpTimeOff->value());
	inpTimeOn2->value(inpTimeOff->value());
	inpTimeOn3->value(inpTimeOff->value());
	inpTimeOn4->value(inpTimeOff->value());
	inpTimeOn5->value(inpTimeOff->value());

	inpState->value (qsoPtr->getField(STATE));
	inpState->position (0);
	inpCounty->value (qsoPtr->getField(CNTY));
	inpCounty->position (0);
	cboCountry->value (qsoPtr->getField(COUNTRY));
	inpXchgIn->value(qsoPtr->getField(XCHG1));
	inpQTH->value (qsoPtr->getField(QTH));
	inpLoc1->value (qsoPtr->getField(GRIDSQUARE));
	inpLoc1->position (0);
	inpNotes->value (qsoPtr->getField(NOTES));

	wBrowser->take_focus();
	if (n3fjp_connected)
		n3fjp_get_record(inpCall->value());

}

void clearRecord() {
	Date tdy;
	inpCall_log->value ("");
	inpName_log->value ("");
	inpDate_log->value (tdy.szDate(2));
	inpDateOff_log->value (tdy.szDate(2));
	inpTimeOn_log->value ("");
	inpTimeOff_log->value ("");
	inpRstR_log->value ("");
	inpRstS_log->value ("");
	inpFreq_log->value ("");
	inpBand_log->value ("");
	inpMode_log->value ("");
	inpQth_log->value ("");
	inpState_log->value ("");
	inpVE_Prov_log->value ("");
	inpCountry_log->value ("");
	inpLoc_log->value ("");

	inpQSLrcvddate_log->value ("");
	inpQSLsentdate_log->value ("");

	inpEQSLrcvddate_log->value ("");
	inpEQSLsentdate_log->value ("");

	inpLOTWrcvddate_log->value ("");
	inpLOTWsentdate_log->value ("");

	inpSerNoOut_log->value ("");
	inpSerNoIn_log->value ("");
	inpXchgIn_log->value("");
	inpMyXchg_log->value(progdefaults.myXchg.c_str());
	inpNotes_log->value ("");
	inpIOTA_log->value("");
	inpDXCC_log->value("");
	inpQSL_VIA_log->value("");
	inpCONT_log->value("");
	inpCNTY_log->value("");
	inpCQZ_log->value("");
	inpITUZ_log->value("");
	inpTX_pwr_log->value("");
	inpSearchString->value ("");

	inp_log_sta_call->value("");
	inp_log_op_call->value("");
	inp_log_sta_qth->value("");
	inp_log_sta_loc->value("");

	inp_log_cwss_serno->value("");
	inp_log_cwss_prec->value("");
	inp_log_cwss_chk->value("");
	inp_log_cwss_sec->value("");

}

void saveRecord() {
	cQsoRec rec;
	rec.putField(CALL, inpCall_log->value());
	rec.putField(NAME, inpName_log->value());
	rec.putField(QSO_DATE, inpDate_log->value());
	rec.putField(QSO_DATE_OFF, inpDateOff_log->value());

	std::string tm = timestring(inpTimeOn_log->value());
	rec.putField(TIME_ON, tm.c_str());
	inpTimeOn_log->value(timeview(tm.c_str()));

	tm = timestring(inpTimeOff_log->value());
	rec.putField(TIME_OFF, tm.c_str());
	inpTimeOff_log->value(timeview(tm.c_str()));

	rec.putField(FREQ, inpFreq_log->value());
	rec.putField(BAND, inpBand_log->value());
	rec.putField(ADIF_MODE, inpMode_log->value());
	rec.putField(QTH, inpQth_log->value());
	rec.putField(STATE, inpState_log->value());
	rec.putField(VE_PROV, inpVE_Prov_log->value());
	rec.putField(COUNTRY, inpCountry_log->value());
	rec.putField(GRIDSQUARE, inpLoc_log->value());
	rec.putField(NOTES, inpNotes_log->value());

	rec.putField(QSLRDATE, inpQSLrcvddate_log->value());
	rec.putField(QSLSDATE, inpQSLsentdate_log->value());

	rec.putField(EQSLRDATE, inpEQSLrcvddate_log->value());
	rec.putField(EQSLSDATE, inpEQSLsentdate_log->value());

	rec.putField(LOTWRDATE, inpLOTWrcvddate_log->value());

	if (progdefaults.submit_lotw) {
		lotwsdate = zdate();
		rec.putField(LOTWSDATE, lotwsdate.c_str());
		Fl::awake(refresh_logbook_dialog);
	} else
		rec.putField(LOTWSDATE, inpLOTWsentdate_log->value());

	rec.putField(RST_RCVD, inpRstR_log->value ());
	rec.putField(RST_SENT, inpRstS_log->value ());
	rec.putField(SRX, inpSerNoIn_log->value());
	rec.putField(STX, inpSerNoOut_log->value());

	if (inpSPCnum->value()[0]) {
		inpXchgIn_log->value(inpSPCnum->value());
		rec.putField(XCHG1, inpSPCnum->value());
	} else if (inpSQSO_category->value()[0]) {
		inpXchgIn_log->value(inpSQSO_category->value());
		rec.putField(XCHG1, inpSQSO_category->value());
	} else
		rec.putField(XCHG1, inpXchgIn_log->value());

	rec.putField(CLASS, inpClass_log->value());
	rec.putField(ARRL_SECT, inpSection_log->value());

	if (!qso_exchange.empty()) {
		rec.putField(MYXCHG, qso_exchange.c_str());
		qso_exchange.clear();
		qso_time.clear();
	} else if (!qso_time.empty()) {
		std::string myexch = inpMyXchg_log->value();
		myexch.append(" ").append(qso_time);
		rec.putField(MYXCHG, myexch.c_str());
		qso_time.clear();
	} else {
		rec.putField(MYXCHG, inpMyXchg_log->value());
	}
	rec.putField(CNTY, inpCNTY_log->value());
	rec.putField(IOTA, inpIOTA_log->value());
	rec.putField(DXCC, inpDXCC_log->value());
	rec.putField(DXCC, inpQSL_VIA_log->value());
	rec.putField(CONT, inpCONT_log->value());
	rec.putField(CQZ, inpCQZ_log->value());
	rec.putField(ITUZ, inpITUZ_log->value());
	rec.putField(TX_PWR, log_power());

	rec.putField(STA_CALL, inp_log_sta_call->value());
	rec.putField(OP_CALL, inp_log_op_call->value());
	rec.putField(MY_CITY, inp_log_sta_qth->value());
	rec.putField(MY_STATE, inp_QP_state_short->value());
	rec.putField(MY_GRID, inp_log_sta_loc->value());

	rec.putField(SS_SERNO, inp_log_cwss_serno->value());
	rec.putField(SS_PREC, inp_log_cwss_prec->value());
	rec.putField(SS_CHK, inp_log_cwss_chk->value());
	rec.putField(SS_SEC, inp_log_cwss_sec->value());

	rec.putField(AGE,  inp_age_log->value());
	rec.putField(TEN_TEN, inp_1010_log->value());
	rec.putField(CHECK, inp_check_log->value());

	rec.putField(TROOPS, inp_log_troop_s->value());
	rec.putField(TROOPR, inp_log_troop_r->value());
	rec.putField(SCOUTS, inp_log_scout_s->value());
	rec.putField(SCOUTR, inp_log_scout_r->value());

	qsodb.qsoNewRec (&rec);

	lotw_recs_sent.push_back(qsodb.nbrRecs() - 1);

	dxcc_entity_cache_add(&rec);
	submit_record(rec);

	qsodb.isdirty(0);

	reload_browser();

	if (qsodb.nbrRecs() == 1)
		adifFile.writeLog (logbook_filename.c_str(), &qsodb);
	else
		adifFile.writeAdifRec(&rec, logbook_filename.c_str());

}


void updateRecord() {
	cQsoRec rec;
	if (qsodb.nbrRecs() == 0) return;
	rec.putField(CALL, inpCall_log->value());
	rec.putField(NAME, inpName_log->value());
	rec.putField(QSO_DATE, inpDate_log->value());
	rec.putField(QSO_DATE_OFF, inpDateOff_log->value());

	std::string tm = timestring(inpTimeOn_log->value());
	rec.putField(TIME_ON, tm.c_str());
	inpTimeOn_log->value(timeview(tm.c_str()));

	tm = timestring(inpTimeOff_log->value());
	rec.putField(TIME_OFF, tm.c_str());
	inpTimeOff_log->value(timeview(tm.c_str()));

	rec.putField(FREQ, inpFreq_log->value());
	rec.putField(BAND, inpBand_log->value());
	rec.putField(ADIF_MODE, inpMode_log->value());
	rec.putField(QTH, inpQth_log->value());
	rec.putField(STATE, inpState_log->value());
	rec.putField(VE_PROV, inpVE_Prov_log->value());
	rec.putField(COUNTRY, inpCountry_log->value());
	rec.putField(GRIDSQUARE, inpLoc_log->value());
	rec.putField(NOTES, inpNotes_log->value());

	rec.putField(QSLRDATE, inpQSLrcvddate_log->value());
	rec.putField(QSLSDATE, inpQSLsentdate_log->value());

	rec.putField(EQSLRDATE, inpEQSLrcvddate_log->value());
	rec.putField(EQSLSDATE, inpEQSLsentdate_log->value());

	rec.putField(LOTWRDATE, inpLOTWrcvddate_log->value());
	rec.putField(LOTWSDATE, inpLOTWsentdate_log->value());

	rec.putField(RST_RCVD, inpRstR_log->value ());
	rec.putField(RST_SENT, inpRstS_log->value ());
	rec.putField(SRX, inpSerNoIn_log->value());
	rec.putField(STX, inpSerNoOut_log->value());
	rec.putField(XCHG1, inpXchgIn_log->value());
	rec.putField(MYXCHG, inpMyXchg_log->value());

	rec.putField(CLASS, inpClass_log->value());
	rec.putField(ARRL_SECT, inpSection_log->value());

	rec.putField(CNTY, inpCNTY_log->value());
	rec.putField(IOTA, inpIOTA_log->value());
	rec.putField(DXCC, inpDXCC_log->value());
	rec.putField(QSL_VIA, inpQSL_VIA_log->value());
	rec.putField(CONT, inpCONT_log->value());
	rec.putField(CQZ, inpCQZ_log->value());
	rec.putField(ITUZ, inpITUZ_log->value());
	rec.putField(TX_PWR, log_power());

	rec.putField(STA_CALL, inp_log_sta_call->value());
	rec.putField(OP_CALL, inp_log_op_call->value());
	rec.putField(MY_CITY, inp_log_sta_qth->value());
	rec.putField(MY_GRID, inp_log_sta_loc->value());

	rec.putField(SS_SERNO, inp_log_cwss_serno->value());
	rec.putField(SS_PREC, inp_log_cwss_prec->value());
	rec.putField(SS_CHK, inp_log_cwss_chk->value());
	rec.putField(SS_SEC, inp_log_cwss_sec->value());

	rec.putField(AGE,  inp_age_log->value());
	rec.putField(TEN_TEN, inp_1010_log->value());
	rec.putField(CHECK, inp_check_log->value());

	rec.putField(TROOPS, inp_log_troop_s->value());
	rec.putField(TROOPR, inp_log_troop_r->value());
	rec.putField(SCOUTS, inp_log_scout_s->value());
	rec.putField(SCOUTR, inp_log_scout_r->value());

	dxcc_entity_cache_rm(qsodb.getRec(editNbr));
	qsodb.qsoUpdRec (editNbr, &rec);
	dxcc_entity_cache_add(&rec);

	qsodb.isdirty(0);
	restore_sort();

	loadBrowser(true);

	adifFile.writeLog (logbook_filename.c_str(), &qsodb);

}

void deleteRecord () {
	if (qsodb.nbrRecs() == 0 || fl_choice2(_("Really delete record for \"%s\"?"),
					       _("Yes"), _("No"), NULL, wBrowser->valueAt(-1, 2)))
		return;

	dxcc_entity_cache_rm(qsodb.getRec(editNbr));
	qsodb.qsoDelRec(editNbr);

	qsodb.isdirty(0);
	restore_sort();

	loadBrowser(true);

	adifFile.writeLog (logbook_filename.c_str(), &qsodb);

}

void EditRecord( int i )
{
	cQsoRec *editQSO = qsodb.getRec (i);
	if( !editQSO )
		return;

	inpCall_log->value (editQSO->getField(CALL));
	inpName_log->value (editQSO->getField(NAME));
	inpDate_log->value (editQSO->getField(QSO_DATE));
	inpDateOff_log->value (editQSO->getField(QSO_DATE_OFF));
	inpTimeOn_log->value (timeview(editQSO->getField(TIME_ON)));
	inpTimeOff_log->value (timeview(editQSO->getField(TIME_OFF)));
	inpRstR_log->value (editQSO->getField(RST_RCVD));
	inpRstS_log->value (editQSO->getField(RST_SENT));
	inpFreq_log->value (editQSO->getField(FREQ));
	inpBand_log->value (editQSO->getField(BAND));
	inpMode_log->value (editQSO->getField(ADIF_MODE));
	inpState_log->value (editQSO->getField(STATE));
	inpVE_Prov_log->value (editQSO->getField(VE_PROV));
	inpCountry_log->value (editQSO->getField(COUNTRY));
	inpQth_log->value (editQSO->getField(QTH));
	inpLoc_log->value (editQSO->getField(GRIDSQUARE));

	inpQSLrcvddate_log->value (editQSO->getField(QSLRDATE));
	inpQSLsentdate_log->value (editQSO->getField(QSLSDATE));

	inpEQSLrcvddate_log->value (editQSO->getField(EQSLRDATE));
	inpEQSLsentdate_log->value (editQSO->getField(EQSLSDATE));

	inpLOTWrcvddate_log->value (editQSO->getField(LOTWRDATE));
	inpLOTWsentdate_log->value (editQSO->getField(LOTWSDATE));

	inpNotes_log->value (editQSO->getField(NOTES));
	inpSerNoIn_log->value(editQSO->getField(SRX));
	inpSerNoOut_log->value(editQSO->getField(STX));
	inpXchgIn_log->value(editQSO->getField(XCHG1));

	inpClass_log->value(editQSO->getField(CLASS));
	inpSection_log->value(editQSO->getField(ARRL_SECT));

	inpMyXchg_log->value(editQSO->getField(MYXCHG));
	inpCNTY_log->value(editQSO->getField(CNTY));
	inpIOTA_log->value(editQSO->getField(IOTA));
	inpDXCC_log->value(editQSO->getField(DXCC));
	inpQSL_VIA_log->value(editQSO->getField(QSL_VIA));
	inpCONT_log->value(editQSO->getField(CONT));
	inpCQZ_log->value(editQSO->getField(CQZ));
	inpITUZ_log->value(editQSO->getField(ITUZ));
	inpTX_pwr_log->value(editQSO->getField(TX_PWR));

	inp_log_sta_call->value(editQSO->getField(STA_CALL));
	inp_log_op_call->value(editQSO->getField(OP_CALL));
	inp_log_sta_qth->value(editQSO->getField(MY_CITY));
	inp_log_sta_loc->value(editQSO->getField(MY_GRID));

	inp_log_cwss_serno->value(editQSO->getField(SS_SERNO));
	inp_log_cwss_prec->value(editQSO->getField(SS_PREC));
	inp_log_cwss_chk->value(editQSO->getField(SS_CHK));
	inp_log_cwss_sec->value(editQSO->getField(SS_SEC));

	inp_age_log->value(editQSO->getField(AGE));
	inp_1010_log->value(editQSO->getField(TEN_TEN));
	inp_check_log->value(editQSO->getField(CHECK));

	inp_log_troop_s->value(editQSO->getField(TROOPS));
	inp_log_troop_r->value(editQSO->getField(TROOPR));
	inp_log_scout_s->value(editQSO->getField(SCOUTS));
	inp_log_scout_r->value(editQSO->getField(SCOUTR));

}

std::string sDate_on = "";
std::string sTime_on = "";
std::string sDate_off = "";
std::string sTime_off = "";

const char *log_power()
{
	static std::string stemp;
	if (progdefaults.log_power_meter) 	{
		static char sval[20];
		snprintf(sval, sizeof(sval), "%3.0f", 
			progdefaults.report_average_power ? pwrmeter->average() : pwrmeter->peak());
		stemp = sval;
		strtrim(stemp);
	} else
		stemp = progdefaults.mytxpower;
	return stemp.c_str();
}

void AddRecord ()
{
	inpCall_log->value(inpCall->value());
	inpName_log->value (inpName->value());

	if (progdefaults.force_date_time) {
		inpDate_log->value(sDate_off.c_str());
		inpTimeOn_log->value (timeview(sTime_off.c_str()));
	} else {
		inpDate_log->value(sDate_on.c_str());
		inpTimeOn_log->value (timeview(sTime_on.c_str()));
	}
	inpDateOff_log->value(sDate_off.c_str());
	inpTimeOff_log->value (timeview(sTime_off.c_str()));

	inpRstR_log->value (inpRstIn->value());
	inpRstS_log->value (inpRstOut->value());
	{
		char Mhz[30];
		snprintf(Mhz, sizeof(Mhz), "%-.6lf", atof(inpFreq->value()) / 1000.0);
		inpFreq_log->value(Mhz);
		inpBand_log->value(band_name(Mhz));
	}
	inpMode_log->value (logmode);
	inpState_log->value (ucasestr(inpState->value()).c_str());
	inpVE_Prov_log->value (ucasestr(inpVEprov->value()).c_str());
	inpCountry_log->value (cboCountry->value());
	inpCNTY_log->value (inpCounty->value());

	inpSerNoIn_log->value(inpSerNo->value());
	inpSerNoOut_log->value(outSerNo->value());
	inpXchgIn_log->value(inpXchgIn->value());
	inpMyXchg_log->value(progdefaults.myXchg.c_str());

	inpQth_log->value (inpQTH->value());
	inpLoc_log->value (inpLoc->value());

	inpQSLrcvddate_log->value ("");
	inpQSLsentdate_log->value ("");

	inpEQSLrcvddate_log->value ("");
	inpEQSLsentdate_log->value ("");

	inpLOTWrcvddate_log->value ("");
	inpLOTWsentdate_log->value ("");

	inpNotes_log->value (inpNotes->value());

	if (progdefaults.log_power_meter) inpTX_pwr_log->value(log_power());

	inpIOTA_log->value("");
	inpDXCC_log->value("");
	inpQSL_VIA_log->value("");
	inpCONT_log->value("");

	inpCQZ_log->value(inp_CQzone->value());
	inpITUZ_log->value("");

	inpClass_log->value(ucasestr(inpClass->value()).c_str());
	inpSection_log->value(ucasestr(inpSection->value()).c_str());

	inp_log_sta_call->value(progdefaults.myCall.c_str());
	inp_log_op_call->value(progdefaults.operCall.c_str());
	inp_log_sta_qth->value(progdefaults.myQth.c_str());
	inp_log_sta_loc->value(progdefaults.myLocator.c_str());

	inp_log_cwss_serno->value(ucasestr(inp_SS_SerialNoR->value()).c_str());
	inp_log_cwss_prec->value(ucasestr(inp_SS_Precedence->value()).c_str());
	inp_log_cwss_chk->value(ucasestr(inp_SS_Check->value()).c_str());
	inp_log_cwss_sec->value(ucasestr(inp_SS_Section->value()).c_str());

	inp_age_log->value(ucasestr(inp_KD_age->value()).c_str());
	inp_check_log->value(ucasestr(inp_ARR_check->value()).c_str());
	inp_1010_log->value(ucasestr(inp_1010_nr->value()).c_str());

	inp_log_troop_s->value(progdefaults.my_JOTA_troop.c_str());
	inp_log_troop_r->value(inp_JOTA_troop->value());
	inp_log_scout_s->value(progdefaults.my_JOTA_scout.c_str());
	inp_log_scout_r->value(inp_JOTA_scout->value());

	saveRecord();

	logState = VIEWREC;
	activateButtons();
}

void cb_browser (Fl_Widget *w, void *data )
{
	Table *table = (Table *)w;
	editNbr = atoi(table->valueAt(-1,6));
	EditRecord (editNbr);
}

void addBrowserRow(cQsoRec *rec, int nbr)
{
	char sNbr[6];
	snprintf(sNbr,sizeof(sNbr),"%d", nbr);
	wBrowser->addRow (7,
		rec->getField(progdefaults.sort_date_time_off ? QSO_DATE_OFF : QSO_DATE),
		timeview4(rec->getField(progdefaults.sort_date_time_off ? TIME_OFF : TIME_ON)),
		rec->getField(CALL),
		rec->getField(NAME),
		rec->getField(FREQ),
		rec->getField(ADIF_MODE),
		sNbr);
}

void adjustBrowser(bool keep_pos)
{
	int row = wBrowser->value(), pos = wBrowser->scrollPos();
	if (row >= qsodb.nbrRecs()) row = qsodb.nbrRecs() - 1;
	if (keep_pos && row >= 0) {
		wBrowser->value(row);
		wBrowser->scrollTo(pos);
	}
	else {
		if (cQsoDb::reverse == true)
			wBrowser->FirstRow ();
		else
			wBrowser->LastRow ();
	}
	char szRecs[6];
	snprintf(szRecs, sizeof(szRecs), "%5d", qsodb.nbrRecs());
	txtNbrRecs_log->value(szRecs);
}

void loadBrowser(bool keep_pos)
{
	cQsoRec *rec;
	wBrowser->clear();
	if (qsodb.nbrRecs() == 0)
		return;
	for( int i = 0; i < qsodb.nbrRecs(); i++ ) {
		rec = qsodb.getRec (i);
		addBrowserRow(rec, i);
	}
	adjustBrowser(keep_pos);
}

//=============================================================================
// Cabrillo reporter
//=============================================================================

const char *szContests[] =
{	"AP-SPRINT",
	"ARRL-10", "ARRL-160", "ARRL-DX-CW", "ARRL-DX-SSB", "ARRL-SS-CW",
	"ARRL-SS-SSB", "ARRL-UHF-AUG", "ARRL-VHF-JAN", "ARRL-VHF-JUN", "ARRL-VHF-SEP",
	"ARRL-RTTY",
	"BARTG-RTTY", "BARTG-SPRINT",
	"CQ-160-CW", "CQ-160-SSB", "CQ-WPX-CW", "CQ-WPX-RTTY", "CQ-WPX-SSB", "CQ-VHF",
	"CQ-WW-CW", "CQ-WW-RTTY", "CQ-WW-SSB",
	"DARC-HELL-SPRINT", "DARC-WAEDC-CW", "DARC-WAEDC-RTTY", "DARC-WAEDC-SSB",
	"FCG-FQP", "IARU-HF", "JIDX-CW", "JIDX-SSB",
	"NAQP-CW", "NAQP-RTTY", "NAQP-SSB", "NA-SPRINT-CW", "NA-SPRINT-SSB", "NCCC-CQP",
	"NEQP", "OCEANIA-DX-CW", "OCEANIA-DX-SSB", "RDXC", "RSGB-IOTA",
	"SAC-CW", "SAC-SSB", "STEW-PERRY", "TARA-RTTY", 0 };

enum icontest {
	AP_SPRINT,
	ARRL_10, ARRL_160, ARRL_DX_CW, ARRL_DX_SSB, ARRL_SS_CW,
	ARRL_SS_SSB, ARRL_UHF_AUG, ARRL_VHF_JAN, ARRL_VHF_JUN, ARRL_VHF_SEP,
	ARRL_RTTY,
	BARTG_RTTY, BARTG_SPRINT,
	CQ_160_CW, CQ_160_SSB, CQ_WPX_CW, CQ_WPX_RTTY, CQ_WPX_SSB, CQ_VHF,
	CQ_WW_CW, CQ_WW_RTTY, CQ_WW_SSB,
	DARC_HELL_SPRINT, DARC_WAEDC_CW, DARC_WAEDC_RTTY, DARC_WAEDC_SSB,
	FCG_FQP, IARU_HF, JIDX_CW, JIDX_SSB,
	NAQP_CW, NAQP_RTTY, NAQP_SSB, NA_SPRINT_CW, NA_SPRINT_SSB, NCCC_CQP,
	NEQP, OCEANIA_DX_CW, OCEANIA_DX_SSB, RDXC, RSGB_IOTA,
	SAC_CW, SAC_SSB, STEW_PERRY, TARA_RTTY
};

bool bInitCombo = true;
icontest contestnbr;

static int default_cabrillo_contest()
{
	if (progdefaults.logging == LOG_DARC_HELL_SPRINT)
		return DARC_HELL_SPRINT;
	return AP_SPRINT;
}

void setContestType()
{
    contestnbr = (icontest)cboContest->index();

   	btnCabCall->value(true);	btnCabFreq->value(true);	btnCabMode->value(true);
   	btnCabQSOdate->value(true); btnCabTimeOFF->value(true);	btnCabRSTsent->value(true);
   	btnCabRSTrcvd->value(true);	btnCabSerialIN->value(true);btnCabSerialOUT->value(true);
   	btnCabXchgIn->value(true);	btnCabMyXchg->value(true);
   	btnCabCounty->value(true);
   	btnCabState->value(true);

    switch (contestnbr) {
    	case ARRL_SS_CW :
    	case ARRL_SS_SSB :
    		btnCabRSTrcvd->value(false);
    		break;
    	case BARTG_RTTY :
    	case BARTG_SPRINT :
    		break;
    	case ARRL_UHF_AUG :
    	case ARRL_VHF_JAN :
    	case ARRL_VHF_JUN :
    	case ARRL_VHF_SEP :
    	case CQ_VHF :
    		btnCabRSTrcvd->value(false);
			btnCabSerialIN->value(false);
			btnCabSerialOUT->value(false);
    		break;
    	case AP_SPRINT :
    	case ARRL_10 :
    	case ARRL_160 :
		case ARRL_DX_CW :
		case ARRL_DX_SSB :
    	case CQ_160_CW :
    	case CQ_160_SSB :
    	case CQ_WPX_CW :
		case CQ_WPX_RTTY :
		case CQ_WPX_SSB :
		case RDXC :
		case OCEANIA_DX_CW :
		case OCEANIA_DX_SSB :
    	    break;
		case DARC_WAEDC_CW :
		case DARC_WAEDC_RTTY :
		case DARC_WAEDC_SSB :
		case DARC_HELL_SPRINT :
    		break;
    	case NAQP_CW :
    	case NAQP_RTTY :
    	case NAQP_SSB :
    	case NA_SPRINT_CW :
    	case NA_SPRINT_SSB :
    		break;
    	case RSGB_IOTA :
    		break;
    	default :
    		break;
	}
}

void cb_Export_Cabrillo(Fl_Menu_* m, void* d) {
	if (qsodb.nbrRecs() == 0) return;
	cQsoRec *rec;
	char line[80];
	int indx = 0;

	if (bInitCombo) {
		bInitCombo = false;
		while (szContests[indx]) 	{
			cboContest->add(szContests[indx]);
			indx++;
		}
	}
	cboContest->index(default_cabrillo_contest());
	setContestType();
	chkCabBrowser->clear();
#ifdef __APPLE__
	chkCabBrowser->textfont(FL_SCREEN_BOLD);
	chkCabBrowser->textsize(12);
#else
	chkCabBrowser->textfont(FL_COURIER);
	chkCabBrowser->textsize(12);
#endif
	for( int i = 0; i < qsodb.nbrRecs(); i++ ) {
		rec = qsodb.getRec (i);
		memset(line, 0, sizeof(line));
		snprintf(line,sizeof(line),"%8s %4s %-10s %-10s %-s",
 			rec->getField(QSO_DATE),
 			time4(rec->getField(TIME_OFF)),
			rec->getField(CALL),
			szfreq(rec->getField(FREQ)),
			adif2export(rec->getField(ADIF_MODE)).c_str() );
        chkCabBrowser->add(line);
	}
	chkCabBrowser->check_all();
	wCabrillo->show();
}

static std::string cabrillo_upper(std::string s)
{
	for (size_t i = 0; i < s.length(); i++)
		s[i] = toupper(static_cast<unsigned char>(s[i]));
	return s;
}

static std::string cabrillo_mode(std::string mode)
{
	mode = cabrillo_upper(mode);
	if (contestnbr == DARC_HELL_SPRINT && mode.find("HELL") != std::string::npos)
		return "DG";
	if (mode.compare("USB") == 0 || mode.compare("LSB") == 0 ||
		mode.compare("FM") == 0 ||
		mode.compare("SSB") == 0 || mode.compare("PH") == 0)
		return "PH";
	if (mode.compare("RTTY") == 0)
		return "RY";
	return mode;
}

static std::string cabrillo_contest_call()
{
	if (contestnbr == DARC_HELL_SPRINT)
		return progdefaults.contestCall;
	return progdefaults.contestCall.empty() ? progdefaults.myCall : progdefaults.contestCall;
}

static std::string cabrillo_qso_sort_key(cQsoRec *rec)
{
	std::string date = rec->getField(progdefaults.sort_date_time_off ? QSO_DATE_OFF : QSO_DATE);
	std::string time = rec->getField(progdefaults.sort_date_time_off ? TIME_OFF : TIME_ON);

	if (date.empty())
		date = rec->getField(QSO_DATE);
	if (time.empty())
		time = rec->getField(TIME_ON);

	if (time.length() < 4)
		time = time4(time.c_str());
	else if (time.length() > 4)
		time = time.substr(0, 4);

	return date + time;
}

void cabrillo_append_qso (FILE *fp, cQsoRec *rec)
{
	char freq[16] = "";
	std::string rst_in, rst_out, exch_in, exch_out, date, time, mode, mycall, call, exch, state, county;
	std::string qsoline = "QSO: ";
	int ifreq = 0;
	size_t len = 0;
	size_t p = 0;

	exch_out.clear();
	exch_in.clear();
	exch.clear();

	if (btnCabFreq->value()) {
		ifreq = (int)(1000.0 * atof(rec->getField(FREQ)));
		snprintf(freq, sizeof(freq), "%7d", ifreq);
		qsoline.append(freq); qsoline.append(" ");
	}

	if (btnCabMode->value()) {
		mode = cabrillo_mode(adif2export(rec->getField(ADIF_MODE)));
		if (mode.length() < 10) mode.append(10 - mode.length(), ' ');
		qsoline.append(mode).append(" ");
	}

	if (btnCabQSOdate->value()) {
		date = rec->getField(progdefaults.sort_date_time_off ? QSO_DATE_OFF : QSO_DATE);
		if (date.length() < 8)
			date = rec->getField(QSO_DATE);
		if (date.length() >= 8) {
			date = date.substr(0, 8);
			date.insert(4,"-");
			date.insert(7,"-");
		}
		qsoline.append(date).append(" ");
	}

	if (btnCabTimeOFF->value()) {
		time = rec->getField(progdefaults.sort_date_time_off ? TIME_OFF : TIME_ON);
		if (time.empty())
			time = rec->getField(TIME_ON);
		qsoline.append(time4(time.c_str())).append(" ");
	}

	mycall = cabrillo_contest_call();
	if (mycall.length() > 13) mycall = mycall.substr(0,13);
	len = mycall.length();
	if (len < 13) mycall.append(13 - len, ' ');
	qsoline.append(mycall).append("   ");

	if (btnCabRSTsent->value() || contestnbr == BARTG_RTTY) {
		rst_out = rec->getField(RST_SENT);
		if (rst_out.length() > 3) rst_out = rst_out.substr(0, 3);
		len = rst_out.length();
		if (len < 3) rst_out.append(3 - len, ' ');
		exch_out.append(rst_out).append(" ");
	}

	if (btnCabSerialOUT->value() || contestnbr == BARTG_RTTY) {
		exch_out.append(rec->getField(STX)).append(" ");
	}

	if (btnCabMyXchg->value()) {
		exch = rec->getField(MYXCHG);
		if (!exch.empty())
			exch_out.append(exch).append(" ");
	}

	if (contestnbr == BARTG_RTTY) {
		std::string toff = rec->getField(TIME_OFF);
		if (toff.length() > 4) toff = toff.substr(0,4);
		toff = toff.append(" ");
		exch_out.append(toff);
	}
//
// ADD CONTESTNBR == FD
//
//
	if (exch_out.length() > 20) exch_out = exch_out.substr(0,20);
	len = exch_out.length();
	if (len < 20) exch_out.append(20 - len, ' ');

	qsoline.append(exch_out);

	if (btnCabCall->value()) {
		call = rec->getField(CALL);
		if (call.length() > 13) call = call.substr(0,13);
		len = call.length();
		if (len < 13) call.append(13 - len, ' ');
		qsoline.append(call); qsoline.append(" ");
	}

	if (btnCabRSTrcvd->value()) {
		rst_in = rec->getField(RST_RCVD);
		if (rst_in.length() > 3) rst_in = rst_in.substr(0,3);
		len = rst_in.length();
		if (len < 3) rst_in.append(3 - len, ' ');
		qsoline.append(rst_in); qsoline.append(" ");
	}

	if (btnCabSerialIN->value()) {
		exch_in = exch_in.append(rec->getField(SRX));
		if (exch_in.length())
			exch_in += ' ';
	}

	if (btnCabXchgIn->value()) {
		exch = rec->getField(XCHG1);
		while ((p = exch.find(":")) != std::string::npos) exch.erase(p,1);
		while ((p = exch.find("  ")) != std::string::npos) exch.erase(p,1);
		if (!exch.empty() && exch[0] == ' ') exch.erase(0,1);
		exch_in.append(exch);
	}

	if (exch_in.length() > 14) exch_in = exch_in.substr(0,14);
	len = exch_in.length();
	if (len < 14) exch_in.append(14 - len, ' ');

	if (btnCabState->value()) {
		state = rec->getField(STATE);
		if (!state.empty())
			qsoline.append(state).append(" ");
	}

	if (btnCabCounty->value()) {
		county = rec->getField(CNTY);
		if (!county.empty())
			qsoline.append(county).append(" ");
	}

	qsoline.append(exch_in);

	fprintf (fp, "%s\n", qsoline.c_str());
	return;
}

static std::string cabrillo_qtc_file()
{
	std::string fn = LogsDir.empty() ? HomeDir : LogsDir;
	if (!fn.empty() && *fn.rbegin() != PATH_SEP[0])
		fn.append(PATH_SEP);
	fn.append("qtc-log.tsv");
	return fn;
}

static std::string cabrillo_qtc_date(std::string date)
{
	if (date.length() == 8) {
		date.insert(4, "-");
		date.insert(7, "-");
	}
	return date;
}

static void cabrillo_append_qtc(FILE *fp, const std::string& first_date, const std::string& last_date)
{
	(void)first_date;
	(void)last_date;

	std::ifstream in(cabrillo_qtc_file().c_str());
	if (!in)
		return;

	std::set<std::string> written;
	std::string line;
	while (std::getline(in, line)) {
		if (line.empty() || line[0] == '#')
			continue;

		std::istringstream ls(line);
		std::vector<std::string> f;
		std::string tok;
		while (std::getline(ls, tok, '\t'))
			f.push_back(tok);
		if (f.size() < 11)
			continue;

		const std::string& freq = f[1];
		std::string mode = cabrillo_mode(f[2]);
		const std::string& date = f[3];
		const std::string& time = f[4];
		std::string direction = cabrillo_upper(f[0]);
		std::string received_by = f[5];
		const std::string& group = f[6];
		std::string sent_by = f[7];
		const std::string& qso_time = f[8];
		const std::string& qso_call = f[9];
		const std::string& qso_nr = f[10];

		if (contestnbr == DARC_HELL_SPRINT) {
			std::string own_call = cabrillo_contest_call();
			if (direction == "TX")
				sent_by = own_call;
			else if (direction == "RX")
				received_by = own_call;
		}

		std::string key = received_by + "|" + group + "|" + sent_by + "|" +
			qso_time + "|" + qso_call + "|" + qso_nr;
		if (written.find(key) != written.end())
			continue;
		written.insert(key);

		fprintf(fp, "QTC: %6s %-5s %-10s %4s %-13s %-10s %-13s %4s %-13s %s\n",
			freq.c_str(),
			mode.c_str(),
			cabrillo_qtc_date(date).c_str(),
			time.c_str(),
			received_by.c_str(),
			group.c_str(),
			sent_by.c_str(),
			qso_time.c_str(),
			qso_call.c_str(),
			qso_nr.c_str());
	}
}

void WriteCabrillo()
{
	if (chkCabBrowser->nchecked() == 0)
		chkCabBrowser->check_all();
	if (chkCabBrowser->nchecked() == 0) return;

	cQsoRec *rec;
	std::string first_qtc_date;
	std::string last_qtc_date;

	std::string title = _("Create cabrillo report");
	std::string filters = "TEXT\t*.txt";
#ifdef __APPLE__
	filters.append("\n");
#endif
	std::string strContest = "";

	const char* p = FSEL::saveas( title.c_str(), filters.c_str(), "contest.txt");

	if (!p) return;
	if (!*p) return;

	std::string sp = p;
	if (sp.find(".txt") == std::string::npos) sp.append(".txt");
    FILE *cabFile = fl_fopen (sp.c_str(), "w");
    if (!cabFile)
        return;

    strContest = cboContest->value();
    contestnbr = (icontest)cboContest->index();

	static const char *contest_assisted_values[] = {
		"ASSISTED", "NON-ASSISTED"
	};
	static const char *contest_band_values[] = {
		"ALL", "160M", "80M", "40M", "20M", "15M", "10M", "6M",
		"4M", "2M", "222", "432", "902", "1.2G", "2.3G", "3.4G",
		"5.7G", "10G", "24G", "47G", "75G", "122G", "134G", "Light",
		"VHF-3-BAND", "VHF-FM-ONLY"
	};
	static const char *contest_mode_values[] = {
		"CW", "DIGI", "FM", "MIXED", "RTTY", "SSB"
	};
	static const char *contest_operator_values[] = {
		"CHECKLOG", "MULTI-OP", "SINGLE-OP"
	};
	static const char *contest_power_values[] = {
		"HIGH", "LOW", "QRP"
	};
	static const char *contest_station_values[] = {
		"DISTRIBUTED", "EXPEDITION", "EXPLORER", "FIXED", "HQ", "MOBILE",
		"PORTABLE", "ROVER", "ROVER-LIMITED", "ROVER-UNLIMITED", "SCHOOL"
	};
	static const char *contest_transmitter_values[] = {
		"LIMITED", "ONE", "SWL", "TWO", "UNLIMITED"
	};

#define CONTEST_CATEGORY_VALUE(values, index, fallback) \
	(((index) >= 0 && (index) < static_cast<int>(sizeof(values) / sizeof((values)[0]))) ? \
		(values)[index] : (values)[fallback])

	const char *contest_assisted = CONTEST_CATEGORY_VALUE(contest_assisted_values, progdefaults.contestAssisted, 1);
	const char *contest_band = CONTEST_CATEGORY_VALUE(contest_band_values, progdefaults.contestBand, 0);
	const char *contest_mode = CONTEST_CATEGORY_VALUE(contest_mode_values, progdefaults.contestMode, 1);
	const char *contest_operator = CONTEST_CATEGORY_VALUE(contest_operator_values, progdefaults.contestOperator, 2);
	const char *contest_power = CONTEST_CATEGORY_VALUE(contest_power_values, progdefaults.contestPower, 1);
	const char *contest_station = CONTEST_CATEGORY_VALUE(contest_station_values, progdefaults.contestStation, 3);
	const char *contest_transmitter = CONTEST_CATEGORY_VALUE(contest_transmitter_values, progdefaults.contestTransmitter, 1);

	std::string contest_call = cabrillo_contest_call();
	std::string contest_operators = contestnbr == DARC_HELL_SPRINT ?
		progdefaults.contestOperators :
		(progdefaults.contestOperators.empty() ? contest_call : progdefaults.contestOperators);

	if (contestnbr == DARC_HELL_SPRINT) {
		fprintf(cabFile,
"START-OF-LOG: 3.0\n\
CREATED-BY: %s %s\n\
CONTEST: %s\n\
GRID-LOCATOR: %s\n\
CALLSIGN: %s\n\
CATEGORY-OPERATOR: %s\n\
CATEGORY-TRANSMITTER: %s\n\
CATEGORY-ASSISTED: %s\n\
CATEGORY-BAND: %s\n\
CATEGORY-POWER: %s\n\
CATEGORY-MODE: %s\n\
CATEGORY-STATION: %s\n\
CLAIMED-SCORE: \n\
OPERATORS: %s\n\
CLUB: %s\n\
NAME: %s\n\
ADDRESS: %s\n\
ADDRESS-CITY: %s\n\
ADDRESS-POSTALCODE: %s\n\
EMAIL: %s\n\
SPECIFIC: %s\n\
SOAPBOX: %s\n",
			PACKAGE_NAME, PACKAGE_VERSION,
			strContest.c_str(),
			progdefaults.contestLocator.c_str(),
			contest_call.c_str(),
			contest_operator,
			contest_transmitter,
			contest_assisted,
			contest_band,
			contest_power,
			contest_mode,
			contest_station,
			contest_operators.c_str(),
			progdefaults.contestClub.c_str(),
			progdefaults.contestName.c_str(),
			progdefaults.contestAddress.c_str(),
			progdefaults.contestCity.c_str(),
			progdefaults.contestPostalCode.c_str(),
			progdefaults.contestEmail.c_str(),
			progdefaults.contestDarcDok.c_str(),
			progdefaults.contestSoapbox.c_str());
	} else {
		fprintf (cabFile,
"START-OF-LOG: 3.0\n\
CREATED-BY: %s %s\n\
\n\
# The callsign used during the contest.\n\
CALLSIGN: %s\n\
\n\
# ASSISTED or NON-ASSISTED\n\
CATEGORY-ASSISTED: \n\
\n\
# Band: ALL, 160M, 80M, 40M, 20M, 15M, 10M, 6M, 2M, 222, 432, 902, 1.2G\n\
CATEGORY-BAND: \n\
\n\
# Mode: SSB, CW, RTTY, MIXED \n\
CATEGORY-MODE: %s\n\
\n\
# Operator: SINGLE-OP, MULTI-OP, CHECKLOG \n\
CATEGORY-OPERATOR: \n\
\n\
# Power: HIGH, LOW, QRP \n\
CATEGORY-POWER: %s\n\
\n\
# Station: FIXED, MOBILE, PORTABLE, ROVER, EXPEDITION, HQ, SCHOOL \n\
CATEGORY-STATION: \n\
\n\
# Time: 6-HOURS, 12-HOURS, 24-HOURS \n\
CATEGORY-TIME: \n\
\n\
# Transmitter: ONE, TWO, LIMITED, UNLIMITED, SWL \n\
CATEGORY-TRANSMITTER: \n\
\n\
# Overlay: ROOKIE, TB-WIRES, NOVICE-TECH, OVER-50 \n\
CATEGORY-OVERLAY: \n\
\n\
# Integer number\n\
CLAIMED-SCORE: \n\
\n\
# Name of the radio club with which the score should be aggregated.\n\
CLUB: %s\n\
\n\
# Contest: AP-SPRINT, ARRL-10, ARRL-160, ARRL-DX-CW, ARRL-DX-SSB, ARRL-SS-CW,\n\
# ARRL-SS-SSB, ARRL-UHF-AUG, ARRL-VHF-JAN, ARRL-VHF-JUN, ARRL-VHF-SEP,\n\
# ARRL-RTTY, BARTG-RTTY, CQ-160-CW, CQ-160-SSB, CQ-WPX-CW, CQ-WPX-RTTY,\n\
# CQ-WPX-SSB, CQ-VHF, CQ-WW-CW, CQ-WW-RTTY, CQ-WW-SSB, DARC-HELL-SPRINT,\n\
# DARC-WAEDC-CW, DARC-WAEDC-RTTY, DARC-WAEDC-SSB, FCG-FQP, IARU-HF, JIDX-CW, JIDX-SSB,\n\
# NAQP-CW, NAQP-RTTY, NAQP-SSB, NA-SPRINT-CW, NA-SPRINT-SSB, NCCC-CQP,\n\
# NEQP, OCEANIA-DX-CW, OCEANIA-DX-SSB, RDXC, RSGB-IOTA, SAC-CW, SAC-SSB,\n\
# STEW-PERRY, TARA-RTTY \n\
CONTEST: %s\n\
\n\
# Optional email address\n\
EMAIL: %s\n\
\n\
LOCATION: \n\
\n\
SPECIFIC: %s\n\
\n\
# Operator name\n\
NAME: %s\n\
\n\
# Maximum 4 address lines.\n\
ADDRESS: %s\n\
ADDRESS: \n\
ADDRESS: \n\
ADDRESS: \n\
\n\
# A space-delimited list of operator callsign(s). \n\
OPERATORS: %s\n\
\n\
# Offtime yyyy-mm-dd nnnn yyyy-mm-dd nnnn \n\
# OFFTIME: \n\
\n\
# Soapbox comments.\n\
SOAPBOX: %s\n\
SOAPBOX: \n\
SOAPBOX: \n\n",
		PACKAGE_NAME, PACKAGE_VERSION,
		contest_call.c_str(),
		"",
		contest_power,
		progdefaults.contestClub.c_str(),
		strContest.c_str(),
		progdefaults.contestEmail.c_str(),
		progdefaults.contestDarcDok.c_str(),
		progdefaults.contestName.c_str(),
		progdefaults.contestAddress.c_str(),
		contest_call.c_str(),
		progdefaults.contestSoapbox.c_str() );
	}

#undef CONTEST_CATEGORY_VALUE

	struct cabrillo_qso_item {
		cQsoRec *rec;
		int index;
		std::string sort_key;

		cabrillo_qso_item() : rec(0), index(0) {}
	};
	std::vector<cabrillo_qso_item> qso_items;

	for (int i = 0; i < chkCabBrowser->FLTK_nitems() && i < qsodb.nbrRecs(); i++) {
		if (!chkCabBrowser->checked(i + 1))
			continue;

		rec = qsodb.getRec(i);
		cabrillo_qso_item item;
		item.rec = rec;
		item.index = i;
		item.sort_key = cabrillo_qso_sort_key(rec);
		qso_items.push_back(item);
	}

	if (contestnbr == DARC_HELL_SPRINT) {
		std::stable_sort(qso_items.begin(), qso_items.end(),
			[](const cabrillo_qso_item& a, const cabrillo_qso_item& b) {
				if (a.sort_key == b.sort_key)
					return a.index < b.index;
				return a.sort_key < b.sort_key;
			});
	}

	for (size_t i = 0; i < qso_items.size(); i++) {
		rec = qso_items[i].rec;
		std::string qso_date = rec->getField(progdefaults.sort_date_time_off ? QSO_DATE_OFF : QSO_DATE);
		if (qso_date.empty())
			qso_date = rec->getField(QSO_DATE);
		if (!qso_date.empty()) {
			if (first_qtc_date.empty() || qso_date < first_qtc_date)
				first_qtc_date = qso_date;
			if (last_qtc_date.empty() || qso_date > last_qtc_date)
				last_qtc_date = qso_date;
		}
		cabrillo_append_qso(cabFile, rec);
	}
	cabrillo_append_qtc(cabFile, first_qtc_date, last_qtc_date);
    fprintf(cabFile, "END-OF-LOG:\n");
    fclose (cabFile);
    return;
}

#if HAVE_STD_HASH
#	include <unordered_map>
 	typedef std::unordered_map<std::string, unsigned> dxcc_entity_cache_t;
#elif HAVE_STD_TR1_HASH
#	include <tr1/unordered_map>
 	typedef std::tr1::unordered_map<std::string, unsigned> dxcc_entity_cache_t;
#else
#	error "No std::hash or std::tr1::hash support"
#endif

static dxcc_entity_cache_t dxcc_entity_cache;
static bool dxcc_entity_cache_enabled = false;

#include "dxcc.h"

static void dxcc_entity_cache_clear(void)
{
	if (dxcc_entity_cache_enabled)
		dxcc_entity_cache.clear();
}

static void dxcc_entity_cache_add(cQsoRec* r)
{
	if (!dxcc_entity_cache_enabled | !r)
		return;

	const dxcc* e = dxcc_lookup(r->getField(CALL));
	if (e)
		dxcc_entity_cache[e->country]++;
}
static void dxcc_entity_cache_add(cQsoDb& db)
{
	if (!dxcc_entity_cache_enabled)
		return;

	int n = db.nbrRecs();
	for (int i = 0; i < n; i++)
		dxcc_entity_cache_add(db.getRec(i));
	if (!dxcc_entity_cache.empty()) {
		unsigned int un = dxcc_entity_cache.size();
		LOG_INFO("Found %u countries in %d QSO records", un, n);
	}
}

static void dxcc_entity_cache_rm(cQsoRec* r)
{
	if (!dxcc_entity_cache_enabled || !r)
		return;

	const dxcc* e = dxcc_lookup(r->getField(CALL));
	if (!e)
		return;
	dxcc_entity_cache_t::iterator i = dxcc_entity_cache.find(e->country);
	if (i != dxcc_entity_cache.end()) {
		if (i->second)
			i->second--;
		else
			dxcc_entity_cache.erase(i);
	}
}

void dxcc_entity_cache_enable(bool v)
{
	if (dxcc_entity_cache_enabled == v)
		return;

	dxcc_entity_cache_clear();
	if ((dxcc_entity_cache_enabled = v))
		dxcc_entity_cache_add(qsodb);
}

bool qsodb_dxcc_entity_find(const char* country)
{
	return dxcc_entity_cache.find(country) != dxcc_entity_cache.end();
}

//======================================================================
// eQSL verification support
//======================================================================

static std::string eqsl_download_name = "";
static cQsoDb *eqsl_db = 0;

void verify_eqsl(void *)
{
	eqsl_db = new cQsoDb;
LOG_INFO("VERIFY_EQSL: adifFile.do_readfile(%s)", eqsl_download_name.c_str());
	adifFile.do_readfile (eqsl_download_name.c_str(), eqsl_db);

	if (eqsl_db->nbrRecs() == 0) {
		LOG_INFO("No records in eqsl download file");
		return;
	}
	LOG_INFO("logbook %d records, verify with %d records", qsodb.nbrRecs(), eqsl_db->nbrRecs());

	int matchrec;
	cQsoRec *qrec, *lrec;
	int nverified = 0;

	for (int i = 0; i < eqsl_db->nbrRecs(); i++) {
		lrec = eqsl_db->getRec(i);
		matchrec = qsodb.matched( lrec );
		if (matchrec != -1) {
			qrec = qsodb.getRec(matchrec);
			if (qrec->getField(EQSLRDATE)[0] == 0) {
				nverified++;
				qrec->putField(EQSLRDATE, zdate());
			}
		} else {
			LOG_INFO("Could not match %s on %s", lrec->getField(CALL), lrec->getField(QSO_DATE));
		}
	}
	LOG_INFO("%d records updated", nverified);
	delete eqsl_db;
}

void cb_btn_verify_eqsl(Fl_Button *, void *) {
	ENSURE_THREAD(FLMAIN_TID);

	const char* p = FSEL::select(_("LoTW download file"), "ADIF\t*.{adi,adif}", LoTWDir.c_str());

	if (!p) return;
	if (!*p) return;

	eqsl_download_name = p;

	Fl::awake(verify_eqsl);

}
