/***************************************************************************
                          crqwiz.cpp  -  description
                             -------------------
    begin                : Sat Jun 15 2002
    copyright            : (C) 2002 by ARRL
    author               : Jon Bloom
    email                : jbloom@arrl.org
    revision             : $Id$
 ***************************************************************************/

#include "crqwiz.h"
#include <expat.h>
#include <ctype.h>
#include <stdlib.h>
#include <wx/validate.h>
#include <wx/datetime.h>
#include <wx/config.h>
#include <wx/tokenzr.h>
#include <wx/regex.h>
#include <algorithm>
#include <iostream>
#ifdef DEBUG
#include <fstream>
#endif
#include <string>
#include <vector>
#include <map>
#include "dxcc.h"
#include "util.h"
#include "tqslctrls.h"
#include "tqsltrace.h"
#include "tqsl_prefs.h"

#include "winstrdefs.h"

extern int SaveAddressInfo(const char *callsign, int dxcc);

extern int GetULSInfo(const char *callsign, wxString &name, wxString &attn, wxString &street, wxString &city, wxString &state, wxString &zip);

using std::string;
using std::make_pair;

extern int get_address_field(const char *callsign, const char *field, string& result);

static wxString callTypeChoices[] = {
	_("This callsign replaces my existing callsign"),
	_("This is my former callsign"),
	_("I am the QSL manager for this callsign"),
	_("This is a club callsign"),
	_("This is a DXpedition callsign"),
	_("This is a special event callsign"),
	_("None of these apply")
};

// List of DXCC entities in the US.
static int USEntities[] = { 6,  // Alaska
			    9,  // American Samoa
			   20,  // Baker and Howland
			   43,  // Desecheo Island
			  103,  // Guam
			  105,  // Guantanamo Bay
			  110,  // Hawaii
			  123,  // Johnston Island
			  138,  // Kure Island
			  166,  // Mariana Islands
			  174,  // Midway Island
			  182,  // Navassa Island
			  197,  // Palmyra & Jarvis
			  202,  // Puerto Rico
			  285,  // US Virgin Islands
			  291,  // USA
			  297,  // Wake Island
			  515,  // Swains Island
			   -1 };

void
CRQWiz::startElement(void *userData, const char *name, const char **atts) {
	CRQWiz* wiz = reinterpret_cast<CRQWiz *> (userData);
	if (wiz->skipFile)	// Newer one already loaded
		return;
	int entnum = 0;
	long maj = 0;
	long min = 0;
	if (strcmp(name, "prefixes") == 0) {
		for (int i = 0; atts[i]; i+=2) {
			if (strcmp(atts[i], "majorversion") == 0) {
				maj = strtol(atts[i+1], NULL, 10);
			} else if (strcmp(atts[i], "minorversion") == 0) {
				min = strtol(atts[i+1], NULL, 10);
			}
		}
		if (maj > wiz->pmajor || (maj == wiz->pmajor && min > wiz->pminor)) {
			wiz->prefixRegex.clear();			// This is newer. Clear then reload.
			wiz->entityNames.clear();
			wiz->pmajor = maj;
			wiz->pminor = min;
			return;
		}
		wiz->skipFile = true;				// Newer one already loaded
		return;
	}
	if (strcmp(name, "prefix"))				// Not a prefix entry
		return;
	for (int i = 0; atts[i]; i+=2) {
		if (strcmp(atts[i], "entity") == 0) {
			entnum = strtol(atts[i+1], NULL, 10);
			if (entnum == 0)
				return;
		} else if (strcmp(atts[i], "regex") == 0) {
			wxString r = wxString::FromUTF8(atts[i+1]);
			wiz->insertRegex(entnum, r);
		} else if (strcmp(atts[i], "name") == 0) {
			wxString eName = wxString::FromUTF8(atts[i+1]);
			eName = eName.Trim();			// Last is entity name
			// Translate "(Deleted)" to local language
			wxString del = wxGetTranslation(wxT("DELETED"));
			if (del != wxT("DELETED"))
				eName.Replace(wxT("Deleted"), wxGetTranslation(wxT("DELETED")));

			wiz->insertName(entnum, eName);
		}
	}
}

static void
processPfxFile(const char* baseDir, void* ud, XML_StartElementHandler hnd) {
	char prefixfile[TQSL_MAX_PATH_LEN];
	FILE *lfp;

#ifdef _WIN32
	snprintf(prefixfile, sizeof prefixfile, "%s\\prefixes.dat", baseDir);
	wchar_t *wfilename = utf8_to_wchar(prefixfile);
	if ((lfp = _wfopen(wfilename, L"rb, ccs=UTF-8")) == NULL) {
		free_wchar(wfilename);
		return;
	}
#else
	snprintf(prefixfile, sizeof prefixfile, "%s/prefixes.dat", baseDir);
	if ((lfp = fopen(prefixfile, "rb")) == NULL) {
		return;
	}
#endif

	CRQWiz* wiz = reinterpret_cast<CRQWiz *> (ud);
	XML_Parser p = XML_ParserCreate(NULL);
	XML_SetUserData(p, ud);
	XML_SetStartElementHandler(p, hnd);

	char pBuf[2048];

	bool done = false;
	do {
		unsigned int len = fread(pBuf, 1, sizeof pBuf, lfp);
		done = (len < sizeof(pBuf));
		if (XML_Parse(p, pBuf, len, done) == XML_STATUS_ERROR) {
			XML_ParserFree(p);
			fclose(lfp);
			return;
		}
	} while (!done && !wiz->skipFile);

	XML_ParserFree(p);
	fclose(lfp);
}

void
CRQWiz::initPrefixes() {
	if (getNumNames()> 0)			// Already initialized
		return;

	processPfxFile(tQSL_BaseDir, reinterpret_cast<void *>(this), &CRQWiz::startElement);
	processPfxFile(tQSL_RsrcDir, reinterpret_cast<void *>(this), &CRQWiz::startElement);
	return;
}

static bool
isUSCallsign(wxString& call) {
	wxString first = call.Upper().Left(1);
	wxString second = call.Upper().Left(2);

	if (call.size() < 3) {
		return false;
	}
	if (call.Find(wxT("/")) != wxNOT_FOUND) {
		return false;
	}
	if (first == wxT("W") || first == wxT("K") || first == wxT("N") ||
	    (second >= wxT("AA") && second <= wxT("AL"))) {
		return true;
	}
	return false;
}

static bool
isUSEntity(int entity) {
	for (int i = 0; USEntities[i] > 0; i++) {
		if (entity == USEntities[i]) {
			return true;
		}
	}
	return false;
}

CRQWiz::CRQWiz(TQSL_CERT_REQ *crq, tQSL_Cert xcert, wxWindow *parent, wxHtmlHelpController *help,
	const wxString& title)
	: ExtWizard(parent, help, title), cert(xcert), _crq(crq)  {
	tqslTrace("CRQWiz::CRQWiz", "crq=%lx, xcert=%lx, title=%s", reinterpret_cast<void *>(cert), reinterpret_cast<void *>(xcert), S(title));

	dxcc = -1;
	validcerts = false;		// No signing certs to use
	onebyone = false;
	signIt = CRQ_SIGN_MAYBE;	// Not forcing signing of this
	portable = false;		// Not portable
	replacementCall = false;	// Not a replacement
	renewal = (_crq != NULL);	// It's a renewal if there's a CRQ provided
	usa = validusa = false;		// Not usa
	expired = false;
	nqcert = 0;
	qcertlist = NULL;
	qsonotbefore.year = qsonotbefore.month = qsonotbefore.day = 0;
	qsonotafter.year = qsonotafter.month = qsonotafter.day = 0;
	skipFile = false;
	pmajor = pminor = -1;

	initPrefixes();			// Initialize prefix regex list

	// Get count of valid certificates
	int ncerts = 0;
	if (!tqsl_selectCertificates(NULL, &ncerts, NULL, 0, NULL, NULL, 0)) {
		validcerts = (ncerts > 0);
	}
	nprov = 1;
	networkError = false;
	if (tqsl_getNumProviders(&nprov))
		nprov = 0;
	providerPage = new CRQ_ProviderPage(this, _crq);
	signPage = new CRQ_SignPage(this, _crq);
	callsignPage = new CRQ_CallsignPage(this, _crq);
	namePage = new CRQ_NamePage(this, _crq);
	emailPage = new CRQ_EmailPage(this, _crq);
	wxConfig *config = reinterpret_cast<wxConfig *>(wxConfig::Get());
	config->Read(wxT("CertPwd"), &CertPwd, DEFAULT_CERTPWD);
	pwPage = new CRQ_PasswordPage(this);
	typePage = new CRQ_TypePage(this);
	if (nprov != 1)
		wxWizardPageSimple::Chain(providerPage, callsignPage);
	wxWizardPageSimple::Chain(callsignPage, typePage);
	wxWizardPageSimple::Chain(typePage, namePage);
	wxWizardPageSimple::Chain(namePage, emailPage);
	wxWizardPageSimple::Chain(emailPage, pwPage);
	if (!cert)
		wxWizardPageSimple::Chain(pwPage, signPage);
	if (nprov == 1)
		_first = callsignPage;
	else
		_first = providerPage;
	AdjustSize();
	CenterOnParent();
}

CRQWiz::~CRQWiz() {
	if (qcertlist != NULL) {
		tqsl_freeCertificateList(qcertlist, nqcert);
	}
	qcertlist = NULL;
	nqcert = 0;
}

bool
CRQWiz::ShouldBeSigned(void) {
	switch (signIt) {
		case CRQ_NOT_SIGNED:		// Don't sign if no certs
		case CRQ_SIGN_RENEWAL:		// or a renewal
			return false;
		case CRQ_SIGN_PORTABLE:		// Portable must be signed
		case CRQ_SIGN_REPLACEMENT:	// Replacement signed by current
		case CRQ_SIGN_1X1:		// 1x1 must be signed
		case CRQ_SIGN_QSL_MGR:		// QSL Mgr - Maybe
		case CRQ_SIGN_SPC_EVENT:	// Special event - Maybe
		case CRQ_SIGN_NONE:		// NONE - always signed
		case CRQ_SIGN_MAYBE:		// Dunno. Maybe.
		default:
			return true;
	}
}

bool
CRQWiz::MustBeSigned(void) {
	switch (signIt) {
		case CRQ_NOT_SIGNED:		// Don't sign if no certs
		case CRQ_SIGN_RENEWAL:		// or a renewal
		case CRQ_SIGN_MAYBE:		// Dunno. Maybe.
		case CRQ_SIGN_SPC_EVENT:	// Special event - Maybe
		case CRQ_SIGN_QSL_MGR:		// QSL Mgr - Maybe
			return false;
		case CRQ_SIGN_PORTABLE:		// Portable must be signed
		case CRQ_SIGN_REPLACEMENT:	// Replacement signed by current
		case CRQ_SIGN_1X1:		// 1x1 must be signed
		case CRQ_SIGN_NONE:		// NONE - always signed
		default:
			return true;
	}
}

// Page constructors

BEGIN_EVENT_TABLE(CRQ_ProviderPage, CRQ_Page)
	EVT_COMBOBOX(ID_CRQ_PROVIDER, CRQ_ProviderPage::UpdateInfo)
END_EVENT_TABLE()

static bool
prov_cmp(const TQSL_PROVIDER& p1, const TQSL_PROVIDER& p2) {
	return strcasecmp(p1.organizationName, p2.organizationName) < 0;
}

CRQ_ProviderPage::CRQ_ProviderPage(CRQWiz *parent, TQSL_CERT_REQ *crq) :  CRQ_Page(parent) {
	tqslTrace("CRQ_ProviderPage::CRQ_ProviderPage", "parent=%lx, crq=%lx", reinterpret_cast<void *>(parent), reinterpret_cast<void *>(crq));
	wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);

	wxWindowDC dc(this);
	dc.SetFont(this->GetFont());
	Parent()->maxWidth = 0;

	wxCoord em_w, em_h;
	dc.GetTextExtent(wxString(wxT("M")), &em_w, &em_h);

	if (Parent()->maxWidth < em_w * 40)
		Parent()->maxWidth = em_w * 40;

	wxString lbl = _("This will create a new Callsign Certificate request file.");
		lbl += wxT("\n\n");
		lbl += _("Once you supply the requested information and the request file has been created, you must send the request file to the certificate issuer.");
	wxStaticText *st = new wxStaticText(this, -1, lbl);
	st->SetSize(Parent()->maxWidth + em_w * 2, em_h * 5);
	st->Wrap(Parent()->maxWidth + em_w * 3);

	sizer->Add(st, 0, wxALL, 10);

	sizer->Add(new wxStaticText(this, -1, _("Certificate Issuer:")), 0, wxLEFT|wxRIGHT, 10);
	tc_provider = new tqslComboBox(this, ID_CRQ_PROVIDER, wxT(""), wxDefaultPosition,
		wxDefaultSize, 0, 0, wxCB_DROPDOWN|wxCB_READONLY);
	ACCESSIBLE(tc_provider, ComboBoxAx);
	tc_provider->SetName(wxT("Certificate Issuer"));
	sizer->Add(tc_provider, 0, wxLEFT|wxRIGHT|wxEXPAND, 10);
	tc_provider_info = new wxStaticText(this, ID_CRQ_PROVIDER_INFO, wxT(""), wxDefaultPosition,
		wxSize(0, em_h*5));
	sizer->Add(tc_provider_info, 0, wxALL|wxEXPAND, 10);
	int nprov = 0;
	if (tqsl_getNumProviders(&nprov))
		wxMessageBox(getLocalizedErrorString(), _("Error"), wxOK | wxICON_ERROR, this);
	for (int i = 0; i < nprov; i++) {
		TQSL_PROVIDER prov;
		if (!tqsl_getProvider(i, &prov))
			providers.push_back(prov);
	}
	sort(providers.begin(), providers.end(), prov_cmp);
	int selected = -1;
	for (int i = 0; i < static_cast<int>(providers.size()); i++) {
		tc_provider->Append(wxString::FromUTF8(providers[i].organizationName), reinterpret_cast<void *>(i));
		if (crq && !strcmp(providers[i].organizationName, crq->providerName)
			&& !strcmp(providers[i].organizationalUnitName, crq->providerUnit)) {
			selected = i;
		}
	}
	tc_provider->SetSelection((selected < 0) ? 0 : selected);
	if (providers.size() < 2 || selected >= 0)
		tc_provider->Enable(false);
	DoUpdateInfo();
	AdjustPage(sizer, wxT("crq.htm"));
}

void
CRQ_ProviderPage::DoUpdateInfo() {
	tqslTrace("CRQ_ProviderPage::DoUpdateInfo", NULL);
	int sel = tc_provider->GetSelection();
	if (sel >= 0) {
		long idx = (long)(tc_provider->GetClientData(sel));
		if (idx >=0 && idx < static_cast<int>(providers.size())) {
			Parent()->provider = providers[idx];
			wxString info;
			info = wxString::FromUTF8(Parent()->provider.organizationName);
			if (Parent()->provider.organizationalUnitName[0] != 0)
				info += wxString(wxT("\n  ")) + wxString::FromUTF8(Parent()->provider.organizationalUnitName);
			if (Parent()->provider.emailAddress[0] != 0)
				info += wxString(wxT("\n")) += _("Email: ") + wxString::FromUTF8(Parent()->provider.emailAddress);
			if (Parent()->provider.url[0] != 0)
				info += wxString(wxT("\n")) + _("URL: ") + wxString::FromUTF8(Parent()->provider.url);
			tc_provider_info->SetLabel(info);
		}
	}
}

void
CRQ_ProviderPage::UpdateInfo(wxCommandEvent&) {
	tqslTrace("CRQ_ProviderPage::UpdateInfo", NULL);
	DoUpdateInfo();
}


static wxDateTime::Month mons[] = {
	wxDateTime::Inv_Month, wxDateTime::Jan, wxDateTime::Feb, wxDateTime::Mar,
	wxDateTime::Apr, wxDateTime::May, wxDateTime::Jun, wxDateTime::Jul,
	wxDateTime::Aug, wxDateTime::Sep, wxDateTime::Oct, wxDateTime::Nov,
	wxDateTime::Dec };

BEGIN_EVENT_TABLE(CRQ_CallsignPage, CRQ_Page)
	EVT_TEXT(ID_CRQ_CALL, CRQ_Page::check_valid)
	EVT_TEXT(ID_CRQ_DXCC, tqslComboBox::OnTextEntry)
	EVT_TEXT(ID_CRQ_QBYEAR, tqslComboBox::OnTextEntry)
	EVT_TEXT(ID_CRQ_QBMONTH, tqslComboBox::OnTextEntry)
	EVT_TEXT(ID_CRQ_QBDAY, tqslComboBox::OnTextEntry)
	EVT_TEXT(ID_CRQ_QEYEAR, tqslComboBox::OnTextEntry)
	EVT_TEXT(ID_CRQ_QEMONTH, tqslComboBox::OnTextEntry)
	EVT_TEXT(ID_CRQ_QEDAY, tqslComboBox::OnTextEntry)
	EVT_COMBOBOX(ID_CRQ_DXCC, CRQ_Page::check_valid)
	EVT_COMBOBOX(ID_CRQ_QBYEAR, CRQ_Page::check_valid)
	EVT_COMBOBOX(ID_CRQ_QBMONTH, CRQ_Page::check_valid)
	EVT_COMBOBOX(ID_CRQ_QBDAY, CRQ_Page::check_valid)
	EVT_COMBOBOX(ID_CRQ_QEYEAR, CRQ_Page::check_valid)
	EVT_COMBOBOX(ID_CRQ_QEMONTH, CRQ_Page::check_valid)
	EVT_COMBOBOX(ID_CRQ_QEDAY, CRQ_Page::check_valid)
	EVT_CHECKBOX(ID_CRQ_SHOWALL, CRQ_CallsignPage::OnShowHide)
END_EVENT_TABLE()

CRQ_CallsignPage::CRQ_CallsignPage(CRQWiz *parent, TQSL_CERT_REQ *crq) :  CRQ_Page(parent) {
	tqslTrace("CRQ_CallsignPage::CRQ_CallsignPage", "parent=%lx, crq=%lx", reinterpret_cast<void *>(parent), reinterpret_cast<void *>(crq));
	initialized = false;
	_parent = parent;
	showAll = false;
	wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);

	wxStaticText *dst = new wxStaticText(this, -1, _("DXCC entity:"));
	wxSize sz = getTextSize(this);
	int em_h = sz.GetHeight();
	int em_w = sz.GetWidth();
	wxStaticText *st = new wxStaticText(this, -1, _("Call sign:"), wxDefaultPosition, wxDefaultSize,
		wxST_NO_AUTORESIZE|wxALIGN_RIGHT);
	st->SetSize(dst->GetSize());

	wxBoxSizer *hsizer = new wxBoxSizer(wxHORIZONTAL);
	hsizer->Add(st, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5);
	wxString cs;
	if (crq && crq->callSign[0])
		cs = wxString::FromUTF8(crq->callSign);
	tc_call = new wxTextCtrl(this, ID_CRQ_CALL, cs, wxDefaultPosition, wxSize(em_w*15, -1));
	tc_call->SetMaxLength(TQSL_CALLSIGN_MAX);
	ACCESSIBLE(tc_call, WindowAccessible);
	tc_call->SetName(wxT("Call sign"));
	hsizer->Add(tc_call, 0, wxEXPAND, 0);
	sizer->Add(hsizer, 0, wxLEFT|wxRIGHT|wxTOP|wxEXPAND, 10);
	if (crq && crq->callSign[0])
		tc_call->Enable(false);

	hsizer = new wxBoxSizer(wxHORIZONTAL);
	hsizer->Add(dst, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5);
#if  wxUSE_ACCESSIBILITY && __WXMAC__
	tc_dxcc = new tqslComboBox(this, ID_CRQ_DXCC, wxT(""), wxDefaultPosition,
		wxSize(em_w*25, -1), 0, 0, wxCB_DROPDOWN);
#else
	tc_dxcc = new tqslComboBox(this, ID_CRQ_DXCC, wxT(""), wxDefaultPosition,
		wxSize(em_w*25, -1), 0, 0, wxCB_DROPDOWN|wxCB_READONLY);
#endif
	hsizer->Add(tc_dxcc, 1, 0, 0);
	ACCESSIBLE(tc_dxcc, ComboBoxAx);
	tc_dxcc->SetName(wxT("DXCC Entity"));

	tc_showall = new wxCheckBox(this, ID_CRQ_SHOWALL, wxT("Show All Entities"));

	hsizer->Add(tc_showall);

	sizer->Add(hsizer, 0, wxALL, 10);

	DXCC dx;
	bool ok = dx.getFirst();
	while (ok) {
		wxString ename = parent->getEntityName(dx.number());
		if (ename.IsEmpty())
			ename = wxString::FromUTF8(dx.name());
		tc_dxcc->Append(ename, reinterpret_cast<void *>(dx.number()));
		ok = dx.getNext();
	}

	char ent[TQSL_COUNTRY_MAX];
	strncpy(ent, "-NONE-", sizeof ent);

	if (crq) {
		if (crq->dxccEntity >= 0)
			_parent->dxcc = crq->dxccEntity;
		if (dx.getByEntity(crq->dxccEntity)) {
			if (_parent->getEntityName(crq->dxccEntity).IsEmpty())
				strncpy(ent, dx.name(), sizeof ent);
			else
				strncpy(ent, _parent->getEntityName(crq->dxccEntity).ToUTF8(), sizeof ent);
			tc_dxcc->Enable(false);
		}
	}
	int i = tc_dxcc->FindString(wxString::FromUTF8(ent));
	if (i >= 0)
		tc_dxcc->SetSelection(i);
	struct {
		tqslComboBox **cb;
		int id;
	} boxes[][3] = {
	    { {&tc_qsobeginy, ID_CRQ_QBYEAR}, {&tc_qsobeginm, ID_CRQ_QBMONTH}, {&tc_qsobegind, ID_CRQ_QBDAY} },
	    { {&tc_qsoendy, ID_CRQ_QEYEAR}, {&tc_qsoendm, ID_CRQ_QEMONTH}, {&tc_qsoendd, ID_CRQ_QEDAY} }
	};
	int year = wxDateTime::GetCurrentYear() + 1;

	int sels[2][3];
	int dates[2][3];
	if (crq) {
		dates[0][0] = crq->qsoNotBefore.year;
		dates[0][1] = crq->qsoNotBefore.month;
		dates[0][2] = crq->qsoNotBefore.day;
		dates[1][0] = crq->qsoNotAfter.year;
		dates[1][1] = crq->qsoNotAfter.month;
		dates[1][2] = crq->qsoNotAfter.day;
	}
	wxString label = _("Date of the first QSO you made or will make using this callsign:");
	for (int i = 0; i < 2; i++) {
		sels[i][0] = sels[i][1] = sels[i][2] = 0;
		sizer->Add(new wxStaticText(this, -1, label), 0, wxBOTTOM, 5);
		hsizer = new wxBoxSizer(wxHORIZONTAL);
		hsizer->Add(new wxStaticText(this, -1, wxT("Y")), 0, wxLEFT|wxALIGN_CENTER_VERTICAL, 20);
		*(boxes[i][0].cb) = new tqslComboBox(this, boxes[i][0].id, wxT(""), wxDefaultPosition,
			wxSize(em_w*8, -1), 0, 0, wxCB_DROPDOWN/*|wxCB_READONLY*/);
		hsizer->Add(*(boxes[i][0].cb), 0, wxLEFT, 5);
		ACCESSIBLE(*(boxes[i][0].cb), ComboBoxAx);
		(*boxes[i][0].cb)->SetName(i ? wxT("QSO End Year") : wxT("QSO Begin Year"));
		hsizer->Add(new wxStaticText(this, -1, wxT("M")), 0, wxLEFT|wxALIGN_CENTER_VERTICAL, 10);
		*(boxes[i][1].cb) = new tqslComboBox(this, boxes[i][1].id, wxT(""), wxDefaultPosition,
			wxSize(em_w*6, -1), 0, 0, wxCB_DROPDOWN/*|wxCB_READONLY*/);
		hsizer->Add(*(boxes[i][1].cb), 0, wxLEFT, 5);
		ACCESSIBLE(*(boxes[i][1].cb), ComboBoxAx);
		(*boxes[i][1].cb)->SetName(i ? wxT("QSO End Month") : wxT("QSO Begin Month"));
		hsizer->Add(new wxStaticText(this, -1, wxT("D")), 0, wxLEFT|wxALIGN_CENTER_VERTICAL, 10);
		*(boxes[i][2].cb) = new tqslComboBox(this, boxes[i][2].id, wxT(""), wxDefaultPosition,
			wxSize(em_w*6, -1), 0, 0, wxCB_DROPDOWN/*|wxCB_READONLY*/);
		hsizer->Add(*(boxes[i][2].cb), 0, wxLEFT, 5);
		ACCESSIBLE(*(boxes[i][2].cb), ComboBoxAx);
		(*boxes[i][2].cb)->SetName(i ? wxT("QSO End Day of Month") : wxT("QSO Begin Day of Month"));

		// for end-date boxes, add a blank entry at the top
		if (i > 0) {
			for (int j = 0; j < 3; j++)
				(*(boxes[i][j].cb))->Append(wxT(""));
		}
		for (int j = year; j >= 1945; j--) {
			wxString s;
			s.Printf(wxT("%d"), j);
			if (crq && dates[i][0] == j)
				sels[i][0] = year - j + i;
			(*(boxes[i][0].cb))->Append(s);
		}
		for (int j = 1; j <= 12; j++) {
			wxString s;
			s.Printf(wxT("%d"), j);
			if (crq && dates[i][1] == j)
				sels[i][1] = j - 1 + i;
			(*(boxes[i][1].cb))->Append(s);
		}
		for (int j = 1; j <= 31; j++) {
			wxString s;
			s.Printf(wxT("%d"), j);
			if (crq && dates[i][2] == j)
				sels[i][2] = j - 1 + i;
			(*(boxes[i][2].cb))->Append(s);
		}
		sizer->Add(hsizer, 0, wxLEFT|wxRIGHT, 10);
		if (i == 0)
			sizer->Add(0, 40);
		label = _("Date of the last QSO you made or will make using this callsign:\n(Leave this date blank if this is still your valid callsign.)");
	}
	if (crq) {
		tc_qsobeginy->SetSelection(sels[0][0]);
		tc_qsobeginm->SetSelection(sels[0][1]);
		tc_qsobegind->SetSelection(sels[0][2]);
		wxDateTime now = wxDateTime::Now();
		wxDateTime qsoEnd(crq->qsoNotAfter.day, mons[crq->qsoNotAfter.month],
			crq->qsoNotAfter.year, 23, 59, 59);
		if (qsoEnd < now) {
			_parent->expired = true;
			// Looks like this is a cert for an expired call sign,
			// so keep the QSO end date as-is. Otherwise, leave it
			// blank so CA can fill it in.
			tc_qsoendy->SetSelection(sels[1][0]);
			tc_qsoendm->SetSelection(sels[1][1]);
			tc_qsoendd->SetSelection(sels[1][2]);
		}
	}
	tc_cs_status = new wxStaticText(this, -1, wxT(""), wxDefaultPosition, wxSize(_parent->maxWidth, em_h*4));
	sizer->Add(tc_cs_status, 0, wxALL|wxEXPAND, 10);
	AdjustPage(sizer, wxT("crq0.htm"));
	initialized = true;
}

CRQ_Page *
CRQ_CallsignPage::GetNext() const {
	tqslTrace("CRQ_CallsignPage::GetNext", NULL);
	if (_parent->cert) {			// Renewal
		_parent->signIt = CRQ_SIGN_RENEWAL;
		reinterpret_cast<CRQ_NamePage*>(_parent->namePage)->Preset(reinterpret_cast<CRQ_CallsignPage*>(_parent->callsignPage));
		return _parent->namePage;
	}
	if (_parent->dxcc == 0) {		// NONE always requires signature
		_parent->signIt = CRQ_SIGN_NONE;
		return _parent->namePage;
	}
	if (_parent->onebyone) {		// 1x1 always requires signature
		_parent->signIt = CRQ_SIGN_1X1;
		return _parent->namePage;
	}
	if (!_parent->validcerts) {		// No certs, can't sign.
		_parent->signIt = CRQ_NOT_SIGNED;
		reinterpret_cast<CRQ_NamePage*>(_parent->namePage)->Preset(reinterpret_cast<CRQ_CallsignPage*>(_parent->callsignPage));
		return _parent->namePage;
	}
	if (_parent->portable) {		// Portable, no need to ask
		_parent->signIt = CRQ_SIGN_PORTABLE;
		reinterpret_cast<CRQ_NamePage*>(_parent->namePage)->Preset(reinterpret_cast<CRQ_CallsignPage*>(_parent->callsignPage));
		return _parent->namePage;
	}
	return _parent->typePage;
}

CRQ_Page *
CRQ_CallsignPage::GetPrev() const {
	tqslTrace("CRQ_CallsignPage::GetPrev", NULL);
	if (_parent->nprov > 1)
		return _parent->providerPage;
	return _parent->callsignPage;
}

void
CRQ_CallsignPage::ShowHide() {
	showAll = tc_showall->GetValue();
	validate();
}

BEGIN_EVENT_TABLE(CRQ_NamePage, CRQ_Page)
	EVT_TEXT(ID_CRQ_NAME, CRQ_Page::check_valid)
	EVT_TEXT(ID_CRQ_ADDR1, CRQ_Page::check_valid)
	EVT_TEXT(ID_CRQ_CITY, CRQ_Page::check_valid)
END_EVENT_TABLE()

CRQ_NamePage::CRQ_NamePage(CRQWiz *parent, TQSL_CERT_REQ *crq) :  CRQ_Page(parent) {
	tqslTrace("CRQ_NamePage::CRQ_NamePage", "parent=%lx, crq=%lx", reinterpret_cast<void *>(parent), reinterpret_cast<void *>(crq));
	initialized = false;
	wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
	_parent = parent;

	wxStaticText *zst = new wxStaticText(this, -1, _("Zip/Postal"));

	wxSize sz = getTextSize(this);
	int em_w = sz.GetWidth();
	int def_w = em_w * 20;
	wxStaticText *st = new wxStaticText(this, -1, _("Name"), wxDefaultPosition, wxDefaultSize,
		wxST_NO_AUTORESIZE|wxALIGN_RIGHT);
	st->SetSize(zst->GetSize());

	wxConfig *config = reinterpret_cast<wxConfig *>(wxConfig::Get());
	wxString val;
	wxBoxSizer *hsizer = new wxBoxSizer(wxHORIZONTAL);
	hsizer->Add(st, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5);
	wxString s;
	if (crq && crq->name[0])
		s = wxString::FromUTF8(crq->name);
	else if (config->Read(wxT("Name"), &val))
		s = val;
	tc_name = new wxTextCtrl(this, ID_CRQ_NAME, s, wxDefaultPosition, wxSize(def_w, -1));
	ACCESSIBLE(tc_name, WindowAccessible);
	tc_name->SetName(wxT("Name"));
	hsizer->Add(tc_name, 1, 0, 0);
	sizer->Add(hsizer, 0, wxALL, 10);
	tc_name->SetMaxLength(TQSL_CRQ_NAME_MAX);

	s = wxT("");
	if (crq && crq->address1[0])
		s = wxString::FromUTF8(crq->address1);
	else if (config->Read(wxT("Addr1"), &val))
		s = val;
	hsizer = new wxBoxSizer(wxHORIZONTAL);
	hsizer->Add(new wxStaticText(this, -1, _("Address"), wxDefaultPosition, zst->GetSize(),
		wxST_NO_AUTORESIZE|wxALIGN_RIGHT), 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5);
	tc_addr1 = new wxTextCtrl(this, ID_CRQ_ADDR1, s, wxDefaultPosition, wxSize(def_w, -1));
	ACCESSIBLE(tc_addr1, WindowAccessible);
	tc_addr1->SetName(wxT("Address line 1"));
	hsizer->Add(tc_addr1, 1, 0, 0);
	sizer->Add(hsizer, 0, wxLEFT|wxRIGHT|wxBOTTOM, 10);
	tc_addr1->SetMaxLength(TQSL_CRQ_ADDR_MAX);

	s = wxT("");
	if (crq && crq->address2[0])
		s = wxString::FromUTF8(crq->address2);
	else if (config->Read(wxT("Addr2"), &val))
		s = val;
	hsizer = new wxBoxSizer(wxHORIZONTAL);
	hsizer->Add(new wxStaticText(this, -1, wxT(""), wxDefaultPosition, zst->GetSize(),
		wxST_NO_AUTORESIZE|wxALIGN_RIGHT), 0, wxRIGHT, 5);
	tc_addr2 = new wxTextCtrl(this, ID_CRQ_ADDR2, s, wxDefaultPosition, wxSize(def_w, -1));
	ACCESSIBLE(tc_addr2, WindowAccessible);
	tc_addr2->SetName(wxT("Address line 2"));
	hsizer->Add(tc_addr2, 1, 0, 0);
	sizer->Add(hsizer, 0, wxLEFT|wxRIGHT|wxBOTTOM, 10);
	tc_addr2->SetMaxLength(TQSL_CRQ_ADDR_MAX);

	s = wxT("");
	if (crq && crq->city[0])
		s = wxString::FromUTF8(crq->city);
	else if (config->Read(wxT("City"), &val))
		s = val;
	hsizer = new wxBoxSizer(wxHORIZONTAL);
	hsizer->Add(new wxStaticText(this, -1, _("City"), wxDefaultPosition, zst->GetSize(),
		wxST_NO_AUTORESIZE|wxALIGN_RIGHT), 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5);
	tc_city = new wxTextCtrl(this, ID_CRQ_CITY, s, wxDefaultPosition, wxSize(def_w, -1));
	ACCESSIBLE(tc_city, WindowAccessible);
	tc_city->SetName(wxT("City"));
	hsizer->Add(tc_city, 1, 0, 0);
	sizer->Add(hsizer, 0, wxLEFT|wxRIGHT|wxBOTTOM, 10);
	tc_city->SetMaxLength(TQSL_CRQ_CITY_MAX);

	s = wxT("");
	if (crq && crq->state[0])
		s = wxString::FromUTF8(crq->state);
	else if (config->Read(wxT("State"), &val))
		s = val;
	hsizer = new wxBoxSizer(wxHORIZONTAL);
	hsizer->Add(new wxStaticText(this, -1, _("State"), wxDefaultPosition, zst->GetSize(),
		wxST_NO_AUTORESIZE|wxALIGN_RIGHT), 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5);
	tc_state = new wxTextCtrl(this, ID_CRQ_STATE, s, wxDefaultPosition, wxSize(def_w, -1));
	ACCESSIBLE(tc_state, WindowAccessible);
	tc_state->SetName(wxT("State"));
	hsizer->Add(tc_state, 1, 0, 0);
	sizer->Add(hsizer, 0, wxLEFT|wxRIGHT|wxBOTTOM, 10);
	tc_state->SetMaxLength(TQSL_CRQ_STATE_MAX);

	s = wxT("");
	if (crq && crq->postalCode[0])
		s = wxString::FromUTF8(crq->postalCode);
	else if (config->Read(wxT("ZIP"), &val))
		s = val;
	hsizer = new wxBoxSizer(wxHORIZONTAL);
	hsizer->Add(zst, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5);
	tc_zip = new wxTextCtrl(this, ID_CRQ_ZIP, s, wxDefaultPosition, wxSize(def_w, -1));
	hsizer->Add(tc_zip, 1, 0, 0);
	ACCESSIBLE(tc_zip, WindowAccessible);
	tc_zip->SetName(wxT("ZIP"));
	sizer->Add(hsizer, 0, wxLEFT|wxRIGHT|wxBOTTOM, 10);
	tc_zip->SetMaxLength(TQSL_CRQ_POSTAL_MAX);

	s = wxT("");
	if (crq && crq->country[0])
		s = wxString::FromUTF8(crq->country);
	else if (config->Read(_("Country"), &val))
		s = val;
	hsizer = new wxBoxSizer(wxHORIZONTAL);
	hsizer->Add(new wxStaticText(this, -1, _("Country"), wxDefaultPosition, zst->GetSize(),
		wxST_NO_AUTORESIZE|wxALIGN_RIGHT), 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5);
	tc_country = new wxTextCtrl(this, ID_CRQ_COUNTRY, s, wxDefaultPosition, wxSize(def_w, -1));
	ACCESSIBLE(tc_country, WindowAccessible);
	tc_country->SetName(wxT("Country"));
	hsizer->Add(tc_country, 1, 0, 0);
	sizer->Add(hsizer, 0, wxLEFT|wxRIGHT|wxBOTTOM, 10);
	tc_country->SetMaxLength(TQSL_CRQ_COUNTRY_MAX);
	tc_addr_status = new wxStaticText(this, -1, wxT(""));
	sizer->Add(tc_addr_status, 0, wxALL|wxEXPAND, 10);
	AdjustPage(sizer, wxT("crq1.htm"));
	initialized = true;
}

void
CRQ_NamePage::Preset(CRQ_CallsignPage *ip) {
	wxString s;
	string t;
	if (!_parent->networkError) {
		if (SaveAddressInfo(_parent->callsign.ToUTF8(), _parent->dxcc) < 0) {	// Timeout, net error
			_parent->networkError = true;
		}
	}
	if (!_parent->name.IsEmpty()) {
		tc_name->SetValue(_parent->name);
	} else if (get_address_field(_parent->callsign.ToUTF8(), "name", t) == 0) {
		s = wxString::FromUTF8(t.c_str());
		tc_name->SetValue(s);
	}

	if (!_parent->addr1.IsEmpty()) {
		tc_addr1->SetValue(_parent->addr1);
	} else if (get_address_field(_parent->callsign.ToUTF8(), "addr1", t) == 0) {
		s = wxString::FromUTF8(t.c_str());
		tc_addr1->SetValue(s);
	}
	if (!_parent->addr2.IsEmpty()) {
		if (_parent->addr2 == wxT("."))
			_parent->addr2 = wxT("");
		tc_addr2->SetValue(_parent->addr2);
	} else if (get_address_field(_parent->callsign.ToUTF8(), "addr2", t) == 0) {
		s = wxString::FromUTF8(t.c_str());
		tc_addr2->SetValue(s);
	}
	if (!_parent->city.IsEmpty()) {
		tc_city->SetValue(_parent->city);
	} else if (get_address_field(_parent->callsign.ToUTF8(), "city", t) == 0) {
		s = wxString::FromUTF8(t.c_str());
		tc_city->SetValue(s);
	}
	if (!_parent->state.IsEmpty()) {
		tc_state->SetValue(_parent->state);
	} else if (get_address_field(_parent->callsign.ToUTF8(), "addrState", t) == 0) {
		s = wxString::FromUTF8(t.c_str());
		tc_state->SetValue(s);
	}
	if (!_parent->zip.IsEmpty()) {
		tc_zip->SetValue(_parent->zip);
	} else if (get_address_field(_parent->callsign.ToUTF8(), "mailCode", t) == 0) {
		s = wxString::FromUTF8(t.c_str());
		tc_zip->SetValue(s);
	}
	if (!_parent->country.IsEmpty()) {
		tc_country->SetValue(_parent->country);
	} else if (get_address_field(_parent->callsign.ToUTF8(), "aCountry", t) == 0) {
		s = wxString::FromUTF8(t.c_str());
		tc_country->SetValue(s);
	}
}

CRQ_Page *
CRQ_NamePage::GetNext() const {
	tqslTrace("CRQ_NamePage::GetNext", NULL);
	return _parent->emailPage;
}

CRQ_Page *
CRQ_NamePage::GetPrev() const {
	tqslTrace("CRQ_NamePage::GetPrev", NULL);
	if ((_parent->dxcc == 0) || _parent->onebyone || _parent->portable)
		return _parent->callsignPage;
	if (_parent->validcerts)
		return _parent->typePage;
	return _parent->callsignPage;
}


BEGIN_EVENT_TABLE(CRQ_EmailPage, CRQ_Page)
	EVT_TEXT(ID_CRQ_EMAIL, CRQ_Page::check_valid)
END_EVENT_TABLE()

CRQ_EmailPage::CRQ_EmailPage(CRQWiz *parent, TQSL_CERT_REQ *crq) :  CRQ_Page(parent) {
	tqslTrace("CRQ_EmailPage::CRQ_EmailPage", "parent=%lx, crq=%lx", reinterpret_cast<void *>(parent), reinterpret_cast<void *>(crq));
	initialized = false;
	wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);

	_parent = parent;
	wxSize sz = getTextSize(this);
	int em_w = sz.GetWidth();
	wxStaticText *st = new wxStaticText(this, -1, _("Your e-mail address"));

	wxConfig *config = reinterpret_cast<wxConfig *>(wxConfig::Get());
	wxString val;
	wxString s;
	if (crq && crq->emailAddress[0])
		s = wxString::FromUTF8(crq->emailAddress);
	else if (config->Read(wxT("Email"), &val))
		s = val;
	sizer->Add(st, 0, wxLEFT|wxRIGHT|wxTOP, 10);
	tc_email = new wxTextCtrl(this, ID_CRQ_EMAIL, s, wxDefaultPosition, wxSize(em_w*30, -1));
	sizer->Add(tc_email, 0, wxLEFT|wxRIGHT|wxBOTTOM, 10);
	tc_email->SetMaxLength(TQSL_CRQ_EMAIL_MAX);
	ACCESSIBLE(tc_email, WindowAccessible);
	tc_email->SetName(wxT("e-mail"));
	wxStaticText *tc_warn = new wxStaticText(this, -1, _("Note: The e-mail address you provide here is the address to which the issued Certificate will be sent. Make sure it's the correct address!"));
	sizer->Add(tc_warn, 0, wxALL, 10);
	tc_warn->Wrap(_parent->maxWidth);
	tc_em_status = new wxStaticText(this, -1, wxT(""));
	sizer->Add(tc_em_status, 0, wxALL|wxEXPAND, 10);
	AdjustPage(sizer, wxT("crq2.htm"));
	initialized = true;
}

int
getEntityNum(wxString& name) {
	int nument;
	int entnum;
	const char *entname;
	const char *probe = name.ToUTF8();
	tqsl_getNumDXCCEntity(&nument);
	for (int i = 0; i < nument; i++) {
		tqsl_getDXCCEntity(i, &entnum, &entname);
		if (strcmp(entname, probe) == 0) {
			return entnum;
		}
	}
	return -1;
}

CRQ_Page *
CRQ_EmailPage::GetNext() const {
	tqslTrace("CRQ_EmailPage::GetNext", NULL);
	if (_parent->CertPwd) {
		return _parent->pwPage;
	} else {
		if (_parent->ShouldBeSigned()) {
			if (_parent->signIt ==  CRQ_SIGN_QSL_MGR) {
				wxConfig *config = reinterpret_cast<wxConfig *>(wxConfig::Get());
				wxString defQMGR;
				config->Read(wxT("primaryQSLMgrCall"), &defQMGR);
				tqslTrace("CRQ_EmailPage::GetNet", "QSL Mgr, pref %s", S(defQMGR));
				if (!defQMGR.IsEmpty()) {
					wxStringTokenizer t(defQMGR, wxT("-"));
					wxString call = t.GetNextToken().Trim(true).Trim(false);
					wxString ename = t.GetNextToken().Trim(true).Trim(false);
					int entity = getEntityNum(ename);
					// is there a cert for this call?
					tqslTrace("CRQ_EmailPage::GetNet", "QSL Mgr, call %s ent %s/%d", S(call), S(ename), entity);
					if (_parent->qcertlist && _parent->nqcert) {
						tqsl_freeCertificateList(_parent->qcertlist, _parent->nqcert);
						_parent->qcertlist = NULL;
						_parent->nqcert = 0;
					}
					if (!tqsl_selectCertificates(&_parent->qcertlist, &_parent->nqcert, call.ToUTF8(), entity, 0,
							&(_parent->provider), 0)) {
						tqslTrace("CRQ_EmailPage::GetNet", "%d certs found", _parent->nqcert);
						if (_parent->nqcert > 0) {
							_parent->cert = _parent->qcertlist[0];
						}
						return NULL;
					}
				}
			}
			return _parent->signPage;
		} else {
			return NULL;
		}
	}
	return NULL;
}

CRQ_Page *
CRQ_EmailPage::GetPrev() const {
	tqslTrace("CRQ_EmailPage::GetPrev", NULL);

	return _parent->namePage;
}

BEGIN_EVENT_TABLE(CRQ_PasswordPage, CRQ_Page)
	EVT_TEXT(ID_CRQ_PW1, CRQ_Page::check_valid)
	EVT_TEXT(ID_CRQ_PW2, CRQ_Page::check_valid)
END_EVENT_TABLE()

CRQ_PasswordPage::CRQ_PasswordPage(CRQWiz *parent) :  CRQ_Page(parent) {
	tqslTrace("CRQ_PasswordPage::CRQ_PasswordPage", "parent=%lx", reinterpret_cast<void *>(parent));
	initialized = false;
	_parent = parent;

	wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);

	wxSize sz = getTextSize(this);
	em_w = sz.GetWidth();
	em_h = sz.GetHeight();
	wxString lbl = _("You may protect this Callsign Certificate using a passphrase. If you are using a computer system that is shared with others, you should specify a passphrase to protect this Callsign Certificate. However, if you are using a computer in a private residence, no passphrase need be specified.");
	wxStaticText *st = new wxStaticText(this, -1, lbl);
	st->SetSize(_parent->maxWidth, em_h * 5);
	st->Wrap(_parent->maxWidth);
	sizer->Add(st, 0, wxLEFT|wxRIGHT|wxTOP, 10);
	fwdPrompt = new wxStaticText(this, -1, _("Leave the passphrase blank and click 'Next' unless you want to use a passphrase."));
	fwdPrompt->SetSize(_parent->maxWidth, em_h * 5);
	fwdPrompt->Wrap(_parent->maxWidth);
	sizer->Add(fwdPrompt, 0, wxLEFT|wxRIGHT|wxTOP, 10);
	sizer->Add(new wxStaticText(this, -1, _("Passphrase:")),
		0, wxLEFT|wxRIGHT|wxTOP, 10);
	tc_pw1 = new wxTextCtrl(this, ID_CRQ_PW1, wxT(""), wxDefaultPosition, wxSize(em_w*20, -1), wxTE_PASSWORD);
	tc_pw1->SetName(wxT("Passphrase"));
	ACCESSIBLE(tc_pw1, WindowAccessible);
	sizer->Add(tc_pw1, 0, wxLEFT|wxRIGHT, 10);
	sizer->Add(new wxStaticText(this, -1, _("Enter the passphrase again for verification:")),
		0, wxLEFT|wxRIGHT|wxTOP, 10);
	tc_pw2 = new wxTextCtrl(this, ID_CRQ_PW2, wxT(""), wxDefaultPosition, wxSize(em_w*20, -1), wxTE_PASSWORD);
	ACCESSIBLE(tc_pw2, WindowAccessible);
	tc_pw2->SetName(wxT("Repeat the Passphrase"));
	sizer->Add(tc_pw2, 0, wxLEFT|wxRIGHT, 10);
	wxStaticText *tc_pwwarn = new wxStaticText(this, -1, _("DO NOT lose the passphrase you choose! You will be unable to use the Certificate without this passphrase!"));
	tc_pwwarn->Wrap(em_w * 40);
	sizer->Add(tc_pwwarn, 0, wxALL, 10);
	tc_pwd_status = new wxStaticText(this, -1, wxT(""));
	sizer->Add(tc_pwd_status, 0, wxALL|wxEXPAND, 10);
	AdjustPage(sizer, wxT("crq3.htm"));
	initialized = true;
}

CRQ_Page *
CRQ_PasswordPage::GetNext() const {
	tqslTrace("CRQ_PasswordPage::GetNext", NULL);
	if (_parent->ShouldBeSigned()) {
		fwdPrompt->SetLabel(_("Leave the passphrase blank and click 'Next' unless you want to use a passphrase."));
		fwdPrompt->SetSize(_parent->maxWidth, em_h * 5);
		fwdPrompt->Wrap(_parent->maxWidth);
		if (_parent->signIt ==  CRQ_SIGN_QSL_MGR && !_parent->renewal) {		// Renewals are self-signed
			wxConfig *config = reinterpret_cast<wxConfig *>(wxConfig::Get());
			wxString defQMGR;
			config->Read(wxT("primaryQSLMgrCall"), &defQMGR);
			tqslTrace("CRQ_EmailPage::GetNet", "QSL Mgr, pref %s", S(defQMGR));
			if (!defQMGR.IsEmpty()) {
				wxStringTokenizer t(defQMGR, wxT("-"));
				wxString call = t.GetNextToken().Trim(true).Trim(false);
				wxString ename = t.GetNextToken().Trim(true).Trim(false);
				int entity = getEntityNum(ename);
				// is there a cert for this call?
				tqslTrace("CRQ_EmailPage::GetNet", "QSL Mgr, call %s ent %s/%d", S(call), S(ename), entity);
				if (_parent->qcertlist && _parent->nqcert) {
					tqsl_freeCertificateList(_parent->qcertlist, _parent->nqcert);
					_parent->qcertlist = NULL;
					_parent->nqcert = 0;
				}
				if (!tqsl_selectCertificates(&_parent->qcertlist, &_parent->nqcert, call.ToUTF8(), entity, 0,
						&(_parent->provider), 0)) {
					tqslTrace("CRQ_EmailPage::GetNet", "%d certs found", _parent->nqcert);
					if (_parent->nqcert > 0) {
						_parent->cert = _parent->qcertlist[0];
					}
					return NULL;
				}
			}
		}
		return _parent->signPage;
	} else {
		fwdPrompt->SetLabel(_("Leave the passphrase blank and click 'Finish' unless you want to use a passphrase."));
		fwdPrompt->SetSize(_parent->maxWidth, em_h * 5);
		fwdPrompt->Wrap(_parent->maxWidth);
		return NULL;
	}
}

CRQ_Page *
CRQ_PasswordPage::GetPrev() const {
	tqslTrace("CRQ_PasswordPage::GetPrev", NULL);
	return _parent->emailPage;
}

BEGIN_EVENT_TABLE(CRQ_TypePage, CRQ_Page)
	EVT_RADIOBOX(ID_CRQ_TYPE, CRQ_Page::check_valid)
END_EVENT_TABLE()

CRQ_TypePage::CRQ_TypePage(CRQWiz *parent)
	:  CRQ_Page(parent) {
	tqslTrace("CRQ_TypePage::CRQ_TypePage", "parent=%lx", reinterpret_cast<void *>(parent));

	initialized = false;
	_parent = parent;

	wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);

	wxArrayString ch;
	for (unsigned int i = 0; i < sizeof callTypeChoices / sizeof callTypeChoices[0]; i++) {
		ch.Add(wxGetTranslation(callTypeChoices[i]));
	}

	certType = new wxRadioBox(this, ID_CRQ_TYPE, _("What is this Callsign Certificate for?"), wxDefaultPosition,
		wxDefaultSize, ch, 1, wxRA_SPECIFY_COLS);

	sizer->Add(certType, 0, wxALL|wxEXPAND, 10);
}

bool
CRQ_TypePage::TransferDataFromWindow() {
	static int signThisType[] = {
		CRQ_SIGN_REPLACEMENT,	// Replaces former call
		CRQ_SIGN_REPLACEMENT,	// Is former call
		CRQ_SIGN_QSL_MGR,	// QSL manager
		CRQ_SIGN_QSL_MGR,	// Club
		CRQ_SIGN_QSL_MGR,	// DXpedition
		CRQ_SIGN_SPC_EVENT,	// Special event
		CRQ_SIGN_MAYBE,		// None of the above
		0
	};

	tqslTrace("CRQ_TypePage::TransferDataFromWindow", NULL);
	int selected = certType->GetSelection();
	if (selected  == wxNOT_FOUND || selected > static_cast<int>(sizeof signThisType / sizeof signThisType[0]))
		return false;
	_parent->signIt = signThisType[selected];
	_parent->certType = selected;
	if (_parent->dxcc == 0)
		_parent->signIt = CRQ_SIGN_NONE;
	return true;
}

CRQ_Page *
CRQ_TypePage::GetPrev() const {
	tqslTrace("CRQ_TypePage::GetPrev", NULL);

	if (_parent->nprov > 1)
		return _parent->providerPage;
	else
		return _parent->callsignPage;
}

CRQ_Page *
CRQ_TypePage::GetNext() const {
	tqslTrace("CRQ_TypePage::GetNext", NULL);

	return _parent->namePage;
}

BEGIN_EVENT_TABLE(CRQ_SignPage, CRQ_Page)
	EVT_TREE_SEL_CHANGED(ID_CRQ_CERT, CRQ_SignPage::CertSelChanged)
	EVT_RADIOBOX(ID_CRQ_SIGN, CRQ_Page::check_valid)
	EVT_WIZARD_PAGE_CHANGING(wxID_ANY, CRQ_SignPage::OnPageChanging)
END_EVENT_TABLE()


void CRQ_SignPage::CertSelChanged(wxTreeEvent& event) {
	tqslTrace("CRQ_SignPage::CertSelChanged", NULL);
	if (cert_tree->GetItemData(event.GetItem()))
		_parent->signIt = CRQ_SIGN_MAYBE;
	wxCommandEvent dummy;
	check_valid(dummy);
}

CRQ_SignPage::CRQ_SignPage(CRQWiz *parent, TQSL_CERT_REQ *crq)
	:  CRQ_Page(parent) {
	tqslTrace("CRQ_SignPage::CRQ_SignPage", "parent=%lx", reinterpret_cast<void *>(parent));

	initialized = false;
	wxSize sz = getTextSize(this);
	em_h = sz.GetHeight();
	em_w = sz.GetWidth();

	_parent = parent;
	wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);

	introText = new wxStaticText(this, -1, wxT(""), wxDefaultPosition, wxSize(em_w * 50, em_h * 8));
	sizer->Add(introText);
	introText->Wrap(_parent->maxWidth);

	tc_sign_status = new wxStaticText(this, -1, wxT(""), wxDefaultPosition, wxSize(_parent->maxWidth, em_h*3));

	cert_tree = new CertTree(this, ID_CRQ_CERT, wxDefaultPosition,
		wxSize(em_w*30, em_h*8), wxTR_HAS_BUTTONS | wxSUNKEN_BORDER);
	sizer->Add(cert_tree, 0, wxLEFT|wxRIGHT|wxBOTTOM|wxEXPAND);
	cert_tree->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
	ACCESSIBLE(cert_tree, TreeCtrlAx);
	sizer->Add(tc_sign_status, 0, wxALL|wxEXPAND, 10);
	// Default to 'signed' unless there's no valid certificates to use for signing.
	if (cert_tree->Build(0, &(_parent->provider)) > 0) {
		_parent->signIt = CRQ_NOT_SIGNED;
		cert_tree->Show(false);
		introContent = _("Since you have no Callsign Certificates, you must "
					"submit an 'Unsigned' certificate request. This will allow you to "
					"create your initial Callsign Certificate for LoTW use. "
					"Click 'Finish' to complete this Callsign Certificate request.");
		introText->SetLabel(introContent);
	}
	introText->Wrap(em_w * 50);
	AdjustPage(sizer, wxT("crq4.htm"));
	initialized = true;
}

void
CRQ_SignPage::refresh() {
	tqslTrace("CRQ_SignPage::refresh", NULL);
	if (cert_tree->Build(0, &(_parent->provider)) > 0 || _parent->ShouldBeSigned()) {
		cert_tree->Show(true);
		if (_parent->MustBeSigned()) {
			wxString it(wxT("\n\n\n"));
			it += _("This Callsign Certificate request requires approval using an existing Callsign Certificate.");
			introText->SetLabel(it);
		} else {
			wxString introContent = wxString(_("Is this new certificate for a callsign where you already have a LoTW account, and you want the QSOs for this call to be added to an existing LoTW account? "));
			introContent += wxT("\n\n");
			introContent += _("If so, choose a callsign below for the primary LoTW account. If not, click 'Finish', and a new LoTW account will be set up for these QSOs.");

			introContent += wxT("\n\n");
			introContent += _("CAUTION: Mixing QSOs for unrelated callsigns into one LoTW account can cause issues with handling awards.");
			introText->SetLabel(introContent);
		}
		introText->Wrap(em_w * 50);
	} else {
		// No certificates
		_parent->signIt = CRQ_NOT_SIGNED;
		introText->SetLabel(introContent);
		introText->Wrap(_parent->maxWidth);
	}
}

CRQ_Page *
CRQ_SignPage::GetPrev() const {
	tqslTrace("CRQ_SignPage::GetPrev", NULL);

	if (_parent->CertPwd)
		return _parent->pwPage;
	else
		return _parent->emailPage;
}

// Page validation

bool
CRQ_ProviderPage::TransferDataFromWindow() {
	// Nothing to validate
	return true;
}

static bool
validCallSign(const string& call) {
	// Check for invalid characters
	if (call.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/") != string::npos)
		return false;
	// Need at least one letter
	if (call.find_first_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ") == string::npos)
		return false;
	// Need at least one number
	size_t num;
	if ((num = call.find_first_of("0123456789")) == string::npos)
		return false;
	// At least one letter after the number - catches "/KP4" for example
	if (call.find_first_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ", num) == string::npos)
		return false;
	return true;
}

bool
CRQ_CallsignPage::validPrefix(const string& prefix) {
	wxRegEx r;
	prefixMap::iterator it;
	for (it = _parent->prefixRegex.begin(); it != _parent->prefixRegex.end(); it++) {
		if (it->second.IsEmpty())
			continue;
		wxString p = it->second + wxT("\\d*$");
		if (r.Compile(p, wxRE_EXTENDED) && r.Matches(wxString::FromUTF8(prefix.c_str()))) {
			return true;
		}
	}
	return false;
}

const char *
CRQ_CallsignPage::validate() {
	tqslTrace("CRQ_CallsignPage::validate", NULL);
	tQSL_Cert *certlist = 0;
	int ncert = 0;
	DXCC dx;
	bool dxok;
	wxRegEx r;
	wxString prefix;
	vector<int> allowedDXCC;
	wxArrayString bits;
	wxString callsign;
	const char *dxccname = NULL;
	bool ok = true;
	tQSL_Date oldStartDate = {-1, -1, -1};
	tQSL_Date oldEndDate = {-1, -1, -1};
	tQSL_Date startDate = {-1, -1, -1};
	tQSL_Date endDate = {-1, -1, -1};

	if (!initialized)
		return 0;
#ifdef DEBUG
	while (1) {
		std::ifstream lotsacalls("callsigns.txt");
		string call;
		while(lotsacalls >> call) {
			_parent->callsign = call;
#else
	_parent->callsign = tc_call->GetValue().MakeUpper();
#endif

	valMsg = wxT("");
	if (tc_call->GetValue().Len() > TQSL_CALLSIGN_MAX) {
		valMsg = wxString::Format(_("The callsign is too long. Only %d characters are allowed."), TQSL_CALLSIGN_MAX);
		tc_cs_status->SetLabel(valMsg);
		tc_cs_status->Wrap(_parent->maxWidth);
		return 0;
	}

	// First check if there's a slash. If so, it's a portable. Use the base callsign
	_parent->portable = false;
	callsign = _parent->callsign;
	_parent->modifier = wxT("");
	_parent->home_call = callsign;

	if (!callsign.IsEmpty()) {
		bits.Clear();
		wxStringTokenizer callsplitter(_parent->callsign, wxT("/"));
		while (callsplitter.CountTokens() > 0) {
			wxString temp = callsplitter.GetNextToken();
			// numbers alone aren't interesting
			long n;
			if (temp.ToLong(&n)) {
				continue;
			}
			// modifiers that don't need to be considered
			// but only for cases when used as suffixes
			if (bits.GetCount() > 0) {
				if (temp == wxT("M") || temp == wxT("P") ||
				    temp == wxT("R") || temp == wxT("MM") || temp == wxT("QRP")) {
					continue;
				}
			}
			bits.Add(temp);
		}

		switch (bits.GetCount()) {
			case 0:				// Single char or MM - nothing to see.
				break;
			case 1:
				_parent->home_call = bits[0];
				_parent->modifier = wxT("");
				break;
			case 2:
				_parent->home_call = bits[1];	// Assumes P5/W1AW
				_parent->modifier = bits[0];
				break;
			default:				// more than one slash, give up
				break;
		}
	}

	_parent->usa = false;					// Assume not USA

	// Shuffle time. Is one of these a valid callsign?
	if (!_parent->modifier.IsEmpty()) {
		// Is this Home/mod or mod/home ?
		if (validCallSign(std::string(_parent->modifier.mb_str())) && validPrefix(std::string(_parent->home_call.mb_str()))) {
			_parent->modifier = bits[1];	// Flip them as it's P5/W1AW
			_parent->home_call = bits[0];
		}
		_parent->portable = true;
		_parent->signIt = CRQ_SIGN_PORTABLE;
		if (isUSCallsign(_parent->modifier)) {
			_parent->usa = true;
		}
	} else {
		_parent->portable = false;
		if (isUSCallsign(_parent->home_call)) {
			_parent->usa = true;
		}
	}
#ifdef DEBUG
		std::cout << "callsign " <<_parent->callsign << " prefix " << _parent->modifier << " base " << _parent->home_call;
#endif
	int sel;

	_parent->onebyone = false;
	if (_parent->callsign.Len() < 3)
		ok = false;
	if (ok) {
		string call = string(_parent->callsign.ToUTF8());
		// Check for invalid characters
		if (call.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/") != string::npos)
			ok = false;
		// Need at least one letter
		if (call.find_first_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ") == string::npos)
			ok = false;
		// Need at least one number
		if (call.find_first_of("0123456789") == string::npos)
			ok = false;
		// Invalid callsign patterns
		// Starting with 0, Q, (no longer: C7, or 4Y)
		// 1x other than 1A, 1M, 1S
		string first = call.substr(0, 1);
		string second = call.substr(1, 1);
		string third = call.substr(2, 1);
		if (first == "0" || first == "Q" ||
		    (first == "1" && second != "A" && second != "M" && second != "S"))
			ok = false;
		if (call.size() == 3 &&
		    (first == "W" || first == "K" || first == "N") &&
		    (second >= "0" && second <= "9") &&
		    (third != "X")) {
			_parent->onebyone = true;
		}
	}

	if (!ok) {
		valMsg = _("You must enter a valid call sign.");
		tc_dxcc->Clear();
		goto notok;
	}

#ifndef DEBUG
	sel = tc_dxcc->GetSelection();
	if (tc_dxcc->IsEnabled() && sel >= 0) {
		_parent->dxcc = (long)(tc_dxcc->GetClientData(sel));
	}
	tc_dxcc->Clear();
#endif
	int found;
	found = -1;

	prefix = _parent->home_call;
	if (!_parent->modifier.IsEmpty()) {
		prefix = _parent->modifier;
	}

#ifdef DEBUG
	DXCC dx;
#endif
	if (!showAll && _parent->_crq == NULL) {
		prefixMap::iterator it;
		for (it = _parent->prefixRegex.begin(); it != _parent->prefixRegex.end(); it++) {
#ifndef DEBUG
			if (_parent->usa && isUSEntity(it->first)) {
					allowedDXCC.push_back(it->first);
			} else {
#else
{
#endif
				if (it->second.IsEmpty())
					continue;
				if (r.Compile(it->second, wxRE_EXTENDED) && r.Matches(prefix)) {
#ifdef DEBUG
					dx.getByEntity(it->first);
					std::cout << " " << it->first << ":" << _parent->entityNames[it->first];
#endif
					allowedDXCC.push_back(it->first);
				}
			}
		}
	}
#ifdef DEBUG
	std::cout << std::endl;
		}
		lotsacalls.close();
		exit(0);
	}
#else
	dxok = dx.getFirst();
	while (dxok) {
		if (showAll || allowedDXCC.size() == 0 || dx.number() == 0) {
			wxString ename = _parent->entityNames[dx.number()];
			if (ename.IsEmpty())
				ename = wxString::FromUTF8(dx.name());
			tc_dxcc->Append(ename, reinterpret_cast<void *>(dx.number()));
			if (sel >= 0 && dx.number() == _parent->dxcc) {
				found = tc_dxcc->GetCount() - 1;
			}
			dxok = dx.getNext();
			continue;
		}
		for (size_t i = 0; i < allowedDXCC.size(); i++) {
			if (allowedDXCC[i] == dx.number()) {
				wxString ename = _parent->entityNames[dx.number()];
				if (ename.IsEmpty())
					ename = wxString::FromUTF8(dx.name());
				tc_dxcc->Append(ename, reinterpret_cast<void *>(dx.number()));
				break;
			}
		}
		if (sel >= 0 && dx.number() == _parent->dxcc) {
			found = tc_dxcc->GetCount() - 1;
		}
		dxok = dx.getNext();
	}

	if (found >= 0) {
		tc_dxcc->SetSelection(found);
	} else {
		tc_dxcc->SetSelection(1);
	}

	long old_dxcc;
	old_dxcc = _parent->dxcc;

	tqsl_getDXCCStartDate(old_dxcc, &oldStartDate);
	tqsl_getDXCCEndDate(old_dxcc, &oldEndDate);
	sel = tc_dxcc->GetSelection();
	if (sel >= 0)
		_parent->dxcc = (long)(tc_dxcc->GetClientData(sel));

	tqsl_getDXCCStartDate(_parent->dxcc, &startDate);
	tqsl_getDXCCEndDate(_parent->dxcc, &endDate);

	if (sel < 0 || _parent->dxcc < 0) {
		valMsg = _("You must select a DXCC entity.");
		goto notok;
	}

	if (_parent->dxcc != old_dxcc) {
		if (tqsl_isDateValid(&startDate) && !tqsl_isDateNull(&startDate) &&
		    tqsl_isDateValid(&oldStartDate) && !tqsl_isDateNull(&oldStartDate) &&
		    tqsl_compareDates(&_parent->qsonotbefore, &oldStartDate) == 0) {
			tc_qsobeginy->SetSelection(startDate.year - 1945);
			tc_qsobeginm->SetSelection(startDate.month - 1);
			tc_qsobegind->SetSelection(startDate.day - 1);
		}
		if ((tqsl_isDateValid(&endDate) || tqsl_isDateNull(&endDate)) &&
		    tqsl_isDateValid(&oldEndDate) && !tqsl_isDateNull(&oldEndDate) &&
		     tqsl_compareDates(&_parent->qsonotafter, &oldEndDate) == 0) {
			if (tqsl_isDateNull(&endDate)) {
				tc_qsoendy->SetSelection(0);
				tc_qsoendm->SetSelection(0);
				tc_qsoendd->SetSelection(0);
			} else {
				tc_qsoendy->SetSelection(endDate.year - 1944);
				tc_qsoendm->SetSelection(endDate.month);
				tc_qsoendd->SetSelection(endDate.day);
			}
		}
	}
	_parent->qsonotbefore.year = strtol(tc_qsobeginy->GetValue().ToUTF8(), NULL, 10);
	_parent->qsonotbefore.month = strtol(tc_qsobeginm->GetValue().ToUTF8(), NULL, 10);
	_parent->qsonotbefore.day = strtol(tc_qsobegind->GetValue().ToUTF8(), NULL, 10);
	_parent->qsonotafter.year = strtol(tc_qsoendy->GetValue().ToUTF8(), NULL, 10);
	_parent->qsonotafter.month = strtol(tc_qsoendm->GetValue().ToUTF8(), NULL, 10);
	_parent->qsonotafter.day = strtol(tc_qsoendd->GetValue().ToUTF8(), NULL, 10);
	if (!tqsl_isDateValid(&_parent->qsonotbefore)) {
		valMsg = _("QSO begin date: You must choose proper values for Year, Month and Day.");
		goto notok;
	}
	if (!tqsl_isDateNull(&_parent->qsonotafter) && !tqsl_isDateValid(&_parent->qsonotafter)) {
		valMsg = _("QSO end date: You must either choose proper values for Year, Month and Day or leave all three blank.");
		goto notok;
	}
	if (tqsl_isDateValid(&_parent->qsonotbefore) && tqsl_isDateValid(&_parent->qsonotafter)
		&& tqsl_compareDates(&_parent->qsonotbefore, &_parent->qsonotafter) > 0) {
		valMsg = _("QSO end date cannot be before QSO begin date.");
		goto notok;
	}
	char startStr[50], endStr[50];
	tqsl_convertDateToText(&endDate, endStr, sizeof endStr);
	if (tqsl_getDXCCEntityName(_parent->dxcc, &dxccname))
		dxccname = "UNKNOWN";
	if (!tqsl_isDateValid(&startDate)) {
		startDate.year = 1945; startDate.month = 11; startDate.day = 1;
	}
	tqsl_convertDateToText(&startDate, startStr, sizeof startStr);

	if (tqsl_isDateValid(&endDate) && tqsl_isDateNull(&_parent->qsonotafter)) {
		_parent->qsonotafter = endDate;
		if (tqsl_isDateNull(&endDate)) {
			tc_qsoendy->SetSelection(0);
			tc_qsoendm->SetSelection(0);
			tc_qsoendd->SetSelection(0);
		} else {
			tc_qsoendy->SetSelection(endDate.year - 1944);
			tc_qsoendm->SetSelection(endDate.month);
			tc_qsoendd->SetSelection(endDate.day);
		}
	}

	if (tqsl_isDateValid(&endDate)) {
		tqsl_convertDateToText(&endDate, endStr, sizeof endStr);
	} else {
		endStr[0] = '\0';
	}

	if (tqsl_isDateValid(&startDate) && tqsl_compareDates(&_parent->qsonotbefore, &startDate) < 0) {
		valMsg = wxString::Format(_("The date of your first QSO is before the first valid date (%hs) of the selected DXCC Entity %hs"), startStr, dxccname);
		goto notok;
	}
	if (tqsl_isDateValid(&endDate) && tqsl_compareDates(&_parent->qsonotbefore, &endDate) > 0) {
		valMsg = wxString::Format(_("The date of your first QSO is after the last valid date (%hs) of the selected DXCC Entity %hs"), endStr, dxccname);
		goto notok;
	}
	if (tqsl_isDateValid(&startDate) && !tqsl_isDateNull(&_parent->qsonotafter) && tqsl_compareDates(&_parent->qsonotafter, &startDate) < 0) {
		valMsg = wxString::Format(_("The date of your last QSO is before the first valid date (%hs) of the selected DXCC Entity %hs"), startStr, dxccname);
		goto notok;
	}
	if (tqsl_isDateValid(&endDate) && tqsl_compareDates(&_parent->qsonotafter, &endDate) > 0) {
		valMsg = wxString::Format(_("The date of your last QSO is after the last valid date (%hs) of the selected DXCC Entity %hs"), endStr, dxccname);
		goto notok;
	}

	_parent->callsign.MakeUpper();
	// Check for US 1x1 callsigns
	_parent->usa = false;
	for (int i = 0; USEntities[i] > 0; i++) {
		if (_parent->dxcc == USEntities[i]) {
			_parent->usa = true;
			break;
		}
	}
	if (!_parent->usa || _parent->callsign.Len() != 3 || FindFocus() == tc_call) {
		_parent->onebyone = false;
	} else {
		// 1x1 callsigns - must have W/K/N as the first character
		// a number as the second and letters A-Z *except* X as
		// the final character
		char first = _parent->callsign[0];
		char second = _parent->callsign[1];
		char third = _parent->callsign[2];
		if (first != 'W' && first != 'K' && first != 'N') {
		valMsg = _("US 1x1 callsigns must start with W, K, or N");
			goto notok;
		}
		if (second < '0' || second > '9') {
			valMsg = _("US 1x1 callsigns must have a number as the second character");
			goto notok;
		}
		if (third < 'A' || third > 'Z' || third == 'X') {
			valMsg = _("US 1x1 callsigns must end in letters A-Z excluding 'X'");
			goto notok;
		}
	}
	// Check for valid 1x1 callsign
	if (_parent->onebyone && !tqsl_isDateValid(&_parent->qsonotafter)) {
		valMsg = _("US 1x1 callsign requests must provide an end date");
		goto notok;
	}

	// Are there any valid certificates for this DXCC entity?
	bool existsValidForEntity;
	existsValidForEntity = false;

	if (!tqsl_selectCertificates(NULL, &ncert, NULL, _parent->dxcc, 0,
				&(_parent->provider), 0)) {
		existsValidForEntity = (ncert > 0);
	}

	// Data looks okay, now let's make sure this isn't a duplicate request
	// (unless it's a renewal).

	if (tqsl_selectCertificates(&certlist, &ncert, _parent->callsign.ToUTF8(), _parent->dxcc, 0,
				&(_parent->provider), 0)) {
		ncert = 0;
	}
	if (!_parent->renewal && ncert > 0) {			// New request, have cert for this callsign
		char cert_before_buf[40], cert_after_buf[40];
		for (int i = 0; i < ncert; i++) {
			// See if this cert overlaps the user-specified date range
			tQSL_Date cert_not_before, cert_not_after;
			int cert_dxcc = 0;
			tqsl_getCertificateQSONotBeforeDate(certlist[i], &cert_not_before);
			tqsl_getCertificateQSONotAfterDate(certlist[i], &cert_not_after);
			tqsl_getCertificateDXCCEntity(certlist[i], &cert_dxcc);
			if (cert_dxcc == _parent->dxcc
					&& ((tqsl_isDateValid(&_parent->qsonotafter)
					&& !(tqsl_compareDates(&_parent->qsonotbefore, &cert_not_after) == 1
					|| tqsl_compareDates(&_parent->qsonotafter, &cert_not_before) == -1))
					|| (!tqsl_isDateValid(&_parent->qsonotafter)
					&& !(tqsl_compareDates(&_parent->qsonotbefore, &cert_not_after) == 1)))) {
				ok = false;	// Overlap!
				tqsl_convertDateToText(&cert_not_before, cert_before_buf, sizeof cert_before_buf);
				tqsl_convertDateToText(&cert_not_after, cert_after_buf, sizeof cert_after_buf);
			}
		}
		if (ok == false) {
			DXCC dxcc;
			dxcc.getByEntity(_parent->dxcc);
			// TRANSLATORS: first argument is callsign (%s), second is the related DXCC entity name (%hs)
			valMsg = wxString::Format(_("You have an overlapping Certificate for %s (DXCC=%hs) having QSO dates: "), _parent->callsign.c_str(), dxcc.name());
			// TRANSLATORS: here "to" separates two dates in a date range
			valMsg += wxString::FromUTF8(cert_before_buf) + _(" to ") + wxString::FromUTF8(cert_after_buf);
		}
	}
	tqsl_freeCertificateList(certlist, ncert);
	certlist = NULL;
	ncert = 0;
	{
		wxString pending = wxConfig::Get()->Read(wxT("RequestPending"));
		wxStringTokenizer tkz(pending, wxT(","));
		while (tkz.HasMoreTokens()) {
			wxString pend = tkz.GetNextToken();
			if (pend == _parent->callsign) {
				wxString fmt = _("You have already requested a Callsign Certificate for %s and can not request another until that request has been processed by LoTW Staff.");
					fmt += wxT("\n\n");
					fmt += _("Please wait until you receive an e-mail bearing your requested Callsign Certificate.");
					fmt += wxT("\n\n");
					fmt += _("If you are sure that the earlier request is now invalid you should delete the pending Callsign Certificate for %s.");
				valMsg = wxString::Format(fmt, _parent->callsign.c_str(), _parent->callsign.c_str());
				goto notok;
			}
		}
	}
	 {
		wxString requestRecord = wxConfig::Get()->Read(wxT("RequestRecord"));
		wxString requestList;
		wxStringTokenizer rectkz(requestRecord, wxT(","));
		time_t now = time(NULL);
		time_t yesterday = now - 24 * 60 * 60; // 24 hours ago
		int numRequests = 0;
		while (rectkz.HasMoreTokens()) {
			wxString rec = rectkz.GetNextToken();
			char csign[512];
			time_t rectime;
			strncpy(csign, rec.ToUTF8(), sizeof csign);
			char *s = csign;
			while (*s != ':' && *s != '\0')
				s++;
			*s = '\0';
			rectime = strtol(++s, NULL, 10);
			if (rectime < yesterday) continue;		// More than 24 hours old
			if (strcmp(csign, _parent->callsign.ToUTF8()) == 0) { // Same call
				numRequests++;
			}
			if (!requestList.IsEmpty()) {
				requestList = requestList + wxT(",");
			}
			requestList = requestList + wxString::Format(wxT("%hs:%Lu"), csign, rectime);
		}
		wxConfig::Get()->Write(wxT("RequestRecord"), requestList);
		wxConfig::Get()->Flush();

		if (numRequests > 3) {
			wxString fmt = _("You have already requested more than three Callsign Certificates for %s in the past 24 hours. You should submit a request only once, then wait for that request to be processed by LoTW Staff. This may take several business days.");
					fmt += wxT("\n\n");
					fmt += _("Please wait until you receive an e-mail bearing your requested Callsign Certificate.");
					fmt += wxT("\n\n");
			valMsg = wxString::Format(fmt, _parent->callsign.c_str(), _parent->callsign.c_str());
		}
		if (!_parent->renewal && existsValidForEntity) {
			_parent->signIt = CRQ_SIGN_REPLACEMENT;
		}
	}
#endif // DEBUG
notok:
	tc_cs_status->SetLabel(valMsg);
	tc_cs_status->Wrap(_parent->maxWidth);
	return 0;
}

bool
CRQ_CallsignPage::TransferDataFromWindow() {
	tqslTrace("CRQ_CallsignPage::TransferDataFromWindow", NULL);
	bool ok;

	validate();

	bool hasEndDate = (!tqsl_isDateNull(&_parent->qsonotafter) && tqsl_isDateValid(&_parent->qsonotafter));
	bool notInULS = false;

	_parent->signIt = CRQ_SIGN_MAYBE;

	_parent->goodULSData = false;
	// Is this in the ULS?
	if (valMsg.IsEmpty() && _parent->usa && !_parent->onebyone && isUSCallsign(_parent->callsign)) {
		wxString name, attn, addr1, city, state, zip;
		int stat;
		if (_parent->networkError) {
			stat = 3;		// reflect network error
		} else {
			stat = GetULSInfo(_parent->callsign.ToUTF8(), name, attn, addr1, city, state, zip);
		}
		// handle portable/home and home/portable
		if (stat == 2 && _parent->portable && !_parent->networkError) {
			stat = GetULSInfo(_parent->modifier.ToUTF8(), name, attn, addr1, city, state, zip);
		}
		int stat2 = 0;
		switch (stat) {
			case 0:
				_parent->validusa = true;		// Good data returned
				if (name == wxT("null"))
					name = wxT("");
				_parent->name = name;
				_parent->namePage->setName(name);

				if (addr1 == wxT("null"))
					addr1 = _parent->addr1;
				if (attn == wxT("null")) {
					attn = wxT("");
					_parent->addr1 = addr1;
					_parent->addr2 = wxT("");
					_parent->namePage->setAddr1(addr1);
					_parent->namePage->setAddr2(attn);
				} else {
					_parent->addr1 = attn;
					_parent->addr2 = addr1;
					_parent->namePage->setAddr1(attn);
					_parent->namePage->setAddr2(addr1);
				}

				if (city == wxT("null"))
					city = _parent->city;
				_parent->city = city;
				_parent->namePage->setCity(city);

				if (state == wxT("null"))
					state = _parent->state;
				_parent->state = state;
				_parent->namePage->setState(state);

				if (zip == wxT("null"))
					zip = _parent->zip;
				_parent->zip = zip;
				_parent->namePage->setZip(zip);

				_parent->country = wxT("USA");
				_parent->namePage->setCountry(_parent->country);
				if (!_parent->name.IsEmpty() && !_parent->addr1.IsEmpty() && !_parent->city.IsEmpty()) {
					_parent->goodULSData = true;
				}
				break;
			case 1:
				break;						// Error reading ULS info
			case 2:
				stat2 = GetULSInfo("W1AW", name, attn, addr1, city, state, zip);
				if (stat2 == 2)					// Also nothing for a good call
					break;
				if (hasEndDate) {				// Allow former calls
					if (!_parent->validcerts) {
						valMsg = _("You cannot request a Callsign Certificate for a former callsign unless you hold a valid Callsign Certificate to be used to verify that request.");
					}
					notInULS = true;
					break;
				}
				valMsg = wxString::Format(_("The callsign %s is not currently registered in the FCC ULS database.\nIf this is a newly registered call, you must wait at least one business day for it to be valid. Please enter a currently valid callsign."), _parent->callsign.c_str());
				break;
			case 3:
				_parent->networkError = true;			// Error reading
				break;
		}
	}

	// Is this potentially CEPT ?
	if (valMsg.IsEmpty() && _parent->usa && !_parent->modifier.IsEmpty() && !isUSCallsign(_parent->modifier)) {
		wxMessageBox(_("If you are using a US callsign outside of the US persuant to CEPT, IARP or other Reciprocity arrangements, FCC rules require you to be a US Citizen."), _("Warning"), wxOK | wxICON_WARNING, this);
	}
	if (valMsg.IsEmpty()) {
		// If this call has a slash, then it may be a portable call from
		// outside the US. We really can't tell at this point so just
		// let it go.
		if (!_parent->modifier.IsEmpty()) {
			_parent->signIt = CRQ_SIGN_PORTABLE;
		}
		ok = true;
	} else {
		wxMessageBox(valMsg, _("Error"), wxOK | wxICON_ERROR, this);
		ok = false;
	}
	if (ok && _parent->dxcc == 0) {
		if (!_parent->validcerts) {
			wxString msg = _("You cannot select DXCC Entity NONE as you must sign any request for entity NONE and you have no valid Callsign Certificates that you can use to sign this request.");
			wxMessageBox(msg, _("TQSL Error"), wxOK | wxICON_ERROR, this);
			return false;
		}

		_parent->signIt = CRQ_SIGN_NONE;
		wxString msg = _("You have selected DXCC Entity NONE");
			msg += wxT("\n\n");
			msg += _("QSO records signed using the Certificate will not be valid for DXCC award credit (but will be valid for other applicable awards). If the Certificate is to be used for signing QSOs from maritime/marine mobile, shipboard, or air mobile operations, that is the correct selection. Otherwise, you probably should use the \"Back\" button to return to the DXCC page after clicking \"OK\"");
		wxMessageBox(msg, _("TQSL Warning"), wxOK | wxICON_WARNING, this);
	}
	if (ok && _parent->onebyone) {
		if (!_parent->validcerts) {
			wxString msg = _("You cannot request a certificate for a 1x1 callsign as you must sign those requests, but you have no valid Callsign Certificates that you can use to sign this request.");
			wxMessageBox(msg, _("TQSL Error"), wxOK | wxICON_ERROR, this);
			return false;
		}
		_parent->signIt = CRQ_SIGN_1X1;
	}

	if (ok && _parent->signIt == CRQ_SIGN_PORTABLE) {
		if (!_parent->validcerts) {
			wxString msg = _("You cannot request a certificate for a portable callsign as you must sign those requests, but you have no valid Callsign Certificates that you can use to sign this request.");
			wxMessageBox(msg, _("TQSL Error"), wxOK | wxICON_ERROR, this);
			return false;
		}
	}

	if (ok && hasEndDate && !notInULS && !_parent->onebyone) {	// If it has an end date and it's a current call
		wxString msg = _("You have chosen a QSO end date for this Callsign Certificate. The 'QSO end date' should ONLY be set if that date is the date when that callsign's license expired or the license was replaced by a new callsign.");
			msg += wxT("\n\n");
			msg += _("If you set an end date, you will not be able to sign QSOs past that date, even if the Callsign Certificate itself is still valid.");
			msg += wxT("\n\n");
			msg += _("If you still hold this callsign (or if you plan to renew the license for the callsign), you should not set a 'QSO end date'.");
			msg += wxT("\n");
			msg += _("Do you really want to keep this 'QSO end date'?");
		if (wxMessageBox(msg, _("Warning"), wxYES_NO|wxICON_EXCLAMATION, this) == wxNO) {
				tc_qsoendy->SetSelection(0);
				tc_qsoendm->SetSelection(0);
				tc_qsoendd->SetSelection(0);
				return false;
		}
	}
	_parent->callsign = tc_call->GetValue();
	_parent->callsign.MakeUpper();
	tc_call->SetValue(_parent->callsign);
	return ok;
}

static bool
cleanString(wxString &str) {
	str.Trim();
	str.Trim(FALSE);
	return str.IsEmpty();
}

const char *
CRQ_NamePage::validate() {
	tqslTrace("CRQ_NamePage::validate", NULL);
	if (!initialized)
		return 0;
	valMsg = wxT("");
	_parent->name = tc_name->GetValue();
	_parent->addr1 = tc_addr1->GetValue();
	_parent->city = tc_city->GetValue();

	if (cleanString(_parent->name)) {
		valMsg = _("You must enter your name");
	} else if (cleanString(_parent->addr1)) {
		valMsg = _("You must enter your address");
	} else if (cleanString(_parent->city)) {
		valMsg = _("You must enter your city");
	}
	tc_addr_status->SetLabel(valMsg);
	if (!valMsg.IsEmpty()) {
		tc_name->Enable(true);
		tc_addr1->Enable(true);
		tc_addr2->Enable(true);
		tc_city->Enable(true);
		tc_state->Enable(true);
		tc_zip->Enable(true);
		tc_country->Enable(true);
		return 0;
	}
	//
	// If this is not a renewal, and it's in the USA, and there's no certs to sign it with,
	// and we got a valid ULS address, then this is an initial certificate and must match the FCC database. Say so.
	//
	if (_parent->goodULSData && !_parent->renewal && _parent->validusa && !_parent->validcerts) {
		tc_addr_status->SetLabel(_("This address must match the FCC ULS database.\nIf this address information is incorrect, please correct your FCC record."));
		tc_name->Enable(false);
		tc_addr1->Enable(false);
		tc_addr2->Enable(false);
		tc_city->Enable(false);
		tc_state->Enable(false);
		tc_zip->Enable(false);
		tc_country->Enable(false);
	}
	return 0;
}

bool
CRQ_NamePage::TransferDataFromWindow() {
	tqslTrace("CRQ_NamePage::TransferDataFromWindow", NULL);
	_parent->name = tc_name->GetValue();
	_parent->addr1 = tc_addr1->GetValue();
	_parent->addr2 = tc_addr2->GetValue();
	_parent->city = tc_city->GetValue();
	_parent->state = tc_state->GetValue();
	_parent->zip = tc_zip->GetValue();
	_parent->country = tc_country->GetValue();

	bool ok;
	validate();
	if (valMsg.IsEmpty()) {
		ok = true;
	} else {
		wxMessageBox(valMsg, _("Error"), wxOK | wxICON_ERROR, this);
		ok = false;
	}

	cleanString(_parent->name);
	cleanString(_parent->addr1);
	cleanString(_parent->addr2);
	cleanString(_parent->city);
	cleanString(_parent->state);
	cleanString(_parent->zip);
	cleanString(_parent->country);
	tc_name->SetValue(_parent->name);
	tc_addr1->SetValue(_parent->addr1);
	tc_addr2->SetValue(_parent->addr2);
	tc_city->SetValue(_parent->city);
	tc_state->SetValue(_parent->state);
	tc_zip->SetValue(_parent->zip);
	tc_country->SetValue(_parent->country);
	wxConfig *config = reinterpret_cast<wxConfig *>(wxConfig::Get());
	config->Write(wxT("Name"), _parent->name);
	config->Write(wxT("Addr1"), _parent->addr1);
	config->Write(wxT("Addr2"), _parent->addr2);
	config->Write(wxT("City"), _parent->city);
	config->Write(wxT("State"), _parent->state);
	config->Write(wxT("ZIP"), _parent->zip);
	config->Write(wxT("Country"), _parent->country);
	return ok;
}

const char *
CRQ_EmailPage::validate() {
	tqslTrace("CRQ_EmailPage::validate()", NULL);

	if (!initialized)
		return 0;
	valMsg = wxT("");
	_parent->email = tc_email->GetValue();
	cleanString(_parent->email);
	int i = _parent->email.First('@');
	int j = _parent->email.Last('.');
	if (i < 1 || j < i+2 || j == static_cast<int>(_parent->email.length())-1)
		valMsg = _("You must enter a valid email address");
	tc_em_status->SetLabel(valMsg);
	return 0;
}

bool
CRQ_EmailPage::TransferDataFromWindow() {
	tqslTrace("CRQ_EmailPage::TransferDataFromWindow", NULL);
	bool ok;
	validate();
	if (valMsg.IsEmpty()) {
		ok = true;
	} else {
		wxMessageBox(valMsg, _("Error"), wxOK | wxICON_ERROR, this);
		ok = false;
	}

	_parent->email = tc_email->GetValue();
	cleanString(_parent->email);
	wxConfig *config = reinterpret_cast<wxConfig *>(wxConfig::Get());
	config->Write(wxT("Email"), _parent->email);
	return ok;
}

const char *
CRQ_PasswordPage::validate() {
	tqslTrace("CRQ_PasswordPage::validate", NULL);

	if (!initialized)
		return 0;
	valMsg = wxT("");
	wxString pw1 = tc_pw1->GetValue();
	wxString pw2 = tc_pw2->GetValue();

	if (pw1 != pw2)
		valMsg = _("The two copies of the passphrase do not match.");
	tc_pwd_status->SetLabel(valMsg);
	return 0;
}

bool
CRQ_PasswordPage::TransferDataFromWindow() {
	tqslTrace("CRQ_PasswordPage::TransferDataFromWindow", NULL);
	bool ok;
	validate();
	if (valMsg.IsEmpty()) {
		ok = true;
	} else {
		wxMessageBox(valMsg, _("Error"), wxOK | wxICON_ERROR, this);
		ok = false;
	}
	_parent->password = tc_pw1->GetValue();
	return ok;
}

void
CRQ_SignPage::OnPageChanging(wxWizardEvent& ev) {
	tqslTrace("CRQ_SignPage::OnPageChanging", "Direction=", ev.GetDirection());

	validate();
	if (!valMsg.IsEmpty() && ev.GetDirection()) {
		ev.Veto();
		wxMessageBox(valMsg, _("TQSL Error"), wxOK | wxICON_ERROR, this);
	}
}


const char *
CRQ_SignPage::validate() {
	tqslTrace("CRQ_SignPage::validate", NULL);
	bool error = false;

	if (!initialized)
		return 0;

	valMsg = wxT("");
	wxString nextprompt = _("Click 'Finish' to complete this Callsign Certificate request.");

	bool doSigned = _parent->ShouldBeSigned();
	cert_tree->Show(doSigned);

	if (!_parent->MustBeSigned()) {
		nextprompt = _("Please select a Callsign Certificate for the account where you would like the QSOs to be stored");
	} else {
		if (_parent->signIt == CRQ_SIGN_REPLACEMENT) {
			valMsg = _("Please select the Callsign Certificate for your current personal callsign to validate your request.");
		} else {
			valMsg = _("Please select a Callsign Certificate to validate this request");
		}
		if (!cert_tree->GetSelection().IsOk() || cert_tree->GetItemData(cert_tree->GetSelection()) == NULL) {
			error = true;
			if (_parent->signIt == CRQ_SIGN_REPLACEMENT) {
				valMsg = _("Please select the Callsign Certificate for your current personal callsign to validate your request.");
			} else {
				valMsg = _("Please select a Callsign Certificate to validate this request");
			}
		} else {
			char callsign[512];
			tQSL_Cert cert = cert_tree->GetItemData(cert_tree->GetSelection())->getCert();
			if (0 == tqsl_getCertificateCallSign(cert, callsign, sizeof callsign)) {
				wxString fmt = wxT("\n\n");
					fmt += _("QSOs for %hs will be stored in the LoTW account for %s.");
				nextprompt+=wxString::Format(fmt, _parent->callsign.c_str(), callsign);
			}
		}
	}

	tc_sign_status->SetLabel(error ? valMsg : nextprompt);
	tc_sign_status->Wrap(_parent->maxWidth);
	return 0;
}

bool
CRQ_SignPage::TransferDataFromWindow() {
	tqslTrace("CRQ_SignPage::TransferDataFromWindow", NULL);
	validate();

	_parent->cert = 0;
	CertTreeItemData *data = reinterpret_cast<CertTreeItemData *>(cert_tree->GetItemData(cert_tree->GetSelection()));
	if (data)
		_parent->cert = data->getCert();
	return true;
}

