/*
 * A program to access the Buckmaster hamcall DVD from a unix system.
 *
 * This is done by calling the Buckmaster provided DLL, hamcal32.dll, via a Wine program.
 *
 */

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>

/* No idea if this is correct... */
#if defined(__WINE__)
#include "windef.h"
#include "winbase.h"
#else
#include <windows.h>
#endif /* __WINE__ */

/*
 * Wine's default path to the DB is /ham0
 *
 *  copy HamCallDVD:\ham0\* to /ham0
 *    or
 *  you can use the DVD directly by using the '-p <PATH>' option.
 *
 */
#define DEFAULT_PATH		"z:\\ham0\\"		/* from win32's point of view */
#define BUCKMASTER_DLL		"hamcal32.dll"		/* Buckmaster provided win32 DLL */
#define BUCKMASTER_LOOKUP	"LookupCallSign"	/* access function in the DLL */

#define SERVER_PORT		7770
static int listenPort = SERVER_PORT;

/* Disable for a command line interface. */
#define USE_SOCKET

#if defined(USE_SOCKET)
static void serveDB(void);
static int LookUp(char*, char *);
#else
static void queryDB(void);
#endif /* USE_SOCKET */

static inline int lookup(char*);

/*
 * hamcall/API/WINDOWS/HAMCALL.C implies the DLL uses pascal calling conventions.
 * Not sure if its actually a pascal coded DLL, or just old-school win32 stdcall...
 *  typedef int (_pascal *PDLLFI)(char *, char *, int,  char *);
 *  int _far _pascal (*dllLookup)(char *, char *, int, char *);
 *
 * I tricked the compiler into returning a "suggested" call prototype and got:
 *	int (__attribute__((__stdcall__)) *)(char *, char *, int,  char *);
 */
typedef int (_stdcall *PDLLFI)();	/* pointer to a DLL func returning an int */
static int _stdcall (*dllLookup)();	/* pointer to the DLL lookup function */

static void errorExit(char *);
static int parseArgs(int, char**);
static int fixPath(char *);
static int loadDB(void);
static void cleanupBuffer(char*, int);

#define BUF_LEN	2000
static char lookupBuffer[BUF_LEN+1];

/* XXX -2 never occurs... */
static const char *lookupErrors[] =
{
    "success",
    "callsign not found in database",
    "database not found",
    "index error (shouldn't happen short of a read error)",
    "insufficient memory"
};

static char dbPath[MAX_PATH + 1] = DEFAULT_PATH;
static int verbose = 0;

/*
 * XXX why don't fprintf(stderr, ...) or fgets(... stdin) work?
 */
#define fprintf(stderr, expr ...)	printf(expr)
#define fgets(expr, length, stdin)	gets(expr)
#define fputs(expr, stderr)		puts(expr)

static inline void usage(char* a) { printf("Usage: %s [-p <path>] [-P <port>] [-v] [-h]\n", a); }

int
main(int argc, char* argv[])
{
    /* Parse args. */
    if (parseArgs(argc, argv) < 0) {
        usage(argv[0]);
        errorExit("invalid args\n");
    }

    /* Load the hamcall function from vendor dll */
    if (loadDB() < 0)
        errorExit("database missing\n");

#if defined(USE_SOCKET)
    serveDB();
#else
    queryDB();
#endif /* USE_SOCKET */
}

static void
errorExit(char* reason)
{
    fputs(reason, stderr);
    exit(-1);
}

static int
parseArgs(int argc, char* argv[])
{
    int opt;

    while ((opt = getopt(argc, argv, "hvp:P:")) != -1) {
        switch (opt) {
	case 'v':
	    verbose = 1;
	    break;
 	case 'p':
	    if (fixPath(optarg) < 0)
	        return (-1);
	    break;
	case 'P':
	    listenPort = atoi(optarg);
	    if ((listenPort < 1000) || (listenPort > 65000))
	        return (-1);
	    break;
	case 'h':
	    usage(argv[0]);
	    exit(0);
	default:
	    return (-1);
	}
    }

    if (verbose)
        printf("verbose: %d, path: '%s', port: %d, optind=%d\n",
	       verbose, dbPath, listenPort, optind);

    return (0);
}

/* Make the world look like unix. */
static int
fixPath(char *p)
{
    int x, y, l;

    /* Sanity check. */
    if ((l = strlen(p)) > MAX_PATH)
        errorExit("db path too long.\n");

    /* Convert any '/' to '\' (remember to ESCape backslash constants). */
    for (x = 0; x < l; ++x) {
      if (p[x] == '/') {
	    if (x == 0) {		/* add the win32 drive letter */
	        dbPath[y++] = 'z';
		dbPath[y++] = ':';
	    }
	    dbPath[y++] = '\\';		/* convert '/' to '\' */
	}
	else 
	    dbPath[y++] = p[x];
    }

    /* Make sure path ends with '\'. */
    if (dbPath[y-1] != '\\')
        dbPath[y++] = '\\';

    dbPath[y] = '\0';			/* terminate */

    return (0);
}

static int
loadDB(void)
{
    int result;

    /* Load the DLL. */
    HANDLE h = LoadLibrary(BUCKMASTER_DLL);
    if (h == NULL) {
      fprintf(stderr, "couldn't load DLL: '%s'\n", BUCKMASTER_DLL);
	return (-1);
    }

    /* Link to the search function. */
    dllLookup = (PDLLFI) GetProcAddress(h, BUCKMASTER_LOOKUP);
    if (dllLookup == 0) {
        fprintf(stderr, "couldn't find required function: '%s'\n", BUCKMASTER_LOOKUP);
	return (-1);
    }

    /* Test db access. */
    result = lookup("000000");
    if (result < 0) {
        fprintf(stderr, "couldn't find database: '%s'\n", dbPath  );
	return (-1);
    }

    /* Show the copyright (if VERBOSE). */
    if (verbose) {
        cleanupBuffer(lookupBuffer, result);
        printf("%s\n", lookupBuffer);
    }

    return (0);
}

#if !defined(USE_SOCKET)
#define QUERY_LEN	80
static void
queryDB(void)
{
    char n[QUERY_LEN];
    int result;

    if (verbose)
        printf("queryDB()\n");

    while (1) {
        printf("\n\ncallsign: ");
#pragma message "fgets() doesn't work for unknown reasons..."
	fgets(n, QUERY_LEN, stdin);
        if (strlen(n) > 0) {
	    if (n[0] == 'q')
	        exit(0);

	    if ((result = lookup(n)) < 0) {
	        fprintf(stderr, "lookup ERROR: %s\n", lookupErrors[abs(result)]);
		continue;
	    }

	    /* XXX Very cheap parsing... */
	    cleanupBuffer(lookupBuffer, result);
	    printf("%s\n", lookupBuffer);
	}
    }
}
#endif /* USE_SOCKET */

static void
cleanupBuffer(char *buffer, int length)
{
    int x;

    for (x = 0; x < length; ++x ) {
        if ((buffer[x] < ' ') || (buffer[x] > '~'))
	    buffer[x] = ' ';
    }
}

static void
cleanupBuffer2(char *buffer, int length)
{
    int x, y, n;
    uint8_t buf2[512];

    for (x = y = 0; x < length; ++x ) {
        if ((buffer[x] >= ' ') && (buffer[x] <= '~'))
	    buf2[y++] = buffer[x];
	else {
	    printf("  replacing %u\n", buffer[x] & 0xff);
	    buf2[y++] = '<';
	    n = sprintf(&buf2[y], "%d", buffer[x] & 0xff);
	    y += n;
	    buf2[y++] = '>';
	}
    }
    buf2[y] = '\0';
    printf("clean: '%s'\n", buf2);
}

/*
 * From the HamCall DVD: hamcall/API/WINDOWS/HAMCALL.C:
 *
 * int FAR PASCAL
 * LookupCallSign(char *Path, char *CallSign, int Length, char *Buffer);
 *
 *              Path            -- full path for hamcall database 
 *              CallSign        -- CallSign to look for           
 *              Length          -- Size of the result buffer - 1  
 *              Buffer          -- Buffer to put result in.       
 *
 *
 *
 * example:  result=LookupCallSign("G:\HAM0\", "W1AW", 2000, Buffer)
 *
 * Return values:
 *    > 0 : Success, value is length of output string
 *     -1 : Callsign not found in database
 *     -2 : Database not found
 *     -3 : Index error (shouldn't happen short of a read error)
 *     -4 : Insufficient memory
 */

/*
 * Used for commandline access.
 */
static inline int
lookup(char* name)
{
    return (*dllLookup)(dbPath, name, (short)BUF_LEN, lookupBuffer);
}

/*
 * Used for tcp access.
 */
static int
LookUp(char* name, char *buffer)
{
    int result = (*dllLookup)(dbPath, name, (short)BUF_LEN, buffer);

    if (result < 0)
        sprintf(buffer, "lookup ERROR: %s\n", lookupErrors[abs(result)]);
    else {
        printf("1st char: %d\n", buffer[0] & 0xff);
        cleanupBuffer2(buffer, result);
    }

    return (result);
}

/*************************************************************************/

/*
 * The network code was cribbed from Stevens' unpv12e
 * All hail W. Richard Stevens.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#define	LISTENQ	1024
#define	MAXLINE	4096

#if defined(USE_SOCKET)
void
serveDB(void)
{
    int			cnt;
    char		*end;
    int			listenfd, connfd;
    struct sockaddr_in	servaddr;
    char		inbuff[MAXLINE];
    char		outbuff[MAXLINE];

    if (verbose)
        printf("serveDB()\n");

    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        errorExit("socket error");

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(listenPort);

    if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
        errorExit("bind error");

    if (listen(listenfd, LISTENQ) < 0)
        errorExit("listen error");

    /* Wait for a connection. */
    for (;;) {
        if (verbose)
	    printf("waiting...\n");

	if ((connfd = accept(listenfd, (struct sockaddr *) NULL, NULL)) < 0) {
	    if (errno == EPROTO || errno == ECONNABORTED)
	        continue;
	    else
	        errorExit("accept error");
	}

	if ( (cnt = read(connfd, inbuff, MAXLINE-1)) == -1)	/* grab callsign */
	    errorExit("read error");

	if ((end = strchr(inbuff, '\r')) != NULL)		/* remove trailing \r\n */
	    *end = '\0';

	LookUp(inbuff, outbuff);				/* check the database */
	cnt = strlen(outbuff);

	if (write(connfd, outbuff, cnt) != cnt)			/* return the result */
	    errorExit("write error");

	if (close(connfd) == -1)				/* terminate connection */
	    errorExit("close error");
    }
}
#endif /* USE_SOCKET */

