/* Win32-specific code for Xconq kernel.
   Copyright (C) 1999 Stanley T. Shebs.

Xconq is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.  See the file COPYING.  */

/* Unix interface stuff.  Do NOT attempt to use this file in a non-Unix
   system, not even an ANSI one! */

/* Also note that this file does not include all Xconq .h files, since
   it may be used with auxiliary programs. */

#include "config.h"
#include "misc.h"
#include "dir.h"
#include "lisp.h"
#include "module.h"
#include "system.h"

extern int numremotes;
extern int my_rid;
extern int master_rid;
extern int tmprid;

extern void close_displays(void);

#include <signal.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/time.h>
#include <errno.h>
#include <fcntl.h>

#include <sys/file.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/ioctl.h>

/* Default names for places. */

/* (should change default passed in by compiler instead) */
#undef XCONQDATA

#ifndef XCONQDATA
#define XCONQDATA ".."
#endif

#ifndef XCONQLIB
#define XCONQLIB "lib"
#endif

#ifndef XCONQIMAGES
#define XCONQIMAGES "images"
#endif

#ifndef XCONQSCORES
#define XCONQSCORES "scores"
#endif

#ifndef PREFERENCESFILE
#define PREFERENCESFILE "prefs.xconq"
#endif

static char *game_homedir(void);
static char *game_filename(char *namevar, char *defaultname);
static char *score_file_pathname(char *name);

uid_t games_uid;

char *
default_library_pathname(void)
{
    char *name;

    name = xmalloc(strlen(XCONQDATA) + 1 + strlen(XCONQLIB) + 1);
    strcpy(name, XCONQDATA);
    strcat(name, "\\");
    strcat(name, XCONQLIB);
    return name;
}

char *
default_images_pathname(void)
{
    char *name;

    name = xmalloc(strlen(XCONQDATA) + 1 + strlen(XCONQIMAGES) + 1);
    strcpy(name, XCONQDATA);
    strcat(name, "\\");
    strcat(name, XCONQIMAGES);
    return name;
}

char *
news_filename(void)
{
    /* (should search in library list) */
    make_pathname(xconq_libs->path, NEWSFILE, "", spbuf);
    return spbuf;
}

static char *savegamename;

char *
saved_game_filename(void)
{
    if (savegamename == NULL)
      savegamename = game_filename("XCONQSAVEFILE", SAVEFILE);
    return savegamename;
}

static char *checkgamename;

char *
checkpoint_filename(void)
{
    if (checkgamename == NULL)
      checkgamename = game_filename("XCONQCHECKPOINTFILE", CHECKPOINTFILE);
    return checkgamename;
}

static char *preferences_name;

char *
preferences_filename(void)
{
    if (preferences_name == NULL)
      preferences_name = game_filename("XCONQPREFERENCES", PREFERENCESFILE);
    return preferences_name;
}

char *
error_save_filename(void)
{
    /* No need to cache name, will only get requested once. */
    return game_filename("XCONQERRORFILE", ERRORFILE);
}

char *
statistics_filename(void)
{
    /* No need to cache name, will only get requested once. */
    return game_filename("XCONQSTATSFILE", STATSFILE);
}

static char *homedir;

static char *
game_homedir(void)
{
    char *str;
    struct passwd *pwd;

    if (homedir != NULL)
      return homedir;
    if ((str = getenv("XCONQHOME")) != NULL) {
	homedir = copy_string(str);
    } else if ((str = getenv("HOME")) != NULL) {
	homedir = xmalloc(strlen(str) + 20);
	strcpy(homedir, str);
	strcat(homedir, "/.xconq");
    } else if ((pwd = getpwuid(getuid())) != NULL) {
	homedir = xmalloc(strlen(pwd->pw_dir) + 20);
	strcpy(homedir, pwd->pw_dir);
	strcat(homedir, "/.xconq");
    } else {
	homedir = ".";
    }
    /* Try to ensure that the directory exists. */
    if (access(homedir, F_OK) != 0) {
	mkdir(homedir, 0755);
	/* (should warn of problems) */
    }
    return homedir;
}

static char *
game_filename(char *namevar, char *defaultname)
{
    char *str, *home;

    if ((str = getenv(namevar)) != NULL && *str)
      return copy_string(str);
    home = game_homedir();
    str = xmalloc(strlen(home) + 1 + strlen(defaultname) + 1);
    strcpy(str, home);
    strcat(str, "\\");
    strcat(str, defaultname);
    return str;
}

/* This wrapper replaces fopen everywhere in the kernel. On the mac
   side it does unix-to-mac linefeed conversion if necessary. Here it
   does absolutely nothing. */

FILE *
open_file(char *filename, char *mode)
{
	return fopen(filename, mode);
}

/* Attempt to open a library file. */

FILE *
open_module_library_file(Module *module)
{
    LibraryPath *p;
    FILE *fp;

    /* Don't try to do on anon modules? */
    if (module->name == NULL)
      return NULL;
    for_all_library_paths(p) {
	/* Generate library pathname. */
	make_pathname(p->path, module->name, "g", spbuf);
	/* Now try to open the file. */
	fp = open_file(spbuf, "r");
	if (fp != NULL) {
	    /* Remember the filename where we found it. */
	    module->filename = copy_string(spbuf);
	    return fp;
	}
    }
    return NULL;
}

FILE *
open_module_explicit_file(Module *module)
{
    if (module->filename == NULL)
      return NULL;
    return (open_file(module->filename, "r"));
}

FILE *
open_library_file(char *filename)
{
    char fullnamebuf[1024];
    LibraryPath *p;
    FILE *fp = NULL;

    fp = open_file(filename, "r");
    if (fp != NULL) {
	return fp;
    }
    for_all_library_paths(p) {
	/* Generate library pathname. */
	make_pathname(p->path, filename, NULL, fullnamebuf);
	fp = open_file(fullnamebuf, "r");
	if (fp != NULL) {
	    return fp;
	}
    }
    return NULL;
}

FILE *
open_scorefile_for_reading(char *name)
{
    FILE *fp;

    fp = open_file(score_file_pathname(name), "r");
    return fp;
}

FILE *
open_scorefile_for_writing(char *name)
{
    FILE *fp;

    /* The scorefile is only writable by the owner of the Xconq
       executable, but we normally run as the user, so switch over
       before writing. */
    setuid(games_uid);
    fp = open_file(score_file_pathname(name), "a");
    return fp;
}

void
close_scorefile_for_writing(FILE *fp)
{
    fclose(fp);
    /* Reset the uid back to the user who started the game. */
    setuid(getuid());
}

static char *scorenamebuf;

static char *
score_file_pathname(char *name)
{
    char *scorepath, extrabuf[BUFSIZE];

    /* (Note that this wires in the name on the first call, so cannot
       be called with different names.  We could make this smarter, but
       no point to it right now.) */
    if (scorenamebuf == NULL) {
	scorepath = getenv("XCONQSCORES");
	if (empty_string(scorepath)) {
	    strcpy(extrabuf, XCONQDATA);
	    strcat(extrabuf, "/");
	    strcat(extrabuf, XCONQSCORES);
	    scorepath = extrabuf;
	}
	scorenamebuf = xmalloc(strlen(scorepath) + 1 + strlen(name) + 10);
	make_pathname(scorepath, name, NULL, scorenamebuf);
    }
    return scorenamebuf;
}

void
make_pathname(char *path, char *name, char *extn, char *pathbuf)
{
    strcpy(pathbuf, "");
    if (!empty_string(path)) {
	strcat(pathbuf, path);
	strcat(pathbuf, "/");
    }
    strcat(pathbuf, name);
    /* Don't add a second identical extension, but do add if extension
       is different (in case we want "foo.12" -> "foo.12.g" for instance) */
    if (strrchr(name, '.')
	&& extn
	&& strcmp((char *) strrchr(name, '.') + 1, extn) == 0)
      return;
    if (!empty_string(extn)) {
	strcat(pathbuf, ".");
	strcat(pathbuf, extn);
    }
}

/* Remove a saved game from the system. */

void
remove_saved_game(void)
{
    unlink(saved_game_filename());
}

/* Default behavior on explicit kill. */

void
stop_handler(int sig, int code, void *scp, char *addr)
{
    close_displays();
    exit(1);
}

/* This routine attempts to save the state before dying. */

void
crash_handler(int sig, int code, void *scp, char *addr)
{
    static int already_been_here = FALSE;

    if (!already_been_here) {
	already_been_here = TRUE;
	close_displays();  
	printf("Fatal error encountered. Signal %d code %d\n", sig, code);
	write_entire_game_state(ERRORFILE);
    }
    abort();
}

/* Accidental disconnection saves state. */

void
hup_handler(int sig, int code, struct sigcontext *scp, char *addr)
{
    static int already_been_here = FALSE;

    if (!already_been_here) {
	already_been_here = TRUE;
	close_displays();
	printf("Somebody was disconnected, saving the game.\n");
	write_entire_game_state(ERRORFILE);
    }
    abort();
}

void
init_signal_handlers(void)
{
    signal(SIGINT, stop_handler);
    if (0 /* don't accidently quit */ && !Debug) {
	signal(SIGINT, SIG_IGN);
    } else {
	signal(SIGINT, SIG_DFL);
/*	signal(SIGINT, crash_handler);  */
    }
    signal(SIGHUP, hup_handler);
    signal(SIGSEGV, crash_handler);
    signal(SIGFPE, crash_handler);
    signal(SIGILL, crash_handler);
    signal(SIGINT, crash_handler);
    signal(SIGQUIT, crash_handler);
    signal(SIGTERM, crash_handler);
    /* The following signals may not be available everywhere. */
#ifdef SIGBUS
    signal(SIGBUS, crash_handler);
#endif /* SIGBUS */
#ifdef SIGSYS
    signal(SIGSYS, crash_handler);
#endif /* SIGSYS */
}

struct timeval reallasttime = { 0, 0 };

struct timeval realcurtime;

int
n_seconds_elapsed(int n)
{
    gettimeofday(&realcurtime, NULL);
    if (realcurtime.tv_sec > (reallasttime.tv_sec + (n - 1))) {
	reallasttime = realcurtime;
	return TRUE;
    } else {
	return FALSE;
    }
}

/* Returns true if n milliseconds have passed since the time was recorded
   via record_ms(). */

struct timeval reallastmstime = { 0, 0 };

int
n_ms_elapsed(int n)
{
    int interval;
    struct timeval tmprealtime;

    gettimeofday(&tmprealtime, NULL);
    interval =
      (tmprealtime.tv_sec - reallastmstime.tv_sec) * 1000
	+ (tmprealtime.tv_usec - reallastmstime.tv_usec) / 1000;
    return (interval > n);
}

/* Record the current time of day. */

void
record_ms(void)
{
    gettimeofday(&reallastmstime, NULL);
}

/* Low-level networking support for Unix. */

int connection_method;

int hosting;

int remote_desc = -1;

void
open_remote_connection(char *methodname)
{
#if 0
    struct sgttyb sg;
#endif
    char *port_str;
    int port;
    struct hostent *hostent;
    struct sockaddr_in sockaddr;
    int tmp;
    char hostname[100];
    struct protoent *protoent;
    int i;
    int tmp_desc;

    if (!strchr(methodname, ':')) {
	remote_desc = open(methodname, O_RDWR);
	if (remote_desc < 0)
	  init_warning("Could not open remote device");

#if 0
	ioctl(remote_desc, TIOCGETP, &sg);
	sg.sg_flags = RAW;
	ioctl(remote_desc, TIOCSETP, &sg);
#endif
    } else {
	/* Extract the port number. */
	port_str = strchr(methodname, ':');
	port = atoi(port_str + 1);

	tmp_desc = socket(PF_INET, SOCK_STREAM, 0);
	if (tmp_desc < 0)
	  init_warning("Can't open socket");

	/* Allow rapid reuse of this port. */
	tmp = 1;
	setsockopt (tmp_desc, SOL_SOCKET, SO_REUSEADDR, (char *) &tmp,
		    sizeof(tmp));

	if (hosting) {
	    sockaddr.sin_family = PF_INET;
	    sockaddr.sin_port = htons(port);
	    sockaddr.sin_addr.s_addr = INADDR_ANY;

	    if (bind(tmp_desc, (struct sockaddr *) &sockaddr, sizeof(sockaddr))
		|| listen (tmp_desc, 1))
	      init_warning("Can't bind address");

	    tmp = sizeof (sockaddr);
	    remote_desc = accept(tmp_desc, (struct sockaddr *) &sockaddr, &tmp);
	    if (remote_desc == -1)
	      init_warning("Accept failed");

	    protoent = getprotobyname("tcp");
	    if (!protoent)
	      init_warning("getprotobyname");

	    /* Enable TCP keep alive process. */
	    tmp = 1;
	    setsockopt(tmp_desc, SOL_SOCKET, SO_KEEPALIVE, (char *) &tmp, sizeof(tmp));

	    /* Tell TCP not to delay small packets.  This greatly speeds up
	       interactive response. */
	    tmp = 1;
	    setsockopt(remote_desc, protoent->p_proto, TCP_NODELAY,
		       (char *) &tmp, sizeof(tmp));

	    /* No longer need the descriptor. */
	    close(tmp_desc);

	    /* If we don't do this, then program simply exits when
	       the remote side dies.  */
	    signal(SIGPIPE, SIG_IGN);
#if defined(FASYNC)
	    fcntl(remote_desc, F_SETFL, FASYNC);
#endif
	    fprintf (stderr, "Remote connection using %s\n", methodname);

	    my_rid = 1;
	} else {
	    tmp = min (port_str - methodname, (int) sizeof hostname - 1);
	    strncpy (hostname, methodname, tmp);
	    hostname[tmp] = '\0';
	    hostent = gethostbyname (hostname);
	    if (!hostent) {
		init_warning("%s: unknown host\n", hostname);
		return;
	    }
	    for (i = 1; i <= 15; i++) {
		remote_desc = socket(PF_INET, SOCK_STREAM, 0);
		if (remote_desc < 0) {
		    init_warning("can't create a socket");
		    return;
		}

		/* Allow rapid reuse of this port. */
		tmp = 1;
		setsockopt(remote_desc, SOL_SOCKET, SO_REUSEADDR, (char *) &tmp, sizeof(tmp));

		/* Enable TCP keep alive process. */
		tmp = 1;
		setsockopt(remote_desc, SOL_SOCKET, SO_KEEPALIVE, (char *) &tmp, sizeof(tmp));

		sockaddr.sin_family = PF_INET;
		sockaddr.sin_port = htons(port);
		memcpy(&sockaddr.sin_addr.s_addr, hostent->h_addr,
		       sizeof (struct in_addr));

		if (connect(remote_desc, (struct sockaddr *) &sockaddr, sizeof(sockaddr)) == 0)
		  break;

		close(remote_desc);
		remote_desc = -1;

		/* We retry for ECONNREFUSED because that is often a
		   temporary condition, which happens when the server
		   is being restarted.  */
		if (errno != ECONNREFUSED)
		  return;
		sleep(1);
	    }

	    protoent = getprotobyname ("tcp");
	    if (!protoent)
	      return;

	    tmp = 1;
	    if (setsockopt(remote_desc, protoent->p_proto, TCP_NODELAY,
			   (char *) &tmp, sizeof(tmp)))
	      return;

	    signal(SIGPIPE, SIG_IGN);

	    my_rid = 2;
	}
	numremotes = 2;
	connection_method = 2;
    }
}

void
low_send(int id, char *buf)
{
    int len, cc;

    len = strlen(buf);
    while (len > 0) {
	cc = write(remote_desc, buf, len);

	if (cc < 0)
	  return;
	len -= cc;
	buf += cc;
    }
}

static int
wait_for(int timeout)
{
  int numfds;
  struct timeval tv;
  fd_set readfds, exceptfds;

  FD_ZERO (&readfds);
  FD_ZERO (&exceptfds);

  tv.tv_sec = timeout;
  tv.tv_usec = 0;

  FD_SET(remote_desc, &readfds);
  FD_SET(remote_desc, &exceptfds);

  while (1)
    {
      if (timeout >= 0)
	numfds = select(remote_desc+1, &readfds, 0, &exceptfds, &tv);
      else
	numfds = select(remote_desc+1, &readfds, 0, &exceptfds, 0);

      if (numfds <= 0)
	if (numfds == 0)
	  return -1;
	else if (errno == EINTR)
	  continue;
	else
	  return -2;	/* Got an error from select or poll */

      return 0;
    }
}

int
low_receive(int *idp, char *buf, int maxchars, int timeout)
{
    int status, n, n2;
    /* time_t */ unsigned long start_time, now;

    time(&start_time);

    while (1) {
	status = wait_for(timeout);
	if (status < 0)
	  return FALSE;
	n = read(remote_desc, buf, maxchars);
	if (n > 0) {
	    buf[n] = '\0';
	    *idp = (my_rid == 1 ? 2 : 1);
	    return TRUE;
	} else if (timeout == 0) {
	    return FALSE;
	} else if (timeout == -1) {
	    /* Go around again. */
	} else {
	    time(&now);
	    if (now > start_time + timeout) {
		Dprintf("%ul > %ul + %d\n", now, start_time, timeout);
		return FALSE /* timed out */;
	    }
	}
    }
}

void
close_remote_connection(void)
{
    if (remote_desc < 0)
      return;

    close(remote_desc);
    remote_desc = -1;
}
