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

			       XCopilot

This code is part of XCopilot, a port of copilot

		  Copyright (C) 1997 Ivan A. Curtis
		       icurtis@radlogic.com.au

The original MS-Windows95 copilot emulator was written by Greg Hewgill.
The following copyright notice appeared on the original copilot sources:

		  Copyright (c) 1996 Greg Hewgill

 MC68000 Emulation code is from Bernd Schmidt's Unix Amiga Emulator.
       The following copyright notice appeared in those files:

	  Original UAE code Copyright (c) 1995 Bernd Schmidt

This code must not be distributed without these copyright notices intact.

*******************************************************************************
*******************************************************************************

Filename:	main.c

Description:	main file for xcopilot emulator

Update History:   (most recent first)
   Ian Goldberg   26-Jun-98 12:37 -- Made memversion command-line option
                                     and XCOPILOTMEMVERSION env. var.
   Gene McCulley  30-Apr-98 07:02 -- Made ramsize command-line option
   Gene McCulley  28-Apr-98 16:23 -- Added ~/.xcopilot support
   Ian Goldberg   25-Sep-97 11:09 -- rewrite of serial and gdb support
   Ian Goldberg   11-Sep-97 09:48 -- added bus error support
   Jeff Dionne    10-Jul-97 10:00 -- added support for gdb debugging via TCP
   Ian Goldberg   06-Jul-97 17:34 -- added support for serial I/O via a pty
   Brian Grossman 30-Jun-97 24:00 -- added pixeldoubling
   Ian Goldberg   18-Apr-97 11:13 -- added support for gdb debugging via a pty
   I. Curtis       9-Apr-97 11:42 -- v0.4
   I. Curtis      18-Mar-97 14:00 -- v0.3
   I. Curtis      26-Feb-97 13:52 -- first release
   I. Curtis      23-Feb-97 20:43 -- Created.

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

#include "config.h"
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pwd.h>
#include <sys/wait.h>
#include <termios.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include <unistd.h>
#include <sys/ipc.h>
#ifdef USE_PTHREADS
#include <pthread.h>
#else
#include <sys/shm.h>
#endif
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>

#include "sysdeps.h"
#include "shared.h"
#include "memory.h"
#include "custom.h"
#include "newcpu.h"

#include "pilotcpu.h"
#if 0
#include "pdebug.h"
#endif
#include "main.h"
#include "display.h"

extern int tcp_open(char *name);


/*****************************************************************************
 *				Global symbols				     *
 *****************************************************************************/
char *id_version = "QCopilot "VERSION;


/*****************************************************************************
 *				Local symbols				     *
 *****************************************************************************/

/*
 * These error messages correspond to the codes in memory.h
 */
static const char *CpuInitErrors[] = {
  "",
  "ROM file not found",
  "Error loading ROM file",
  "Error loading RAM file",
};


/*****************************************************************************
 *				Local functions				     *
 *****************************************************************************/

/*************************************
 * This is the CPU emulation thread
 *************************************/
static void cpu_proc(shared_img *shptr, Pilot *pilot)
{
  int             r;

  /*
   * init cpu
   * cpu event loop
   */
  r = CPU_init(shptr, pilot->RamSize, pilot->DataDir, pilot->RomFile,
               pilot->RamFile, pilot->ScratchFile, pilot->ResetPilot,
               pilot->NoCheck);
  if (r != 0) {
    fprintf(stderr, "E - %s\n", CpuInitErrors[r]);
    exit(1);
  }
  /*
   * fprintf(stderr, "I - calling CPU..\n"); fflush(stderr);
   */

  /* Start the CPU */
  shptr->CpuReq = cpuStart;
  CPU(shptr);
}

/*************************************
 * This is the LCD emulation thread *
 *************************************/
static void lcd_proc(shared_img *shptr, Pilot *pilot)
{
#if 0
  int no_x_shm = pilot->NoXShm;
  int magfactor = pilot->MagFactor;
  int private_cmap = pilot->PrivateColormap;
  char *sbuf;
#endif

   /*
    * initialize memory
    * init xwindows
    * process events
    */
#ifndef USE_PTHREADS
  memory_init(pilot->RamSize, pilot->DataDir, pilot->RomFile, pilot->RamFile,
              pilot->ScratchFile, pilot->ResetPilot, pilot->NoCheck);
#endif
  display_start(shptr, pilot);
  shptr->CpuReq = cpuExit;
#if 0
  sbuf = xcpInitialize(shptr, pilot->context, pilot->topWidget, no_x_shm, magfactor,
                       private_cmap, NULL, pilot->bbg);
  xcpEventLoop(pilot->context, pilot->topWidget, sbuf, shptr);
#endif
}

#if 0
/********************************
 * This is the Debugger thread *
 ********************************/
#define DEBUG_PORTNUM 2000
static void debug_proc(shared_img *shptr, Pilot *pilot)
{
  if (strcmp(pilot->Debugging, "old")) {
    return;				/* we are not debugging */
  }

  /*
   * initialize memory
   * initialize Shptr in newcpu module
   */
  memory_init(pilot->RamSize, pilot->DataDir, pilot->RomFile, pilot->RamFile,
              pilot->ScratchFile, pilot->ResetPilot, pilot->NoCheck);
  MC68000_setshare(shptr);

  /* Old-style debugging on port 2000 */
  pdebug_loop(pilot->DebugArgs, DEBUG_PORTNUM, shptr);
  shptr->CpuReq = cpuExit;
}

static int correct_write(int fd, unsigned char *buf, int amt)
{
    int wrote = 0;

    while (amt) {
	int res = write(fd, buf, amt);
	if (res <= 0) return res;
	wrote += res;
	buf += res;
	amt -= res;
    }
    return wrote;
}

static void attach_serial(unsigned use_serial, char *SerialPort, int *readfd,
                          int *writefd)
{
  /* Attach to the pty for serial I/O */
  if (use_serial) {
    if ((*readfd = open(SerialPort, O_RDWR)) < 0) {
	fprintf(stderr, "Error opening %s\n", SerialPort);
	perror("open");
	exit(1);
    }
    if ((*writefd = dup(*readfd)) < 0) {
	fprintf(stderr, "Error opening %s\n", SerialPort);
	perror("dup");
	exit(1);
    }
    fprintf(stderr, "Serial I/O on %s\n", SerialPort);
  } else {
    *readfd = 0;
    *writefd = 1;
  }
}

static void attach_gdb(char *DebugArgs, int *gdbfd)
{
    int gdb_fd;

    /* Get a fd for talking to gdb, if necessary */
    if (!DebugArgs) DebugArgs = "/dev/ptyqf";
    if (strchr(DebugArgs, ':')) {
        /* gdb on a TCP port, actually */
        gdb_fd = tcp_open(DebugArgs);
    } else {
        gdb_fd = open(DebugArgs, O_RDWR);
    }
    if (gdb_fd < 0) {
	perror("open");
	fprintf(stderr, "Debugging failed on %s; "
	                "resuming without debugging.\n", DebugArgs);
    } else {
	fprintf(stderr, "Debugging on %s\n", DebugArgs);
    }
    *gdbfd = gdb_fd;
}

static void set_baud(int fd, int baudrate)
{
    struct termios tempio;
    unsigned int brate;

    /* get current setting */
    if (tcgetattr(fd, &tempio) < 0) {
	/* We can't do this on this kind of fd */
	return;
    }
    switch (baudrate) {
    case 300:
	brate = B300;
	break;
    case 1200:
	brate = B1200;
	break;
    case 2400:
	brate = B2400;
	break;
    case 4800:
	brate = B4800;
	break;
    case 9600:
	brate = B9600;
	break;
    case 19200:
	brate = B19200;
	break;
    case 38400:
	brate = B38400;
	break;
#ifdef B57600
    case 57600:
        brate = B57600;
        break;
#endif
    default:
	fprintf(stderr, "E - unsupported baudrate %d; using 38400\n",
			    baudrate);
	brate = B38400;
    }
    tempio.c_iflag = 0;
    tempio.c_oflag = 0;
    tempio.c_cflag |= CLOCAL;
    tempio.c_lflag = 0;
    tempio.c_cc[VMIN] = 1;
    tempio.c_cc[VTIME] = 0;
    if (cfsetispeed(&tempio, brate) < 0) {
	perror("E - cfsetispeed");
    }
    if (cfsetospeed(&tempio, brate) < 0) {
	perror("E - cfsetospeed");
    }
    if (tcsetattr(fd, TCSANOW, &tempio) < 0) {
	perror("E - tcsetattr");
    }
}

static void set_flags(int fd, int flags)
{
    struct termios tempio;

    /* get current setting */
    if (tcgetattr(fd, &tempio) < 0) {
	/* We can't do this on this kind of fd */
	return;
    }
    tempio.c_cflag &= ~(CSIZE|CSTOPB|PARENB|PARODD);
    if (flags & 1) {
	tempio.c_cflag |= CS8;
    } else {
	tempio.c_cflag |= CS7;
    }
    if (flags & 2) {
	tempio.c_cflag |= CSTOPB;
    }
    if (flags & 4) {
	tempio.c_cflag |= PARODD;
    }
    if (flags & 8) {
	tempio.c_cflag |= PARENB;
    }
    if (tcsetattr(fd, TCSANOW, &tempio) < 0) {
	perror("E - tcsetattr");
    }
}

/******************************
 * This is the gdb I/O thread *
 ******************************/
static void gdb_proc(shared_img *shptr, Pilot *pilot)
{
    fd_set	orig_readers;
    fd_set	readers;
    int		maxfd;
    int		gdb_towrite = pilot->gdb_in;
    int		gdb_fd = -1;

    if (strcmp(pilot->Debugging, "gdb")) {
	return;
    }

    attach_gdb(pilot->DebugArgs, &gdb_fd);

    FD_ZERO(&orig_readers);
    FD_SET(gdb_fd, &orig_readers);
    FD_SET(gdb_towrite, &orig_readers);
    maxfd = 0;
    if (gdb_fd > maxfd) maxfd = gdb_fd;
    if (gdb_towrite > maxfd) maxfd = gdb_towrite;
    maxfd++;

    /*
     * WARNING: This uses a shared memory data structure to store the FIFO. The
     * consumer is getting things from this _at the same time_ as this is
     * producing.  Examine custom.c and take a course in concurrent programming
     * before modifying this.  :-) - Ian
     */
    while(shptr->CpuReq != cpuExit) {
	struct timeval tv;
	int selres;
	unsigned char buf[FIFO_SIZE];
	
	readers = orig_readers;
	tv.tv_sec = 1;		/* check once a second if we want to quit */
	tv.tv_usec = 0;

	selres = select(maxfd, &readers, NULL, NULL, &tv);
	if (selres < 0) {
	    perror("select");
	    return;
	}
	if (selres > 0) {
	    /* There's something ready to go. */
	    int res;
	    if (gdb_towrite >= 0 && FD_ISSET(gdb_towrite, &readers)) {
		/* The CPU wants to write to the gdb port */
		res = read(gdb_towrite, buf, FIFO_SIZE);
		if (res > 0) {
		    if (correct_write(gdb_fd, buf, res) <= 0) {
			if (!strcmp(pilot->Debugging, "gdb")) {
			    close(gdb_fd);
			    attach_gdb(pilot->DebugArgs, &gdb_fd);
			}
		    }
		}
	    }

	    if (gdb_fd >= 0 && FD_ISSET(gdb_fd, &readers)) {
		/* There is data available on the gdb port */
		int curhead = shptr->gdb.head;
		int curtail = shptr->gdb.tail;
		if (curtail >= curhead && 
		    (curtail != FIFO_SIZE-1 || curhead != 0)) {
		    res = read(gdb_fd,
				shptr->gdb.fifo+curtail, FIFO_SIZE-curtail);
		    if (res > 0) {
			curtail += res;
			if (curtail == FIFO_SIZE) curtail = 0;
			shptr->gdb.tail = curtail;
		    } else if (!strcmp(pilot->Debugging, "gdb")) {
			close(gdb_fd);
			attach_gdb(pilot->DebugArgs, &gdb_fd);
		    }
		} else if (curtail+1 < curhead) {
		    res = read(gdb_fd,
				shptr->gdb.fifo+curtail, curhead-curtail-1);
		    if (res > 0) {
			curtail += res;
			if (curtail == FIFO_SIZE) curtail = 0;
			shptr->gdb.tail = curtail;
		    } else if (!strcmp(pilot->Debugging, "gdb")) {
			close(gdb_fd);
			attach_gdb(pilot->DebugArgs, &gdb_fd);
		    }
		}
	    }
	}
    }
}

/*********************************
 * This is the serial I/O thread *
 *********************************/
static void serial_proc(shared_img *shptr, Pilot *pilot)
{
    fd_set	orig_readers;
    fd_set	readers;
    int		maxfd;
    int		serial_towrite = pilot->serial_in;
    int		serial_read = 0;
    int		serial_write = 1;
    int		serial_baud = 0;
    int		serial_flags = 0;

    attach_serial(pilot->Serial, pilot->SerialPort, &serial_read,
                  &serial_write);

    FD_ZERO(&orig_readers);
    FD_SET(serial_towrite, &orig_readers);
    FD_SET(serial_read, &orig_readers);
    maxfd = 0;
    if (serial_towrite > maxfd) maxfd = serial_towrite;
    if (serial_read > maxfd) maxfd = serial_read;
    maxfd++;

    /*
     * WARNING: This uses a shared memory data structure to store the FIFO. The
     * consumer is getting things from this _at the same time_ as this is
     * producing.  Examine custom.c and take a course in concurrent programming
     * before modifying this.  :-) - Ian
     */
    while(shptr->CpuReq != cpuExit) {
	struct timeval tv;
	int selres;
	unsigned char buf[FIFO_SIZE];
	
	readers = orig_readers;
	tv.tv_sec = 1;		/* check once a second if we want to quit */
	tv.tv_usec = 0;

	selres = select(maxfd, &readers, NULL, NULL, &tv);
	if (selres < 0) {
	    perror("select");
	    return;
	}
	if (selres > 0) {
	    /* There's something ready to go. */
	    int res;

	    if (serial_towrite >= 0 && FD_ISSET(serial_towrite, &readers)) {
		/* The CPU wants to write to the serial port */
                res = read(serial_towrite, buf, FIFO_SIZE);
		if (res > 0) {
		    /* Check the baud rate */
		    if (serial_baud != shptr->serial_baud) {
			set_baud(serial_write, shptr->serial_baud);
			serial_baud = shptr->serial_baud;
		    }
		    if (serial_flags != shptr->serial_flags) {
			set_flags(serial_write, shptr->serial_flags);
			serial_flags = shptr->serial_flags;
		    }
		    if (correct_write(serial_write, buf, res) <= 0) {
			attach_serial(pilot->Serial, pilot->SerialPort,
			              &serial_read, &serial_write);
		    }
		}
	    }

	    if (serial_read >= 0 && FD_ISSET(serial_read, &readers)) {
		/* There is data available on the serial port */
		int curhead = shptr->serial.head;
		int curtail = shptr->serial.tail;
		if (curtail >= curhead &&
		    (curtail != FIFO_SIZE-1 || curhead != 0)) {
		    res = read(serial_read,
				shptr->serial.fifo+curtail, FIFO_SIZE-curtail);
		    if (res > 0) {
			curtail += res;
			if (curtail == FIFO_SIZE) curtail = 0;
			shptr->serial.tail = curtail;
		    } else {
			attach_serial(pilot->Serial, pilot->SerialPort,
			              &serial_read, &serial_write);
		    }
		} else if (curtail+1 < curhead) {
		    res = read(serial_read,
				shptr->serial.fifo+curtail, curhead-curtail-1);
		    if (res > 0) {
			curtail += res;
			if (curtail == FIFO_SIZE) curtail = 0;
			shptr->serial.tail = curtail;
		    } else {
			attach_serial(pilot->Serial, pilot->SerialPort,
			              &serial_read, &serial_write);
		    }
		}
	    }

	}
    }
}
#endif

#ifdef USE_PTHREADS
typedef void PT_proc(shared_img *shptr, Pilot *pilot);

struct pt_data {
    PT_proc *proc;
    Pilot *pilot;
    shared_img *shptr;
};

static void *startit(void *arg)
{
    struct pt_data *p = (struct pt_data *)arg;
    PT_proc *proc = p->proc;
    Pilot *pilot = p->pilot;
    shared_img *shptr = p->shptr;
    free(p);
    proc(shptr, pilot);
    return NULL;
}

static void
PT_create(Pilot *pilot, shared_img *shptr, PT_proc *proc, pthread_t *thar)
{
    /* We can't just use an automatic structure here for p because there's
     * a race condition involving the structure getting deallocated, and
     * its contents being used by startit. */
    struct pt_data *p = malloc(sizeof(struct pt_data));
    p->proc = proc;
    p->pilot = pilot;
    p->shptr = shptr;
    pthread_create(&(thar[pilot->threads++]), NULL, startit, p);
}

static void
PT_wait(Pilot *pilot, pthread_t *thar)
{
    while(pilot->threads > 0) {
	pthread_join(thar[pilot->threads - 1], NULL);
	pilot->threads--;
    }
}

#else
/*********************************************
 * attach to the shared memory blocks        *
 * for the main memory and for the registers *
 * of the custom block                       *
 *********************************************/
static shared_img *attach_shm(Pilot *pilot)
{
  void *s;

  s = shmat(pilot->shmid, (char *)0, 0);
  if (s == (void *)-1) {
    perror("shmat");
    exit(1);
  }
  return((shared_img *)s);
}

/*****************************************************************************
 *			Pilot Thread stuff
 *****************************************************************************/
typedef void PT_proc(shared_img *shptr, Pilot *pilot);

static void
PT_create(Pilot *pilot, PT_proc *proc)
{
	pid_t	pid;
	/*
	 * fork off a new thread
	 */
	if ((pid = fork()) < 0) {
		perror("fork");
		exit(1);

	} else if (pid == 0) {
		shared_img *shptr;

		shptr = attach_shm(pilot);
		proc(shptr, pilot);
  		shmdt((char *)shptr);

		exit(0);
	} else {
		pilot->threads++;
	}
}

static void
PT_wait(Pilot *pilot)
{
	/*
	 * catch thread as they exit
	 */
	while (pilot->threads > 0) {
		int	status;

		if (wait(&status) < 0) {
			if (errno == EINTR) {
			} else if (errno == ECHILD) {
			    printf("ECHILD\n");
				exit(-1); /* Hey, where did everyone go? */
			} else {
			    printf("errno = %d\n", errno);
				exit(-1); /* not unexpected, what the hell? */
			}

		} else {
			pilot->threads--;
		}
	}
}
#endif

void expand_tilde(char **string)
{
  if (*string[0] == '~') {
    struct passwd *user_info;
    char *new_dir;
    char *end_of_name = strchr(*string, '/');
    const char *homedir = NULL;

    if (end_of_name == *string + 1 ) {
	homedir = getenv("HOME");
	if (!homedir) {
	  user_info = getpwuid(getuid()); 
	  homedir = user_info->pw_dir;
	}
    } else {
      char *name;
      int name_len;

      name_len = end_of_name - &(*string)[0] - 1;
      name = malloc(name_len + 1);
      strncpy(name, &(*string)[1], name_len);
      name[name_len] = '\0';
      user_info = getpwnam(name); 
      homedir = user_info->pw_dir;
      free(name);
    }

    if (!homedir) homedir = "";
    new_dir = malloc(strlen(homedir) + strlen(end_of_name + 1) + 2);
    strcpy(new_dir, homedir);
    strcat(new_dir, "/");
    strcat(new_dir, end_of_name + 1);
    *string = new_dir;
  }
}

static void add_slash(char **string)
{
  if ((*string)[strlen(*string) - 1] != '/') {
    char *new_string = malloc(strlen(*string) + 2);

    strcpy(new_string, *string);
    strcat(new_string, "/");
    *string = new_string;
  }
}

/*****************************************************************************
 *                                                                           *
 *		     Main - Execution starts here                            *
 *                                                                           *
 *****************************************************************************/
int init(Pilot *pilot)
{
  struct stat buf;

  expand_tilde(&pilot->DataDir);
  add_slash(&pilot->DataDir);
  if (stat(pilot->DataDir, &buf) == -1) {
    if (errno == ENOENT) {
      if (mkdir(pilot->DataDir, S_IRWXU)) {
        perror("Could not create rc directory");
        exit(1);
      }
    }
    else {
        perror("Could not access rc directory");
        exit(1);
    }
  }

#if 0
  /* Set the memory version */
  pdebug_memversion = pilot->MemVersion;
#endif

  /* Set up the pipes */
  if (pipe(pilot->serial_pipefd) < 0) {
    perror("pipe");
    exit(1);
  }

  if (!strcmp(pilot->Debugging, "gdb")) {
    if (pipe(pilot->gdb_pipefd) < 0) {
      perror("pipe");
      exit(1);
    }
  } else {
    pilot->gdb_pipefd[0] = -1;	/* gdb_in */
    pilot->gdb_pipefd[1] = -1;	/* gdb_out */
  }

#ifdef USE_PTHREADS
  {
      pthread_t thar[5];
      shared_img *shptr;
      int r;
      
      shptr = calloc(1, sizeof(shared_img));
#else
  /*
   * get a shared memory segment
   */
  pilot->shmid = shmget((key_t) 0, sizeof(shared_img), IPC_CREAT | 0777);
  if (pilot->shmid < 0) {
    perror("shmget");
    exit(1);
  } else {
    shared_img *shptr = attach_shm(pilot);
#endif
  
    /* Init the shm segment */
    shptr->CpuReq = cpuNone;
    shptr->run_updateisr = 0;
    shptr->allowromwrites = 0;

    shptr->pen = 0;
    shptr->pendown = 0;
    shptr->kbin = 0;
    shptr->kbout = 0;

    shptr->LcdReq = lcdNone;

    shptr->gdb_writefd = pilot->gdb_out;
    shptr->gdb.head = 0;
    shptr->gdb.tail = 0;

    shptr->serial_writefd = pilot->serial_out;
    shptr->serial.head = 0;
    shptr->serial.tail = 0;
    shptr->serial_baud = 0;
    shptr->serial_flags = 0;
  
#ifdef USE_PTHREADS
    r = memory_init(pilot->RamSize, pilot->DataDir, pilot->RomFile,
	    pilot->RamFile, pilot->ScratchFile, pilot->ResetPilot,
	    pilot->NoCheck);
    if (r) {
	fprintf(stderr, "Error: %s\n", CpuInitErrors[r]);
	exit(1);
    }
    PT_create(pilot, shptr, lcd_proc, thar);
    PT_create(pilot, shptr, cpu_proc, thar);
    PT_wait(pilot, thar);
  }
#else
    /*
     * Detach the segment before doing fork().  Some OS's have the
     * child inherit the attached segments.
     */
    shmdt((char *)shptr);
  }

  /*
   * Start display thread
   * Start cpu thread
   * Start debug thread
   * Start serial thread
   */
  PT_create(pilot, lcd_proc);
  PT_create(pilot, cpu_proc);
#if 0
  PT_create(pilot, debug_proc);
  PT_create(pilot, gdb_proc);
  PT_create(pilot, serial_proc);
#endif

  PT_wait(pilot);	/* wait until all threads have exited */

  /* Delete the shared mem */
  shmctl(pilot->shmid, IPC_RMID, 0);
#endif
  return 0;
}
