// ----------------------------------------------------------------------------
// hamcalllib.cxx, cloned from qrzlib.cxx
//
// Interface library to the HamCall database distributed by Buckmaster.
// Accessed via tcp socket to buckmaster tcp server.
// Most changes to fldigi source identified by:
//   #ifdef HAMCALL_DVD
//   #endif // HAMCALL_DVD
//
// Copyright (C) 1999-2009 David Freese
// Copyright (C) 2014 Steven Passe AD0ES
//
// This file is part of fldigi.
//
// Fldigi is free software: you can redistribute it and/or modify
// 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <string>

// for the network code
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <iostream>
using namespace std;

#include "hamcalllib.h"
#include "configuration.h"
#include "debug.h"
#include "socket.h"


void HAMCALL::OpenHAMCALLServer(const char* ipaddr, const char* ipport)
{
	//LOG_INFO("OpenHAMCALLserver: %s %s", ipaddr, ipport);

	int status;

	status = OpenServer(ipaddr, ipport);
	if (status >= 0)
		HAMCALLvalid = 1;
	else
		HAMCALLvalid = 0;
}

HAMCALL::HAMCALL(const char* ipaddr, const char* ipport)
{
	//LOG_INFO("HAMCALL created, ipaddr:ipport:: %s:%s", ipaddr, ipport);

	criteria = '?';
	OpenHAMCALLServer(ipaddr, ipport);
}

HAMCALL::HAMCALL(const char* ipaddr, const char* ipport, char c)
{
	LOG_ERROR("HAMCALL created, wants criteria: '%c'", c);

	criteria = c;
	OpenHAMCALLServer(ipaddr, ipport);
}

void HAMCALL::NewDBpath(const char* ipaddr, const char* ipport)
{
	LOG_INFO("HAMCALL NewDBpath, ipaddr:ipport:: %s:%s", ipaddr, ipport);

	criteria = '?';
	OpenHAMCALLServer(ipaddr, ipport);
}


HAMCALL::~HAMCALL()
{
	if (index != NULL )
		free(index);
	if (data != NULL)
		free(data);
	if (datafile != NULL)
		fclose(datafile);

	return;
}

int HAMCALL::FindCallsign(char* field)
{
	LOG_ERROR("FindCallSign()\n");

	found = 0;
	return (0);
}

int HAMCALL::FindName(char* field)
{
	LOG_ERROR("FindName()\n");

	found = 0;
	return (0);
}

int HAMCALL::FindState(char* field)
{
	LOG_ERROR("FindState()\n");

	found = 0;
	return (0);
}

int HAMCALL::FindZip(char* field)
{
	LOG_ERROR("FindZip()\n");

	found = 0;
	return (0);
}

// This isn't finished...
int HAMCALL::FindRecord(char* field)
{
	//LOG_INFO("FindRecord(%s), criteria is: '%c'\n", field, criteria);

	if (HAMCALLvalid == 0)
	  return (0);

	switch (criteria) {
		case 'c' :
			FindCallsign(field);
			break;
		case 'n' :
			FindName(field);
			break;
		case 's' :
			FindState(field);
			break;
		case 'z' :
			FindZip(field);
	}

	int status = CallServer(field);
	if (status < 0) {
		LOG_ERROR("server call FAILURE");
		return (0);
	}

	if (strncmp(recbuffer, "lookup ERROR", 12) == 0)
		found = 0;
	else
		found = 1;

	return (ReadRec());
}

static char empty[] = { '\0' };

/**********************************************************************************************
 * From ham/API/layout.txt:
 *
 * Records are variable length, and have no CR/LF at the end.
 * The field delimiters are high ASCII codes.
 *
 * Data is stored in following way:
 * the (181) represents literal ASCII character 181, (182) represents ASCII 182, etc:
 *
 * [note: 181 never appears in the record, record[0] is 1st character of call sign]
 * [note: by ASCII, they mean decimal value]
 *
 * (181)N1BIC(182)E(183)IZ(184)Jack(185)A(186)Speer(188)6196 Jefferson Highway...
 *
 * In the above example, note there is no ASCII 187 because there is no data for
 * that field (suffix).  Only the necessary codes are present.  There will always 
 * be a callsign.
 *
 * Be sure to code your data parsing routine to ignore unknown field (ASCII)
 * codes.  That way, when we add more fields to the database, it won't break your
 * program.
 *
 * Here are the possible fields:
 *
ASCII Prefix        Data
------------        ----
181 . . . . . . . . Callsign
182 . . . . . . . . Class (E,A,G,P,T,N for U.S.)
                           E - Extra
                           A - Advanced
                           G - General
                           P - Tech. Plus
                           T - Technician
                           N - Novice
183 . . . . . . . . BmCode (one or more characters)
                            I - U.S. Individual         (FCC)
                            A - Internatational
                            X - Cross Reference         (FCC*)
                            C - Club                    (FCC)
                            M - Military                (FCC)
                            R - RACES                   (FCC)
                            D - FCC Delete              (FCC)
                            L - Requested deletion from HamCall
                            Q - Silent Key, as listed in QST
                            V - Vanity Callsign         (FCC)
                            W - LOTW User
                            Z - Internal use only
                            - - No longer in FCC database
184 . . . . . . . . First Name
185 . . . . . . . . Middle
186 . . . . . . . . Last (DX records will use this field, for the entire name)
187 . . . . . . . . Suffix
188 . . . . . . . . Street
189 . . . . . . . . Prefix Country (Country name of prefix; for example DL is Germany.
				    Also see field #209, Mailing Address Country)
190 . . . . . . . . (not used)
191 . . . . . . . . City
192 . . . . . . . . State/Province
193 . . . . . . . . Postal Code
194 . . . . . . . . BirthDate yyyymmdd
195 . . . . . . . . DateFirstIssue yyyymmdd
196 . . . . . . . . ExpirationDate yyyymmdd
197 . . . . . . . . ProcessDate (date of last change) yyyymmdd
198 . . . . . . . . County
199 . . . . . . . . Hours +/- GMT
200 . . . . . . . . Latitude (degrees.decimal degrees)
201 . . . . . . . . Longitude (degrees.decimal degrees)
202 . . . . . . . . Grid Square
203 . . . . . . . . Area Code
204 . . . . . . . . Previous Call
205 . . . . . . . . Previous Class
206 . . . . . . . . Transaction type (FCC designation)
207 . . . . . . . . E-mail address
208 . . . . . . . . QSL Manager (Be sure to display this field,
				 sometimes hams do not want QSLs at their home address!)
209 . . . . . . . . Mailing Address Country (if different from prefix country)
210 . . . . . . . . URL (WWW Home page)
211 . . . . . . . . Vanity eligibility flag
                           A - Former primary station holder
                           B - Close relative of primary station holder
                           C - Former club station holder
                           D - Club station with consent of close relative of
                               former holder
                           E - Primary station preference list
                           F - Club station preference list
212 . . . . . . . . Fax Number
213 . . . . . . . . Interest profile (pointers to entries in \HAM0\INTEREST)
214 . . . . . . . . Phone Number (from FCC)
215 . . . . . . . . FCC Licensee ID
216 . . . . . . . . 10-10 Number
217 . . . . . . . . FCC FRN Number
218 . . . . . . . . IOTA Number
219 . . . . . . . . FISTS Number
220 . . . . . . . . QCWA Number
221 . . . . . . . . OOTC Number
222 . . . . . . . . FCC License Key
254 . . . . . . . . Last edited by
**********************************************************************************************/

enum recordIds {
  Callsign		= 181,
  Class			= 182,		// E,A,G,P,T,N for U.S.
  BmCode		= 183,		// one or more characters
  FirstName		= 184,
  Middle		= 185,
  Last			= 186,		// DX records will use this field, for the entire name
  Suffix		= 187,
  Street		= 188,
  PrefixCountry		= 189,		// Country name of prefix, for example DL is Germany
  // 190 unused
  City			= 191,
  StateProvince		= 192,
  PostalCode		= 193,
  BirthDate		= 194,		// yyyymmdd
  DateFirstIssue	= 195,		// yyyymmdd
  ExpirationDate	= 196,		// yyyymmdd
  ProcessDate		= 197,		// date of last change: yyyymmdd
  County		= 198,
  GMTOffset		= 199,		// Hours +/- GMT
  Latitude		= 200,		// degrees.decimal degrees
  Longitude		= 201,		// degrees.decimal degrees
  GridSquare		= 202,
  AreaCode		= 203,
  PreviousCall		= 204,
  PreviousClass		= 205,
  TransactionType	= 206,		// FCC designation
  EmailAddress		= 207,
  QslManager		= 208,
  MailAddressCountry	= 209,		// if different from prefix country
  URL			= 210,		// WWW Home page
  VanityEligibilityFlag	= 211,
  FaxNumber		= 212,
  InterestProfile	= 213,		// pointers to entries in \HAM0\INTEREST
  PhoneNumber		= 214,		// from FCC
  FCCLicenseeID		= 215,
  TenTenNumber		= 216,
  FccFrnNumber		= 217,
  IotaNumber		= 218,
  FistsNumber		= 219,
  QcwaNumber		= 220,
  OotcNumber		= 221,
  FccLicenseKey		= 222,
  // 223-253 unused
  LastEditedBy		= 254
};


int HAMCALL::ReadRec()
{
	Qcall = empty;
	Qlname = empty;
	Qfname = empty;
	Qdob = empty;
	Qefdate = empty;
	Qexpdate = empty;
	Qmail_str = empty;
	Qmail_city = empty;
	Qmail_country = empty;
	Qprefix = empty;
	Qgrid = empty;
	Qlatd = empty;
	Qlond = empty;
	Qmail_st = empty;
	Qmail_zip = empty;
	Qopclass = empty;
	Qp_call = empty;
	Qimagefname = NULL;
	Qp_class = empty;

	if( !found ) {
		LOG_INFO("FOUND NADA");
		return (0);
	}

	int x = 0;

	// Always 1st field, 181 (Callsign) id missing from raw record
	Qcall = recbuffer;

 again:
	while (isascii(recbuffer[x])) {
		if (recbuffer[x] == '\0')	// XXX why does isascii() return true for '\0'?
			goto end;
		++x;
	}

	switch ((uint8_t)recbuffer[x]) {
	case '\0':
		goto end;
	case Class:
		Qopclass = &recbuffer[x+1];
		///LOG_INFO("Class");
		break;
	case FirstName:
		Qfname = &recbuffer[x+1];
		///LOG_INFO("FirstName");
		break;
	case Last:
		Qlname = &recbuffer[x+1];
		///LOG_INFO("LastName");
		break;
	case BirthDate:
		Qdob = &recbuffer[x+1];
		///LOG_INFO("Birthdate");
		break;
	case PrefixCountry:
		Qprefix = &recbuffer[x+1];
		///LOG_INFO("PrefixCountry");
		break;
	case Street:
		Qmail_str = &recbuffer[x+1];
		///LOG_INFO("Street");
		break;
	case City:
		Qmail_city = &recbuffer[x+1];
		///LOG_INFO("City");
		break;
	case StateProvince:
		Qmail_st = &recbuffer[x+1];
		///LOG_INFO("StateProvince");
		break;
	case PostalCode:
		Qmail_zip = &recbuffer[x+1];
		///LOG_INFO("PostalCode");
		break;
	case MailAddressCountry:
		Qmail_country = &recbuffer[x+1];
		///LOG_INFO("MailAddressCountry");
		break;
	case County:
		///Q = &recbuffer[x+1];
		///LOG_INFO("County");
		break;
	case GMTOffset:
		///Q = &recbuffer[x+1];
		///LOG_INFO("GMTOffset");
		break;
	case Latitude:
		Qlatd = &recbuffer[x+1];
		///LOG_INFO("Latitude");
		break;
	case Longitude:
		Qlond = &recbuffer[x+1];
		///LOG_INFO("Longitude");
		break;
	case GridSquare:
		Qgrid = &recbuffer[x+1];
		///LOG_INFO("GridSquare");
		break;
	case BmCode:
	case Middle:
	case Suffix:
	case DateFirstIssue:
	case ExpirationDate:
	case ProcessDate:
	case PreviousCall:
	case PreviousClass:
	case TransactionType:
	case EmailAddress:
	case QslManager:
	case URL:
	case VanityEligibilityFlag:
	case FaxNumber:
	case InterestProfile:
	case AreaCode:
	case PhoneNumber:
	case FCCLicenseeID:
	case TenTenNumber:
	case FccFrnNumber:
	case IotaNumber:
	case FistsNumber:
	case QcwaNumber:
	case OotcNumber:
	case FccLicenseKey:
	case LastEditedBy:
		//LOG_INFO("unused field: %d", recbuffer[x] & 0xff);
		break;
	case Callsign:
		LOG_ERROR("call << unexpected!");
		break;
	default:
		LOG_ERROR("unknown control char: %d", recbuffer[x] & 0xff);
	}
	recbuffer[x++] = '\0';

	goto again;

 end:
	// post processing
	if ((Qfname == empty) && (Qlname != empty))
		Qfname = Qlname;

	return (1);
}

char* HAMCALL::GetCall()
{
	LOG_INFO("GetCall()\n");

	static char call[15];
	char* p = call;
	strcpy (call, Qcall);
	while (*p) {
		if (*p == ' ') strcpy (p, p+1);
		if (*p != ' ') p++;
	}

	return (call);
};

char* HAMCALL::CSV_Record()
{
	static char info[256];
	memset(info, 0, 256);

	snprintf(info, sizeof(info) - 1, "%s,%s,%s,%s,%s,%s,%s,%s,%s",
		 GetCall(), Qopclass, Qefdate,  Qlname, Qfname,
		 Qmail_str, Qmail_city, Qmail_st, Qmail_zip);

	return (info);
}

char* HAMCALL::Fmt_Record()
{
	static char info[256];
	memset(info, 0, 256);

	snprintf(info, sizeof(info) - 1, "%s   %s : %s\n%s %s\n%s\n%s, %s  %s\n",
		GetCall(), Qopclass, Qefdate, Qfname, Qlname,
		Qmail_str, Qmail_city, Qmail_st, Qmail_zip);

	return (info);
}

/*************************************************************************************/
#define	MAXLINE	4096

int HAMCALL::OpenServer(const char* ipaddr, const char* ipport)
{
	//LOG_INFO("_ipaddr: '%s' '%s'", _ipaddr, _ipport);

	strncpy(ipAddress, ipaddr, IP_FIELD_LEN);
	strncpy(ipPort, ipport, IP_FIELD_LEN);

	return CallServer((char*)"000000");
}

int HAMCALL::CallServer(const char* call)
{
	//LOG_INFO("call: %s", call);

	Socket* callsign_tcpip = 0;
	char recvline[MAXLINE + 1];
	size_t n = strlen(call) + 1;

	// Create a socket.
	try {
		callsign_tcpip = new Socket(Address(ipAddress, ipPort));
		callsign_tcpip->set_timeout(0.01);
		callsign_tcpip->connect();
	}
	catch (...) {
		LOG_ERROR(">>> Hamcall DVD server @ ip address %s, port %s is not available.",
			  ipAddress, ipPort);
		return (-1);
	}

	if (callsign_tcpip->send(call, n) != n) {
		LOG_INFO("write error");
		return (-1);
	}

	if ((n = callsign_tcpip->recv(recvline, MAXLINE)) < 0) {
		LOG_INFO("read error");
		return (-1);
	}

	recvline[n] = 0;
	strcpy(recbuffer, recvline);
	//LOG_INFO("'%s', n: %ld", recvline, n);

	return (0);
}
