/* Copyright (C) 1989, 1992 Aladdin Enterprises.  All rights reserved.
   Distributed by Free Software Foundation, Inc.

This file is part of Ghostscript.

Ghostscript is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY.  No author or distributor accepts responsibility
to anyone for the consequences of using it or for whether it serves any
particular purpose or works at all, unless he says so in writing.  Refer
to the Ghostscript General Public License for full details.

Everyone is granted permission to copy, modify and redistribute
Ghostscript, but only under the conditions described in the Ghostscript
General Public License.  A copy of this license is supposed to have been
given to you along with Ghostscript so you can know your rights and
responsibilities.  It should be in a file named COPYING.  Among other
things, the copyright notice and this notice must be preserved on all
copies.  */

/* gp_djgpp.c */
/* DJ's GCC-specific routines for Ghostscript */

#include <stdarg.h>
#include <fcntl.h>
#include "memory_.h"
#include "string_.h"
#include "gx.h"
#include "gp.h"
#include "signal.h"
#include "stat_.h"
#include "time_.h"
#include "iref.h"
#include "dict.h"
#include "dstack.h"
#include "iname.h"
#include "dparam.h"
#include "dos_.h"
#include "ctype_.h"
#include "alloc.h"
#include "errors.h"
#include "oper.h"
#include "store.h"

#include "gp_djgpp.h"

int gp_printer_putc(int ,FILE *);
int gp_printer_write(const void *, int ,int ,FILE *);
int gp_printer_puts(const char *, FILE * );
int gp_printer_printf(FILE *, const char *, ...);
int gp_printer_flush(FILE *);

private int gp_printer_device_flush(byte *,int);
private int gp_setup_system(int,int,int,int);
private void gp_djgpp_fill_system_procs(gp_djgpp_system_procs * );
private FILE *gp_djgpp_popen(const char *,const char *);
private int gp_djgpp_pclose(FILE *);

/* declaration */
#define djgpp_system_procs__(p) extern gp_djgpp_system_procs p ;
#define djgpp_system_name__(s,n) /**/
#include "gp_djg_.h"
djgpp_system_procs__(gp_djgpp_default_procs)
#undef djgpp_system_procs__

#define djgpp_system_procs__(p)  & p,
private gp_djgpp_system_procs *SystemInfo[] = {
#include "gp_djg_.h"
  djgpp_system_procs__(gp_djgpp_default_procs)
  0,
};
#undef djgpp_system_procs__
#undef djgpp_system_name__

#define djgpp_system_procs__(p) /**/
#define djgpp_system_name__(s,n) n,
enum {
#include "gp_djg_.h"
  system_name_index_default,
  system_name_index_count,
};
static ref gp_system_names[system_name_index_count];
#undef djgpp_system_name__

#define djgpp_system_name__(s,n) { s, &gp_system_names[ n ] },
static const names_def SystemNames[] = {
#include "gp_djg_.h"
  djgpp_system_name__("OTHER",system_name_index_default)
  names_def_end
};
#undef djgpp_system_name__

static ref name_DosSetupName;
static ref name_DosSetupPrintTarget;
static ref name_DosSetupPrintWait;
static ref name_DosSetupPrintBufferSize;
static const names_def DosSetupNames[] = {
  { "Name",             &name_DosSetupName },
  { "PrintTarget",      &name_DosSetupPrintTarget },
  { "PrintWait",        &name_DosSetupPrintWait },
  { "PrintBufferSize",  &name_DosSetupPrintBufferSize },
  names_def_end
};

static ref gp_target_names[gp_printer_to_count];
#define name_TargetPort gp_target_names[gp_printer_to_port]
#define name_TargetPort1 gp_target_names[gp_printer_to_port1]
#define name_TargetPort2 gp_target_names[gp_printer_to_port2]
#define name_TargetPort3 gp_target_names[gp_printer_to_port3]
#define name_TargetBios  gp_target_names[gp_printer_to_bios]
#define name_TargetBios1 gp_target_names[gp_printer_to_bios1]
#define name_TargetBios2 gp_target_names[gp_printer_to_bios2]
#define name_TargetBios3 gp_target_names[gp_printer_to_bios3]
#define name_TargetStdprn  gp_target_names[gp_printer_to_stdprn]
static const names_def TargetNames[] = {
  {"PORT", &name_TargetPort },
  {"PORT1", &name_TargetPort1},
  {"PORT2", &name_TargetPort2},
  {"PORT3", &name_TargetPort3},
  {"BIOS",  &name_TargetBios},
  {"BIOS1", &name_TargetBios1},
  {"BIOS2", &name_TargetBios2},
  {"BIOS3", &name_TargetBios3},
  {"STDPRN", &name_TargetStdprn},
  names_def_end
};


/* Do platform-dependent initialization. */
void
gp_init(void)
{
  int i;
  for (i=0;SystemInfo[i];i++) {
    gp_djgpp_fill_system_procs(SystemInfo[i]);
  }
  gp_setup_system(system_name_index_default,gp_printer_to_stdprn,1024,0);
}

/* Do platform-dependent cleanup. */
void
gp_exit(int exit_status, int code)
{
}

/* ------ Screen management ------ */

/* Write a string to the console. */
void
gp_console_puts(const char *str, uint size)
{	fwrite(str, 1, size, stdout);
}

/* Make the console current on the screen. */
int
gp_make_console_current(struct gx_device_s *dev)
{	return 0;
}

/* Make the graphics current on the screen. */
int
gp_make_graphics_current(struct gx_device_s *dev)
{	return 0;
}

/* ------ Printer accessing ------ */

/* Open a connection to a printer.  A null file name means use the */
/* standard printer connected to the machine, if any. */
/* Return NULL if the connection could not be opened. */
extern void gp_set_printer_binary(P2(int, int));
FILE *
gp_open_printer(char *fname, int binary_mode)
{
  if ( strlen(fname) == 0 || !strcmp(fname, "PRN") ) {
    gp_set_printer_binary(fileno(stdprn),binary_mode);
    return stdprn;
  }
  else {
    const char *mode = binary_mode ? "wb":"w";
    return (fname[0] == '|') ? gp_djgpp_popen(fname,mode) : fopen(fname, mode);
  }
}

/* Close the connection to the printer. */
void
gp_close_printer(FILE *pfile, const char *fname)
{
  if (pfile == stdprn) gp_printer_flush(pfile);
  else if (fname[0] == '|') gp_djgpp_pclose(pfile);
  else fclose(pfile);
}

/* Create and open a scratch file with a given name prefix. */
/* Write the actual file name at fname. */
FILE *
gp_open_scratch_file(const char *prefix, char *fname, const char *mode)
{	char *temp;
	if ( (temp = getenv("TEMP")) == NULL )
      temp = getenv("TMP");
	if ( temp == NULL )
		*fname = 0;
	else
	{	char last = '/';
		strcpy(fname, temp);
		/* Prevent X's in path from being converted by mktemp. */
		for ( temp = fname; *temp; temp++ )
			*temp = last = (*temp == '\\' ? '/' : tolower(*temp));
		switch ( last )
		{
		default:
			strcat(fname, "/");
		case ':': case '/':
			;
		}
	}
	strcat(fname, prefix);
	strcat(fname, "XXXXXX");
	mktemp(fname);
	return fopen(fname, mode);
}

/* ------ File operations ------ */

/* If the file given by fname exists, fill in its status and return 1; */
/* otherwise return 0. */
int
gp_file_status(const char *fname, file_status *pstatus)
{	struct stat sbuf;
	/* The RS/6000 prototype for stat doesn't include const, */
	/* so we have to explicitly remove the const modifier. */
	if ( stat((char *)fname, &sbuf) < 0 ) return 0;
	pstatus->size_pages = (sbuf.st_size + 1023) >> 10; /* from gp_itbc.c */
	pstatus->size_bytes = sbuf.st_size;
	pstatus->time_referenced = sbuf.st_mtime;
	pstatus->time_created = sbuf.st_ctime;
	return 1;
}

/*** ------------- Printer output routines. -------------- ***/
/***  These procedures are NOT standard gp procedures.     ***/
/***  Use these procedures insted of                       ***/
/***    fputc(), fputs(), fwrite(), fprintf() and fflush() ***/
/***    in each source file of printer device driver.      ***/

private int PRN_BUF_SIZE = 1024;
private byte *gp_printer_buf;
private int gp_printer_buf_count = 0;

int gp_printer_putc(int c,FILE *fp)
{
  if (fp == stdprn) {
    gp_printer_buf[gp_printer_buf_count++] = c;
    if (gp_printer_buf_count == PRN_BUF_SIZE) {
      int code = gp_printer_device_flush(gp_printer_buf,gp_printer_buf_count);
      gp_printer_buf_count = 0;
      if (code) return EOF;
    }
    return c;
  }
  return fputc(c,fp);
}

int
  gp_printer_write(const void *ptr, int size,int count,FILE *fp)
{
  if (fp == stdprn) {
    int n;
    int code;
    size *= count;
    while (size>0) {
      if (size >= (n = PRN_BUF_SIZE-gp_printer_buf_count)) {
        size -= n;
        while(n--) {
          gp_printer_buf[gp_printer_buf_count++] = *(byte *)ptr++;
        }
        code = gp_printer_device_flush(gp_printer_buf,gp_printer_buf_count);
        gp_printer_buf_count = 0;
        if (code) return (count-1);
      }
      else {
        while (size--) {
          gp_printer_buf[gp_printer_buf_count++] = *(byte *)ptr++;
        }
        break;
      }
    }
    return count;
  }
  return fwrite(ptr,size,count,fp);
}

int
  gp_printer_puts(const char *ptr,FILE *fp)
{
  if (fp == stdprn) {
    if (gp_printer_write(ptr,strlen(ptr),1,fp) < 1)
      return -1;
    return 0;
  }
  return fputs(ptr,fp);
}

int
  gp_printer_vprintf(FILE *fp,const char *fmt,va_list ap)
{
  static char buf[256];
  int code = vsprintf(buf,fmt,ap);
  
  return gp_printer_write(buf,code,1,fp) < 1 ? -1 : code;
}

int
  gp_printer_printf(FILE *fp,const char *fmt,...)
{
  int code;
  va_list ap;
  
  va_start(ap,fmt);
  code = gp_printer_vprintf(fp,fmt,ap);
  va_end(ap);
  
  return code;
}

int
  gp_printer_flush(FILE *fp)
{
  if (fp == stdprn) {
    if (gp_printer_buf_count) {
      int code = gp_printer_device_flush(gp_printer_buf,gp_printer_buf_count);
      gp_printer_buf_count = 0;
      return code;
    }
    return 0;
  }
  return fflush(fp);
}


/* ----------------- Internal routines ---------------- */

/*** Machine dependent informations. ***/

private gp_djgpp_identify_proc(gp_default_identify);
private gp_djgpp_open_console_proc(gp_default_open_console);
private gp_djgpp_close_console_proc(gp_default_close_console);
private gp_djgpp_open_printer_proc(gp_default_open_printer);
private gp_djgpp_close_printer_proc(gp_default_close_printer);
private gp_djgpp_alloc_buffer_proc(gp_default_alloc_buffer);
private gp_djgpp_free_buffer_proc(gp_default_free_buffer);
private gp_djgpp_flush_printer_proc(gp_default_flush);

gp_djgpp_system_procs gp_djgpp_default_procs = {
  gp_printer_to_stdprn,
  gp_default_identify,
  0, /* open console */
  0, /* close console */
  0, /* open printer */
  0, /* close printer */
  0, /* allocate buffer */
  0, /* free buffer */
  0, /* flush printer PORT */
  0, /* flush printer BIOS */
};

private gp_djgpp_system_procs gp_current_system;
private int gp_current_system_index;
private int gp_current_target_name;
private int gp_current_target_type;
private int gp_current_target_no;
private int gp_printer_wait = 0;
private int (*gp_printer_current_flush_proc)(byte *,int);

/********************************* operators *********************************/

private int
  gp_djgpp_notidentify(int v)
{ return 0;
}

private void
  gp_djgpp_fill_system_procs(gp_djgpp_system_procs *procs)
{
#define copy_proc(p,pp) if (procs->p == 0) procs->p = pp
  copy_proc(identify_proc, gp_djgpp_notidentify);
  copy_proc(open_console_proc, gp_default_open_console);
  copy_proc(close_console_proc, gp_default_close_console);
  copy_proc(open_printer_proc, gp_default_open_printer);
  copy_proc(close_printer_proc, gp_default_close_printer);
  copy_proc(alloc_buffer_proc, gp_default_alloc_buffer);
  copy_proc(free_buffer_proc, gp_default_free_buffer);
  copy_proc(flush_port_proc, gp_default_flush);
  copy_proc(flush_bios_proc, gp_default_flush);
#undef copy_proc
}

private int
  gp_djgpp_op_init(void)
{
  init_names(DosSetupNames);
  init_names(TargetNames);
  init_names(SystemNames);
  return 0;
}

private int
  zgp_djgpp_identifysystemauto(register os_ptr op)
{
  int i;
  int oem_ser;
  
  { /* Dependent on MS-DOS. */
    union REGS r;
    r.h.ah = 0x30;
    intdos(&r,&r); /* get OEM number. */
    oem_ser = r.h.bh;
  }
  
  for (i=0;SystemInfo[i];i++) {
    if (SystemInfo[i]->identify_proc(oem_ser))
      break;
  }
  /* cannot happen. */
  if (!SystemInfo[i]) i = system_name_index_default;
  
  push(1);
  op[0] = gp_system_names[i];
  return 0;
}

private int
  zgp_djgpp_currentsystem(register os_ptr op)
{
  int code;
  ref tmpref;
  ref dosdict;
  
  if ((code = dict_create(countof(DosSetupNames), &dosdict)) < 0)
    return code;
  
  if ((code = dict_put(&dosdict, &name_DosSetupName ,
                 &gp_system_names[gp_current_system_index])) < 0)
    return code;
  if ((code = dict_put(&dosdict, &name_DosSetupPrintTarget ,
                 &gp_target_names[gp_current_target_name])) < 0)
    return code;
  make_int(&tmpref, gp_printer_wait);
  if ((code = dict_put(&dosdict, &name_DosSetupPrintWait , &tmpref)) < 0)
    return code;
  make_int(&tmpref, PRN_BUF_SIZE);
  if ((code = dict_put(&dosdict, &name_DosSetupPrintBufferSize , &tmpref)) < 0)
    return code;
  
  push(1);
  op[0] = dosdict;
  return 0;
}

private int
  zgp_djgpp_setsystem(register os_ptr op)
{
  ref *pval;
  ref tmpref;
  int code;
  int tgt,wait,bufsize,iproc;
  
  check_type(*op,t_dictionary);
  check_dict_read(*op);
  
  if (dict_find(op,&name_DosSetupName,&pval) <= 0) {
    return_error(e_rangecheck);
  }
  if (!r_has_type(pval,t_name)) {
    check_type(*pval,t_string);
    name_from_string(pval,&tmpref);
    pval = &tmpref;
  }
  for ( iproc = 0; !name_eq(pval, &gp_system_names[iproc]); ) {
   if ( ++iproc == countof(gp_system_names) )
     return_error(e_rangecheck);
  }
  
  if (dict_find(op,&name_DosSetupPrintTarget,&pval) > 0) {
    if (!r_has_type(pval,t_name)) {
      check_type(*pval,t_string);
      name_from_string(pval,&tmpref);
      pval = &tmpref;
    }
    for ( tgt = 0; !name_eq(pval, &gp_target_names[tgt]); ) {
     if ( ++tgt == countof(gp_target_names) )
       return_error(e_rangecheck);
    }
  }
  else {
    tgt = SystemInfo[iproc]->target;
  }
  
  if ( (code = dict_int_param(op, &name_DosSetupPrintWait,0, 2048,
                 gp_printer_wait, &wait)) < 0 ||
       (code = dict_int_param(op, &name_DosSetupPrintBufferSize,64,0x7fff,
                 PRN_BUF_SIZE, &bufsize)) < 0
     )
    return code;
  
  gp_printer_flush(stdprn);
  gp_current_system.close_printer_proc(gp_current_target_type,gp_current_target_no);
  gp_current_system.free_buffer_proc(gp_printer_buf,gp_current_target_type);
  
  if ((code = gp_setup_system(iproc,tgt,bufsize,wait) < 0))
    return code;
  
  pop(1);
  return 0;
}

private int
  gp_setup_system(int n,int target,int bufsize,int wait)
{
  int code;
  
  gp_current_system = *SystemInfo[n];
  gp_current_system_index = n;
  gp_printer_wait = wait;
  gp_current_target_name = target;
  PRN_BUF_SIZE = bufsize;
  
  gp_current_target_type = gp_current_target_name;
  gp_current_target_no = 0;
  
  switch (gp_current_target_name) {
  case gp_printer_to_port1:
  case gp_printer_to_port2:
  case gp_printer_to_port3:
    gp_current_target_no = gp_current_target_type - gp_printer_to_port1;
    gp_current_target_type = gp_printer_to_port;
    break;
  case gp_printer_to_bios1:
  case gp_printer_to_bios2:
  case gp_printer_to_bios3:
    gp_current_target_no = gp_current_target_type - gp_printer_to_bios1;
    gp_current_target_type = gp_printer_to_bios;
    break;
  }
  
  if (gp_current_system.open_printer_proc(gp_current_target_type,gp_current_target_no)<0)
    return e_ioerror;
  switch (gp_current_target_type) {
  case gp_printer_to_port:
    gp_printer_current_flush_proc = gp_current_system.flush_port_proc;
    break;
  case gp_printer_to_bios:
    gp_printer_current_flush_proc = gp_current_system.flush_bios_proc;
    break;
  case gp_printer_to_stdprn:
    gp_printer_current_flush_proc = gp_default_flush;
    break;
  }
  gp_printer_buf =
    gp_current_system.alloc_buffer_proc(PRN_BUF_SIZE,gp_current_target_type);
  if (gp_printer_buf == NULL) return e_VMerror;
  
  code = gp_current_system.open_console_proc(0);
  if (code < 0) return code;
  
  return 0;
}

op_def gp_djgpp_op_defs[] = {
	{"0.currentmsdossystem", zgp_djgpp_currentsystem},
	{"1.setmsdossystem", zgp_djgpp_setsystem},
    {"0.identifymsdossystemauto", zgp_djgpp_identifysystemauto},
	op_def_end(gp_djgpp_op_init)
};

/****************************** Output Routines. *****************************/

private void
  gp_printer_slow(int w)
{
  int i,j;
  i = (w<6)?5:w;
  while(w-- > 0){
    for(j = 0; j < 6*i; j++) gp_printer_slow(0);
  }
}

private int
  gp_printer_device_flush(byte *d,int s)
{
  int code = gp_printer_current_flush_proc(d,s);
  if (!code) gp_printer_slow(gp_printer_wait);
  return code;
}

/************************** default procedures *************************/

private int
  gp_default_identify(int oem_no)
{  return 1;
}

private int
  gp_default_open_console(int mode)
{  return 0;
}

private int
  gp_default_close_console(int mode)
{  return 0;
}

private int
  gp_default_open_printer(int t,int n)
{  return 0;
}

private int
  gp_default_close_printer(int t,int n)
{  return 0;
}

private int
  gp_default_flush(byte *buf,int size)
{  return fwrite(buf,size,1,stdprn) == 1;
}

private byte *
  gp_default_alloc_buffer(int size,int target)
{ return malloc(size);
}

private void
  gp_default_free_buffer(byte *buf,int target)
{  free(buf);
}

/******************************* Pipe *********************************/

typedef struct pipe_stack_s pipe_stack;
struct pipe_stack_s {
  FILE *file;
  char *filename;
  char *command;
  int binary;
  pipe_stack *prev;
  pipe_stack *next;
};
private pipe_stack *gp_pipe_stack = NULL;

private FILE *
  gp_djgpp_popen(const char *fname,const char *mode)
{
  char fname_buf[128];
  const char *fname_prefix = "_pipe";
  pipe_stack *ppipe = malloc(sizeof(pipe_stack));
  
  if (ppipe == NULL) return NULL;
  ppipe->command = malloc(strlen(fname)+1);
  if (ppipe->command == NULL) {
    free(ppipe);
    return NULL;
  }
  strcpy(ppipe->command,fname);
  ppipe->file =  gp_open_scratch_file(fname_prefix,fname_buf,mode);
  if (ppipe->file == NULL) {
    free(ppipe->command);
    free(ppipe);
    return NULL;
  }
  ppipe->filename = malloc(strlen(fname_buf)+1);
  if (ppipe->filename == NULL) {
    fclose(ppipe->file);
    unlink(fname_buf);
    free(ppipe->command);
    free(ppipe);
    return NULL;
  }
  strcpy(ppipe->filename,fname_buf);
  ppipe->binary = strchr(mode,'b') ? 1 : 0;
  ppipe->prev = NULL;
  ppipe->next = gp_pipe_stack;
  gp_pipe_stack = ppipe;

  return ppipe->file;
}

private int
  gp_djgpp_pclose(FILE *pfile)
{
  pipe_stack *ppipe = gp_pipe_stack;
  
  fclose(pfile);
  while (ppipe != NULL) {
    if (pfile == ppipe->file) {
      pfile = fopen(ppipe->filename,ppipe->binary ? "rb" : "r");
      if (pfile) {
        int fn = dup(0);
        dup2(fileno(pfile),0);
        system(ppipe->command+1);
        dup2(fn,0);
      }
      unlink(ppipe->filename);
      free(ppipe->filename);
      free(ppipe->command);
      if (ppipe->prev == NULL) { /* top of list */
        gp_pipe_stack = ppipe->next;
      }
      else {
        ppipe->prev->next = ppipe->next;
      }
      if (ppipe->next != NULL) {
        ppipe->next->prev = ppipe->prev;
      }
      free(ppipe);
      return pfile ? 0 : EOF;
    }
    ppipe = ppipe->next;
  }
  return EOF;
}

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