/*
 * Copyright (c) Des Herriott 1993, 1994
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the copyright holder not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  The copyright holder makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 * Author: Des Herriott
 */

/*
 * Source file resource.c - deals with the Xrm database, and initialises
 * various run-time configuration options.
 */

#include <X11/Xlib.h>
#include <X11/Intrinsic.h>

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/param.h>

#ifdef HAS_UNAME
#include <sys/utsname.h>
#endif

#include "resource.h"
#include "version.h"

#ifndef USRLIBDIR
#define USRLIBDIR "/usr/lib/X11"
#endif

#ifndef LIBDIR
#define LIBDIR "/usr/local/lib/xzx"
#endif

#ifndef MAXPATHLEN
#define MAXPATHLEN 1024
#endif

#define MAX_CHARS 80
#define MAX_DISP_LEN 256

#define ZX_MIN(x,y) (x<y?x:y)
#define NELEM(a) (sizeof(a)/sizeof(a[0]))

/* Xrm parseable resources - requires Xlib.h and Intrinsic.h */
static XrmOptionDescRec opts[] = {
	{ "-help",    ".help",    XrmoptionNoArg, "True" },
	{ "-version", ".version", XrmoptionNoArg, "True" },
	{ "-visual",  ".visual",  XrmoptionSepArg, NULL },
	{ "-romName", ".romName", XrmoptionSepArg, NULL },
	{ "-mitshm",  ".mitshm",  XrmoptionSepArg, NULL },
	{ "-libDir",  ".libDir",  XrmoptionSepArg, NULL },
	{ "-display", ".display", XrmoptionSepArg, NULL },
	{ "-intStyle",".intStyle",XrmoptionSepArg, NULL },
	{ "-flashing",".flashing",XrmoptionSepArg, NULL },
	{ "-rrShm",   ".rrShm",   XrmoptionSepArg, NULL },
	{ "-rrNoshm", ".rrNoshm", XrmoptionSepArg, NULL },
#ifdef SLOWDOWN
	{ "-slowdown",".slowdown",XrmoptionSepArg, NULL },
	{ "-keyFaster",".keyFaster",XrmoptionSepArg, NULL },
	{ "-keySlower",".keySlower",XrmoptionSepArg, NULL },
#endif
#ifdef JOY
	{ "-joystick",".joystick",XrmoptionSepArg, NULL },
	{ "-joyDev",  ".joyDev",  XrmoptionSepArg, NULL },
	{ "-tolerance",".tolerance",XrmoptionSepArg, NULL },
#endif
#if defined(DEV_AUDIO) || defined(PCSPKR_AUDIO)
	{ "-sound",   ".sound",   XrmoptionSepArg, NULL },
#endif
#ifdef DEV_AUDIO
	{ "-audioDev",".audioDev",XrmoptionSepArg, NULL },
#endif
#ifdef ZX_IF1
	{ "-if1",     ".if1",	  XrmoptionSepArg, NULL },
	{ "-if1Rom",  ".if1Rom",  XrmoptionSepArg, NULL },
	{ "-crlf",    ".crlf",    XrmoptionSepArg, NULL },
	{ "-m1",      ".m1",	  XrmoptionSepArg, NULL },
	{ "-m2",      ".m2",	  XrmoptionSepArg, NULL },
	{ "-m3",      ".m3",	  XrmoptionSepArg, NULL },
	{ "-m4",      ".m4",	  XrmoptionSepArg, NULL },
	{ "-m5",      ".m5",	  XrmoptionSepArg, NULL },
	{ "-m6",      ".m6",	  XrmoptionSepArg, NULL },
	{ "-m7",      ".m7",	  XrmoptionSepArg, NULL },
	{ "-m8",      ".m8",	  XrmoptionSepArg, NULL },
#endif
	{ "-private", ".private", XrmoptionIsArg, NULL },
	{ "-mono",    ".mono",    XrmoptionIsArg, NULL },
	{ "-recalibrate", ".recalibrate", XrmoptionSepArg, NULL },
	{ "-kempstUp", ".kempstUp", XrmoptionSepArg, NULL },
	{ "-kempstDown", ".kempstDown", XrmoptionSepArg, NULL },
	{ "-kempstLeft", ".kempstLeft", XrmoptionSepArg, NULL },
	{ "-kempstRight", ".kempstRight", XrmoptionSepArg, NULL },
	{ "-kempstFire", ".kempstFire", XrmoptionSepArg, NULL }
};


/* mappings from keysyms to spectrum actions */
static struct k_map {
	char *res_name;
	char *def_keysym;
} map_tab[NUM_KEYMAPPINGS] = {
	"kempstUp",    "q",
	"kempstDown",  "a",
	"kempstLeft",  "o",
	"kempstRight", "p",
	"kempstFire",  "space",
#ifdef SLOWDOWN
	"keySlower",   "KP_Subtract",
	"keyFaster",   "KP_Add",
#endif
};


/* Try to work out whether or not xzx is on the same machine as
 * the X server - needed to know whether or not to use MIT-SHM.
 * FIXME: this probably won't work on non-Unix systems.
 */
static int
is_local_server(char *dispName)
{
#ifdef HAS_UNAME
	struct utsname un;
#else
	char sysname[MAX_LEN];
#endif

	if (dispName[0] == ':')
		return(1);
	if (strncmp(dispName, "unix", 4) == 0)
		return(1);
	if (strncmp(dispName, "localhost", 9) == 0)
		return(1);

	/* Try matching the display name to the machine name */
#ifdef HAS_UNAME
	uname(&un);
	if (strncmp(dispName, un.sysname, strlen(un.sysname)) == 0)
		return(1);
	if (strncmp(dispName, un.nodename, strlen(un.nodename)) == 0)
		return(1);
#else	/* use gethostname() instead */
	gethostname(sysname, MAX_LEN);
	if (strncmp(dispName, sysname, strlen(sysname)) == 0)
		return(1);
#endif

	return(0);
}


static void
usage(void)
{
	printf("Xzx is copyright (c) 1993,1994 Des Herriott\n");
	printf("Xzx comes with no warranty; see the file COPYRIGHT for details\n");

/* The NEC EWS C compiler claims it's ANSI, but doesn't allow string 
 * literal concatenation.  Pretty braindead and useless, if you ask me.
 */
#ifndef nec_ews
	printf("\nUsage: xzx [options] [snapshot-file]\n"
		"Available options:\n"
		"   -help\t\tdisplay this message\n"
		"   -version\t\tdisplay the current version\n"
		"   -visual <visual>\tuse the specified X visual\n"
		"   -romName <filename>\tuse the specified file as a ROM image\n"
		"   -libDir <path>\tspecify library directories\n"
		"   -mitshm <bool>\ttry to use MIT-SHM [true]\n"
		"   -recalibrate <bool>\ttry to keep interrupts at 50/sec [true]\n"
		"   -flashing <bool>\tenable flashing attributes [true]\n"
		"   -rrShm <n>\t\trefresh screen every <n> interrupts (MITSHM) [2]\n"
		"   -rrNoshm <n>\t\trefresh screen every <n> interrupts (no MITSHM) [4]\n"
		"   -mono\t\tuse only black and white\n"
		"   -private\t\tforce a private colormap\n"
#ifdef SLOWDOWN
		"   -slowdown <int>\t\tspecify slowdown factor\n"
		"   -keySlower <keysym>\tslow down the emulator\n"
		"   -keyFaster <keysym>\tspeed up the emulator\n"
#endif
#ifdef JOY
		"   -joystick <bool>\tenable/disable Kempston joystick [false]\n"
		"   -joyDev <path>\tspecify joystick device [/dev/js0]\n"
		"   -tolerance <int>\tspecify joystick displacement tolerance [20]\n"
#endif
#if defined(DEV_AUDIO) || defined(PCSPKR_AUDIO)
		"   -sound <bool>\tenable/disable sound [false]\n"
#endif
#ifdef DEV_AUDIO
		"   -audioDev <path>\tspecify the audio device [/dev/audio]\n"
#endif
#ifdef ZX_IF1
		"   -if1 <bool>\t\tenable Interface 1 [true]\n"
		"   -if1Rom <path>\tspecify Interface 1 ROM image\n"
		"   -crlf <bool>\t\tenable CR/LF translation on RS232 I/O [false]\n"
		"   -m<n> <path>\t\tput Microdrive cartridge file in drive <n>\n"
#endif
		"   -intStyle [signal|counter]\n\t\t\t"
		"specify how interrupts are implemented [signal]\n"
	    "   -kempstUp <keysym>\tKempston Joystick up key [q]\n"
	    "   -kempstDown <keysym>\tKempston Joystick down key [a]\n"
	    "   -kempstLeft <keysym>\tKempston Joystick left key [o]\n"
	    "   -kempstRight <keysym>Kempston Joystick right key [p]\n"
	    "   -kempstFire <keysym>\tKempston Joystick fire key [space]\n");
#ifdef ZX_IF1
	printf("\nMicrodrive emulation reads and writes .MDR files.");
#endif
	printf("\nxzx reads and writes .SNA format snapshots and "
		   "reads .Z80 format snapshots.\n");
#else /* nec_ews */
	printf("Sorry, no help available until NEC fix their C compiler.\n");
#endif

	exit(0);
}


static void
show_version(void)
{
	printf("This is xzx v%d.%d.%d\n", ZX_Major, ZX_Minor, ZX_Patch);
#ifndef nec_ews
	printf("Compile-time options:\n"
#ifdef MITSHM
		   " *  MIT-SHM image transfer is possible.\n"
#endif
		   " *  xzx is built for a"
#ifdef LITTLE_ENDIAN
		   " little-endian architecture.\n"
#else
		   " big-endian architecture.\n"
#endif
#ifdef SPEC16K
		   " *  xzx is emulating a 16k spectrum.\n"
#endif
#ifdef JOY
		   " *  Linux analogue joystick emulation is possible.\n"
#endif
#ifdef DEV_AUDIO
		   " *  Sun Sparc audio emulation is possible.\n"
#endif
#ifdef PCSPKR_AUDIO
		   " *  Linux PC Speaker style audio emulation is possible.\n"
#endif
#ifdef LOAD_SAVE_1
		   " *  Russell Marks' LOAD/SAVE emulation is enabled.\n"
#endif
#ifdef LOAD_SAVE_2
		   " *  Ian Collier's LOAD/SAVE emulation is enabled.\n"
#endif
#ifdef PSEUDO_IO
		   " *  Shell input/output is emulated via ports.\n"
#endif
#ifdef SLOWDOWN
           " *  Speed control is enabled\n"
#endif
#ifdef ZX_IF1
           " *  Interface 1, microdrive & RS232 emulation are enabled.\n"
#endif
		   " *  X11 pixel to Spectrum pixel scaling factor is %d:1.\n",
	SCALING);
#endif /* nec_ews */
	exit(0);
}


static void
list_visuals(void)
{
	fprintf(stderr, "Currently only the default visual is supported\n");
}


static int
is_true(char *str)
{
	return (strncasecmp(str, "true", 4) == 0
		|| strncasecmp(str, "on", 2) == 0
		|| strncasecmp(str, "yes", 3) == 0);
}


static int
get_resource(XrmDatabase db, char *myName, char *myClass,
             char *resource, char *fallback, char *result,
             unsigned size)
{
	int i, len;
	char str_name[80], str_class[80], *str_type[10];
	XrmValue rmValue;

	sprintf(str_name, "%s.%s", myName, resource);
	sprintf(str_class, "%s.%c%s", myClass,
		isupper(*resource) ? toupper(*resource) : *resource, resource + 1);

    if (XrmGetResource(db, str_name, str_class, str_type, &rmValue) == True) {
		if (rmValue.addr == NULL) {
			len = 0;
		} else {
			len = ZX_MIN(rmValue.size, size - 1);
			strncpy(result, rmValue.addr, len);
		}
		result[len] = '\0';
		for (i = 0; i < NELEM(opts); i++) {
			if (opts[i].argKind == XrmoptionIsArg
			  && (strcmp(result, opts[i].option) == 0
			  || strcmp(result, opts[i].specifier) == 0)) {
				strncpy(result, "true", size);
				result[size - 1] = '\0';
				break;
			}
		}
		return 1;
    }
	if (fallback != NULL) {
		strncpy(result, fallback, size - 1);
		result[size - 1] = '\0';
	} else {
		result[0] = '\0';
	}
	return 0;
}


static int
get_string_resource(XrmDatabase db, char *myName, char *myClass,
			       char *resource, char *fallback, char *result,
			       unsigned size)
{
    char *src, *dst;
    int val;

	val = get_resource(db, myName, myClass, resource, fallback, result, size);
    src = dst = result;
	while ((*src & 0x7f) == *src && isgraph(*src) == 0 && *src != '\0') {
		src++;
	}
	while ((*src & 0x7f) != *src || isgraph(*src) != 0) {
		*dst++ = *src++;
	}
	*dst = '\0';

	return val;
}


static void
get_bool_resource(XrmDatabase db, char *myName, char *myClass,
			      char *resource, char *fallback, int *result)
{
    char resValue[MAX_CHARS];

	get_resource(db, myName, myClass, resource, fallback, resValue,
		sizeof resValue);
	*result = (is_true(resValue) != 0);
}


static void
get_int_resource(XrmDatabase db, char *myName, char *myClass,
			     char *resource, int fallback, int *result)
{
    char resValue[MAX_CHARS];

    if (get_resource(db, myName, myClass, resource, NULL, resValue,
		     sizeof resValue) == 0 || sscanf(resValue, "%d", result) <= 0) {
		*result = fallback;
	}
}


static void
get_file_defaults(Display *dpy, XrmDatabase *rDBptr,
                  char *myName, char *myClass)
{
    int len;
    char *ptr;
	char path[MAXPATHLEN];
    XrmDatabase		tmpDB;

	char *lang = getenv("LANG"), *home = getenv("HOME");

    sprintf(path, "%s%s", LIBDIR, myClass);
    *rDBptr = XrmGetFileDatabase(path);

#ifdef VMS
	tmpDB = XrmGetFileDatabase("SYS$LOGIN:decw$xdefaults.dat");
	XrmMergeDatabases(tmpDB, rDBptr);
	tmpDB = XrmGetFileDatabase("DECW$USER_DEFAULTS:xzx.dat");
	XrmMergeDatabases(tmpDB, rDBptr);
#else
    if (lang) {
		sprintf(path, "%s/%s/app-defaults/%s", USRLIBDIR, lang, myClass);
		if (access(path, 0) == -1) {
			sprintf(path, "%s/%s", USRLIBDIR, myClass);
		}
	} else {
		sprintf(path, "%s/%s", USRLIBDIR, myClass);
	}
	tmpDB = XrmGetFileDatabase(path);
	XrmMergeDatabases(tmpDB, rDBptr);

	if ((ptr = getenv("XUSERFILESEARCHPATH")) != NULL) {
		sprintf(path, "%s/%s", ptr, myClass);
		tmpDB = XrmGetFileDatabase(path);
		XrmMergeDatabases(tmpDB, rDBptr);
	}
    else if ((ptr = getenv("XAPPLRESDIR")) != NULL) {
		if (lang) {
			sprintf(path, "%s/%s/%s", ptr, lang, myClass);
			if (access(path, 0) == -1) {
				sprintf(path, "%s/%s", ptr, myClass);
			}
		} else {
			sprintf(path, "%s/%s", ptr, myClass);
		}
		tmpDB = XrmGetFileDatabase(path);
		XrmMergeDatabases(tmpDB, rDBptr);
    }
    else if (home) {
		if (lang) {
			sprintf(path, "%s/app-defaults/%s/%s", home, lang, myClass);
			if (access(path, 0) == -1) {
				sprintf(path, "%s/app-defaults/%s", home, myClass);
			}
		} else {
			sprintf(path, "%s/app-defaults/%s", home, myClass);
		}
		tmpDB = XrmGetFileDatabase(path);
		XrmMergeDatabases(tmpDB, rDBptr);
	}

	if ((ptr = XResourceManagerString(dpy)) != NULL) {
		tmpDB = XrmGetStringDatabase(ptr);
		XrmMergeDatabases(tmpDB, rDBptr);
	} else if (home) {
		sprintf(path, "%s/.Xdefaults", home);
		tmpDB = XrmGetFileDatabase(path);
		XrmMergeDatabases(tmpDB, rDBptr);
	}

	if ((ptr = getenv("XENVIRONMENT")) != NULL) {
		tmpDB = XrmGetFileDatabase(ptr);
		XrmMergeDatabases(tmpDB, rDBptr);
	} else if (home) {
		sprintf(path, "%s/.Xdefaults-", home);
		len = strlen(path);
		gethostname(&path[len], sizeof path - len);
		path[sizeof path - 1] = '\0';
		tmpDB = XrmGetFileDatabase(path);
		XrmMergeDatabases(tmpDB, rDBptr);
	}
#endif
}


Display *
open_display(int *argcp, char **argvp, struct config *cb, KeySym *ks_list)
{
	int	 i;
	char *ptr;
	char *myName = "xzx", *myClass = "Xzx";
	char resValue[MAX_CHARS];
	char dispName[MAX_DISP_LEN];
	XrmDatabase	 argDB, rDB;
	Display *dpy;

    XrmInitialize();

    argDB = 0;
    XrmParseCommand(&argDB, opts, NELEM(opts), myName, argcp, argvp);

    /* check for bad arguments */
    for (i = 1; i < *argcp; i++) {
		if (argvp[i][0] == '-') {
			fprintf(stderr, "Unknown option '%s'\n", argvp[i]);
			fprintf(stderr, "Type: %s -help to see a list of options\n",
				argvp[0]);
			exit(1);
		}
	}

	if (get_resource(argDB, myName, myClass, "help", NULL, resValue,
			sizeof resValue) != 0) {
		usage();
	}

	if (get_resource(argDB, myName, myClass, "version", NULL, resValue,
			sizeof resValue) != 0) {
		show_version();
	}

	if (get_string_resource(argDB, myName, myClass, "display", NULL, dispName,
			MAX_DISP_LEN) == 0 || dispName[0] == '\0') {
#ifdef VMS
		if ((ptr = getenv("DECW$DISPLAY")) != NULL) {
#else
		if ((ptr = getenv("DISPLAY")) != NULL) {
#endif
			strncpy(dispName, ptr, MAX_DISP_LEN);
			dispName[MAX_DISP_LEN - 1] = '\0';
		} else {
#ifdef VMS
			strcpy(dispName, "::0.0");
			sprintf(dispName, "%s::0.0", host);
#else
			strcpy(dispName, ":0.0");
#endif
		}
	}

	if ((dpy = XOpenDisplay(dispName)) == NULL) {
		fprintf(stderr, "xzx: can't open display '%s'\n", dispName);
		return(NULL);
	}

	get_resource(argDB, myName, myClass, "visual", "",
		 cb->visual, sizeof cb->visual);
	if (strncasecmp(cb->visual, "supported", 4) == 0) {
		list_visuals();
		exit(0);
	}

    get_file_defaults(dpy, &rDB, myName, myClass);

    XrmMergeDatabases(argDB, &rDB);


	/* now fill in the blanks in our configuration block */
	get_bool_resource(rDB, myName, myClass, "mitshm", "true", &cb->try_mitshm);
	get_bool_resource(rDB, myName, myClass, "recalibrate", "true", &cb->recalibrate);
	get_bool_resource(rDB, myName, myClass, "mono", "false", &cb->monochrome);
	get_bool_resource(rDB, myName, myClass, "private", "false", &cb->private);
	get_bool_resource(rDB, myName, myClass, "flashing", "true", &cb->flashing);
	get_string_resource(rDB, myName, myClass, "romName", "spectrum.rom",
		cb->rom_name, sizeof(cb->rom_name));

	ptr = (char *)malloc(strlen(LIBDIR) + 3);
	sprintf(ptr, ".:%s", LIBDIR);
	get_string_resource(rDB, myName, myClass, "libDir", ptr,
		cb->lib_dir, sizeof(cb->lib_dir));
	free(ptr);

	get_string_resource(rDB, myName, myClass, "intStyle", "signal",
		resValue, sizeof(resValue));

	get_int_resource(rDB, myName, myClass, "rrShm", 2, &cb->rr_shm);
	get_int_resource(rDB, myName, myClass, "rrNoshm", 6, &cb->rr_noshm);

#ifdef SLOWDOWN
	get_int_resource(rDB, myName, myClass, "slowdown", 0, &cb->slowdown);
#endif

#ifdef JOY
	get_bool_resource(rDB, myName, myClass, "joystick", "false", &cb->use_js);
	get_int_resource(rDB, myName, myClass, "tolerance", 20, &cb->js_tolerance);
	get_string_resource(rDB, myName, myClass, "joyDev", "/dev/js0",
		cb->js_device, sizeof(cb->js_device));
#endif

#if defined(DEV_AUDIO) || defined(PCSPKR_AUDIO)
	get_bool_resource(rDB, myName, myClass, "sound", "false", &cb->use_sound);
#endif

#ifdef DEV_AUDIO
	get_string_resource(rDB, myName, myClass, "audioDev", "/dev/audio",
		cb->audio_dev, sizeof(cb->audio_dev));
#endif
#ifdef ZX_IF1
	get_bool_resource(rDB, myName, myClass, "if1", "true", &cb->if1_active);
	get_string_resource(rDB, myName, myClass, "if1Rom", "if1.rom",
		cb->if1_rom, sizeof(cb->if1_rom));
	get_bool_resource(rDB, myName, myClass, "crlf", "false", &cb->translate_nl);

	/* not elegant - but it works ! */

	get_string_resource(rDB, myName, myClass, "m1", "",
		cb->cartfiles[0], sizeof(cb->cartfiles[0]));
	get_string_resource(rDB, myName, myClass, "m2", "",
		cb->cartfiles[1], sizeof(cb->cartfiles[1]));
	get_string_resource(rDB, myName, myClass, "m3", "",
		cb->cartfiles[2], sizeof(cb->cartfiles[2]));
	get_string_resource(rDB, myName, myClass, "m4", "",
		cb->cartfiles[3], sizeof(cb->cartfiles[3]));
	get_string_resource(rDB, myName, myClass, "m5", "",
		cb->cartfiles[4], sizeof(cb->cartfiles[4]));
	get_string_resource(rDB, myName, myClass, "m6", "",
		cb->cartfiles[5], sizeof(cb->cartfiles[5]));
	get_string_resource(rDB, myName, myClass, "m7", "",
		cb->cartfiles[6], sizeof(cb->cartfiles[6]));
	get_string_resource(rDB, myName, myClass, "m8", "",
		cb->cartfiles[7], sizeof(cb->cartfiles[7]));
#endif
	
	/* Initialise the X keysym -> Spectrum action translations */
	for (i = 0; i < NUM_KEYMAPPINGS; i++) {
		get_string_resource(rDB, myName, myClass, map_tab[i].res_name,
			map_tab[i].def_keysym, resValue, sizeof(resValue));
		ks_list[i] = XStringToKeysym(resValue);
		if (ks_list[i] == NoSymbol) {
			fprintf(stderr, "xzx: unknown keysym <%s> found, using <%s>\n",
				resValue, map_tab[i].def_keysym);
			ks_list[i] = XStringToKeysym(map_tab[i].def_keysym);
		}
	}

	if (strcasecmp(resValue, "signal") == 0)
		cb->icnt = 0;
	else
		cb->icnt = 1;

	/* avoid getting a BadAccess error if the remote server has MIT-SHM */
	if (!is_local_server(dispName))
		cb->try_mitshm = 0;

	return(dpy);
}
