/* for the Linux dos emulator versions 0.49 and newer
 *
 */
#define LPT_C 1

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

#include "emu.h"
#include "bios.h"
#include "port.h"
#include "timers.h"
#include "lpt.h"
#include "utilities.h"
#include "dos2linux.h"
#include "fslib/fslib.h"
#include "ioselect.h"

/* status bits, Centronics */
#define CTS_STAT_NOIOERR	LPT_STAT_NOIOERR
#define CTS_STAT_ONLINE		LPT_STAT_ONLINE
#define CTS_STAT_NOPAPER	LPT_STAT_NOPAPER
#define CTS_STAT_NOT_ACKing	LPT_STAT_NOT_ACK
#define CTS_STAT_BUSY		LPT_STAT_NOT_BUSY

/* control bits, Centronics */
#define CTS_CTRL_NOT_SELECT	LPT_CTRL_SELECT
#define CTS_CTRL_NOT_INIT	LPT_CTRL_NOT_INIT
#define CTS_CTRL_NOT_AUTOLF	LPT_CTRL_AUTOLF
#define CTS_CTRL_NOT_STROBE	LPT_CTRL_STROBE

/* inversion masks to convert LPT<-->Centronics */
#define LPT_STAT_INV_MASK (CTS_STAT_BUSY)
#define LPT_CTRL_INV_MASK (CTS_CTRL_NOT_STROBE | CTS_CTRL_NOT_AUTOLF | \
	CTS_CTRL_NOT_SELECT)

#define DEFAULT_STAT (CTS_STAT_ONLINE | CTS_STAT_NOIOERR | \
    CTS_STAT_NOT_ACKing | LPT_STAT_NOT_IRQ)
#define DEFAULT_CTRL (CTS_CTRL_NOT_INIT | CTS_CTRL_NOT_AUTOLF | \
    CTS_CTRL_NOT_STROBE)

#define NUM_PRINTERS 9
static struct printer lpt[NUM_PRINTERS] =
{
  {NULL, NULL, 5, 0x378, .control = DEFAULT_CTRL, .status = DEFAULT_STAT},
  {NULL, NULL, 5, 0x278, .control = DEFAULT_CTRL, .status = DEFAULT_STAT},
  {NULL, NULL, 10, 0x3bc, .control = DEFAULT_CTRL, .status = DEFAULT_STAT}
};

ioport_t get_lpt_base(int lptnum)
{
  if (lptnum >= NUM_LPTS)
    return -1;
  return lpt[lptnum].base_port;
}

static int get_printer(ioport_t port)
{
  int i;
  for (i = 0; i < NUM_LPTS; i++)
    if (lpt[i].base_port <= port && port <= lpt[i].base_port + 2)
      return i;
  return -1;
}

static Bit8u printer_io_read(ioport_t port, void *arg)
{
  int i = get_printer(port);
  Bit8u val;

  if (i == -1)
    return 0xff;

  switch (port - lpt[i].base_port) {
  case 0:
    val = lpt[i].data; /* simple unidirectional port */
    if (debug_level('p') >= 5)
      p_printf("LPT%d: Reading data byte %#x\n", i+1, val);
    break;
  case 1: /* status port, r/o */
    val = lpt[i].status ^ LPT_STAT_INV_MASK;
    /* we should really set ACK after 5 us but here we just
       use the fact that the BIOS only checks this once */
    lpt[i].status |= CTS_STAT_NOT_ACKing | LPT_STAT_NOT_IRQ;
    lpt[i].status &= ~CTS_STAT_BUSY;
    if (debug_level('p') >= 5)
      p_printf("LPT%d: Reading status byte %#x\n", i+1, val);
    break;
  case 2:
    val = lpt[i].control ^ LPT_CTRL_INV_MASK;
    if (debug_level('p') >= 5)
      p_printf("LPT%d: Reading control byte %#x\n", i+1, val);
    break;
  default:
    val = 0xff;
    break;
  }
  return val;
}

static void printer_io_write(ioport_t port, Bit8u value, void *arg)
{
  int i = get_printer(port);
  if (i == -1)
    return;
  switch (port - lpt[i].base_port) {
  case 0:
    if (debug_level('p') >= 5)
      p_printf("LPT%d: Writing data byte %#x\n", i+1, value);
    lpt[i].data = value;
    break;
  case 1: /* status port, r/o */
    break;
  case 2:
    if (debug_level('p') >= 5)
      p_printf("LPT%d: Writing control byte %#x\n", i+1, value);
    value ^= LPT_CTRL_INV_MASK;		// convert to Centronics
    if (((lpt[i].control & (CTS_CTRL_NOT_STROBE | CTS_CTRL_NOT_SELECT)) == 0)
        && (value & CTS_CTRL_NOT_STROBE)) {
      /* STROBE rising */
      if (debug_level('p') >= 9)
        p_printf("LPT%d: STROBE, sending %#x (%c)\n", i+1, lpt[i].data,
	    lpt[i].data);
      printer_write(i, lpt[i].data);
      lpt[i].status &= ~CTS_STAT_NOT_ACKing;
      lpt[i].status |= CTS_STAT_BUSY;
    }
    lpt[i].control = value;
    break;
  }
}

static int dev_printer_open(int prnum)
{
  lpt[prnum].dev_fd = open(lpt[prnum].dev, O_WRONLY);
  if (lpt[prnum].dev_fd == -1) {
    error("LPT%i: error opening %s: %s\n", prnum+1, lpt[prnum].dev,
	strerror(errno));
    return -1;
  }
  p_printf("LPT: opened printer %d to %s\n", prnum, lpt[prnum].dev);
  return 0;
}

static void pipe_callback(int fd, void *arg)
{
  char buf[1024];
  int num = (long)arg;
  int n = read(lpt[num].file.from_child, buf, sizeof(buf));
  if (n > 0) {
    buf[n] = 0;
    error("LPT%i: %s\n", num+1, buf);
    ioselect_complete(fd);
  }
}

int lpt_popen(int prnum, struct popen2 *file)
{
  int err;

  assert(prnum < config.num_lpt);
  err = popen2_custom(lpt[prnum].prtcmd, file);
  if (err)
    return -1;
  return 2;  // 2 fds to xfer
}

static int pipe_printer_open(int prnum)
{
  int err;
  err = fslib_popen_knownpath(SUBSYS_KP_LPT, prnum, &lpt[prnum].file);
  if (err) {
    error("system(\"%s\") in lpt.c failed, cannot print! "
	"Command returned error %s\n", lpt[prnum].prtcmd, strerror(errno));
    return err;
  }
  p_printf("LPT: doing printer command ..%s..\n", lpt[prnum].prtcmd);
  add_to_io_select(lpt[prnum].file.from_child, pipe_callback, (void *)(long)prnum);
  return err;
}

int printer_open(int prnum)
{
  int rc;

  if (!lpt[prnum].initialized)
    return -1;
  if (lpt[prnum].opened) {
    dosemu_error("opening printer %i twice\n", prnum);
    return 0;
  }

  rc = lpt[prnum].fops.open(prnum);
  if (!rc)
    lpt[prnum].opened = 1;
  else
    error("Error opening printer %i\n", prnum);
  return rc;
}

static int dev_printer_close(int prnum)
{
  return close(lpt[prnum].dev_fd);
}

static int do_waitpid(int pid, int *status)
{
  int retval;
  while ((retval = fslib_waitpid(pid, status)) == 0)
    dosemu_sleep();
  if (retval == -1)
    error("waitpid failed\n");
  return retval;
}

static int pipe_printer_close(int prnum)
{
  remove_from_io_select(lpt[prnum].file.from_child);
  return pclose2_custom(&lpt[prnum].file, do_waitpid);
}

int printer_close(int prnum)
{
  if (lpt[prnum].opened && lpt[prnum].fops.close) {
    p_printf("LPT%i: closing printer\n", prnum+1);
    lpt[prnum].fops.close(prnum);
    lpt[prnum].remaining = 0;
  }
  lpt[prnum].opened = 0;
  return 0;
}

static int dev_printer_write(int prnum, Bit8u outchar)
{
  return write(lpt[prnum].dev_fd, &outchar, 1);
}

static int pipe_printer_write(int prnum, Bit8u outchar)
{
  return write(lpt[prnum].file.to_child, &outchar, 1);
}

int printer_write(int prnum, Bit8u outchar)
{
  if (!lpt[prnum].initialized)
    return -1;
  if (!lpt[prnum].opened)
    printer_open(prnum);
  lpt[prnum].remaining = lpt[prnum].delay;
  if (debug_level('p') >= 9)
    p_printf("LPT%d: writing %#x (%c)\n", prnum+1, outchar, outchar);
  return lpt[prnum].fops.write(prnum, outchar);
}

/* DANG_BEGIN_FUNCTION printer_init
 *
 * description:
 *  Initialize printer control structures
 *
 * DANG_END_FUNCTIONS
 */
static struct p_fops dev_pfops =
{
  dev_printer_open,
  dev_printer_write,
  dev_printer_close,
};
static struct p_fops pipe_pfops =
{
  pipe_printer_open,
  pipe_printer_write,
  pipe_printer_close,
};

void
printer_init(void)
{
  int i;
  emu_iodev_t io_device = {};

  io_device.read_portb   = printer_io_read;
  io_device.write_portb  = printer_io_write;
  io_device.read_portw   = NULL;
  io_device.write_portw  = NULL;
  io_device.read_portd   = NULL;
  io_device.write_portd  = NULL;
  io_device.handler_name = "Parallel printer";

  for (i = 0; i < NUM_PRINTERS; i++) {
    lpt[i].initialized = 0;
    lpt[i].opened = 0;
    lpt[i].remaining = 0;	/* mark not accessed yet */
    if (!lpt[i].dev && !lpt[i].prtcmd)
      continue;
    p_printf("LPT%i: initializing printer %s\n", i+1,
	lpt[i].dev ? lpt[i].dev : lpt[i].prtcmd);
    if (lpt[i].dev)
      lpt[i].fops = dev_pfops;
    else if (lpt[i].prtcmd)
      lpt[i].fops = pipe_pfops;
    if (i >= _min(config.num_lpt, NUM_LPTS)) lpt[i].base_port = 0;

    if (lpt[i].base_port != 0) {
      io_device.start_addr = lpt[i].base_port;
      io_device.end_addr   = lpt[i].base_port + 2;
      port_register_handler(io_device, 0);
    }
    lpt[i].initialized = 1;
  }
}

void
close_all_printers(void)
{
  int loop;

  for (loop = 0; loop < NUM_PRINTERS; loop++) {
    if (!lpt[loop].opened)
      continue;
    p_printf("LPT: closing printer %d (%s)\n", loop,
	     lpt[loop].dev ? lpt[loop].dev : lpt[loop].prtcmd);
    printer_close(loop);
    free(lpt[loop].dev);
    free(lpt[loop].prtcmd);
  }
}

int printer_tick(void)
{
  int i;

  for (i = 0; i < NUM_PRINTERS; i++) {
    if (lpt[i].remaining > 0) {
      if (debug_level('p') >= 9)
        p_printf("LPT%i: doing tick %d\n", i+1, lpt[i].remaining);
      if (lpt[i].remaining) {
        reset_idle(2);
	lpt[i].remaining--;
	if (!lpt[i].remaining)
	  printer_close(i);
      }
    }
  }
  return 0;
}

void lpt_set_command(int prnum, char *cmd)
{
    assert(prnum < NUM_PRINTERS);
    lpt[prnum].prtcmd = cmd;
    if (config.num_lpt <= prnum)
      config.num_lpt = prnum + 1;
}

void printer_config(int prnum, struct printer *pptr)
{
  struct printer *destptr;

  if (prnum < NUM_PRINTERS) {
    destptr = &lpt[prnum];
    fslib_set_command(SUBSYS_KP_LPT, prnum, pptr->prtcmd);
    if (!destptr->prtcmd)
      destptr->prtcmd = pptr->prtcmd;
    destptr->dev = pptr->dev;
    destptr->delay = pptr->delay;
  }
}

void printer_print_config(int prnum, void (*print)(const char *, ...))
{
  struct printer *pptr = &lpt[prnum];
  (*print)("LPT%d command \"%s\"  timeout %d  device \"%s\"  baseport 0x%03x\n",
	  prnum+1, (pptr->prtcmd ? pptr->prtcmd : ""), pptr->delay,
	   (pptr->dev ? pptr->dev : ""), pptr->base_port);
}

int lpt_get_max(void)
{
  return NUM_PRINTERS;
}

int lpt_is_configured(int num)
{
  return lpt[num].initialized;
}
