/* Copyright (C) 1997 Aladdin Enterprises.  All rights reserved.
  
  This file is part of GNU Ghostscript.
  
  GNU 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 GNU General Public License for full details.
  
  Everyone is granted permission to copy, modify and redistribute GNU
  Ghostscript, but only under the conditions described in the GNU General
  Public License.  A copy of this license is supposed to have been given to
  you along with GNU 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.
  
  Aladdin Enterprises is not affiliated with the Free Software Foundation or
  the GNU Project.  GNU Ghostscript, as distributed by Aladdin Enterprises,
  does not depend on any other GNU software.

  However Display Ghostscript System depends on GLib. */

/* $Id: dgsagent.c,v 1.10 2000/06/22 15:09:12 cigas Exp $ */
/* Display Ghostscript agent code to handle dps connections */

#include <glib.h>		/* For Hash tables */

#include "gx.h"
#include "gsexit.h"
#include "iref.h"
#include "ialloc.h"		/* for interp.h */
#include "imain.h"
#include "imainarg.h"
#include "iminst.h"
#include "store.h"		/* for make_true */
#include "interp.h"		/* for initial_enter_name */

#include "igstate.h"		/* for igs */
#include "gxdevice.h"		/* for gx_device_common in gdevx.h */

/* the X people can't make their headers define what they need automatically... */
#define NEED_REPLIES
#define NEED_EVENTS
#include "x_.h"			/* standard X include files */
#include "string_.h"
#include "DPSCAP.h"

#include <unistd.h>
#include <signal.h>
#include <errno.h>
/*#include <stdlib.h>*/
#include "dgs.h"
#include "dgsagent.h"
#include "dgsmisc.h"
#include "dgsconn.h"
#include "dgsdps.h"

#include "gsdpsx.h"			/* for gsdpsx_get/set_offset */
#include "gdevx.h"
extern gx_device_X gs_x11_device;	/* in gdevx.c */

private gx_device_X * DGS_get_primary_device (void) ;
private void DGS_set_primary_device (gx_device_X * dev) ;

private void proc_agent_setup_signal(void);
private void proc_agent_control(int);
private gx_device_X * DGS_primary_device; /* used to be part of conn_info 
					     but only needed in agent_start
					     and ctx_init */
gsdpsx_conn_info conn_info;

/* --- (proc_) process switch functsions --- */
private Window proc_agent_setup_window(Display ** dpy, int agent_number);
private void DGS_free_x_fontmaps(x11fontmap **pmaps, gs_memory_t *mem);

#define RETURN(code) {gs_exit(code); return code;}

int
proc_agent_start(int in_sock,
		 int server_socket_unix,
		 int server_socket_tcp,
		 int gs_argc, char *gs_argv[],
		 int agent_number, enum conn_type type)
{
    int pid;
    int exit_code;
    ref error_object;
    int setup_result;

    if ((pid = fork()) < 0) {
	perror("fork");
	return -1;
    }

    if (pid == 0) {
      /** Child */
	const char *run_string = "(%stdin) run  ";
	const char *render_state =

	    "/setXgcdrawablecolor { pop setXgcdrawable } bind odef";
	const char *render_state2 =

	    "/currentXgcdrawablecolor { currentXgcdrawable [0 0 0 0 0 0 0 0 0 0 0 0 0] } bind odef";
	int len;		/* message length */
	int code;
	gs_main_instance *minst = gs_main_instance_default();

	/* Note - this call uses GS_OPTIONS, set in dgs.c */
	code = gs_main_init_with_args(minst, gs_argc, gs_argv);

	if (code < 0) {
	    gs_exit_with_code(255, code);
	}

	debug_print_pid(DGS_DEBUG);

	/** Shutdown some server resources */

	billboard_atom_needs_cleanup = 0;	/* atom_cleanup:  */
	server_win_tcp = 0;	/* destory_win_tcp: */
	server_win_unix = 0;	/* destory_win_unix: */
	conn_server_close(server_socket_tcp);	/* close_socket_tcp: */
	conn_server_close(server_socket_unix);	/* close_socket_unix: */
	/* close_display: No need */

	conn_info.conn_type = type;
	conn_info.sock = in_sock;
	conn_info.buf = 0;	/* value is checked in proc_agent_finalize */
	proc_agent_setup_signal();

	/* CONTEXT HACK */
	conn_info.cpsid_to_cxid_table = g_hash_table_new(NULL, NULL);
	conn_info.cxid_to_cpsid_table = g_hash_table_new(NULL, NULL);
	conn_info.cpsid_to_mask_table = g_hash_table_new(NULL, NULL);
	conn_info.cpsid_to_nextMask_table = g_hash_table_new(NULL, NULL);
	conn_info.context_state_index_primary =
	    gsdpsx_get_current_context_index();
	gsdpsx_set_primary_context_index(conn_info.
					 context_state_index_primary);
	conn_info.context_state_index_last =
	    conn_info.context_state_index_primary;
	ctx_init_parameter_initialize(&(conn_info.ctx_init_parameter));
	DGS_set_primary_device(NULL);
	conn_info.pause_cxids_set = NULL;
	conn_info.kill_cpsids_set = NULL;

      /** Agent own window */
	conn_info.agent_win =
	    proc_agent_setup_window(&shared_dpy, agent_number);
	if (-1 == conn_info.agent_win) {
	    conn_insock_close(conn_info.sock);
	    RETURN(-1);
	}
	conn_info.resume_atom =
	    XInternAtom(shared_dpy, DPSCAP_TYPE_RESUME, FALSE);

	conn_info.serial = 1;

	/* Set up buffer */
	conn_info.buf = buf_init();
	if (!(conn_info.buf)) {
	    proc_agent_finalize(-1);
	    RETURN(-1);
	}

	/* get the connection setup request */
      recv_again:
	len = buf_recv(conn_info.buf, &conn_info);
	if (len == -1) {
	    if (errno != EINTR) {
		perror("recv ");
		proc_agent_finalize(-1);
		RETURN(-1);
	    } else
		goto recv_again;
	}

	conn_info.byteorder = conn_info.buf->buf[0];
	setup_result = dpscap_ConnSetup(&conn_info, conn_info.buf->buf, len);
	if (setup_result <= 0) {
	    proc_agent_finalize(-1);
	    dprintf("Wrong byte order\n");
	    RETURN(-1);
	}

	/* Initialize rendering state */
	code = gs_main_run_string_with_length(minst, render_state,
					      strlen(render_state),
					      minst->user_errors,
					      &exit_code, &error_object);
	if (code < 0) {
	    dprintf("setXgcdrawablecolor - FAILED\n");
	    gs_exit_with_code(255, code);
	}
	code = gs_main_run_string_with_length(minst, render_state2,
					      strlen(render_state2),
					      minst->user_errors,
					      &exit_code, &error_object);
	if (code < 0) {
	    dprintf("currentXgcdrawablecolor - FAILED\n");
	    gs_exit_with_code(255, code);
	}

	/* Start the interpreter */

	gs_main_run_string_with_length(minst,
				       run_string,
				       strlen(run_string),
				       minst->user_errors,
				       &exit_code, &error_object);
	proc_agent_finalize(-1);
	RETURN(-1);
    } else {
      /** Parent */
	conn_insock_close(in_sock);
	server_agent_pids = g_set_add(server_agent_pids, (gpointer) pid);
	return 0;
    }
}
#undef RETURN

void
proc_agent_finalize(int sig)
{
  void * tmp_ptr;
  int tmp_sock;

    if (DGS_DEBUG) {
	const char *signame = util_signal_int2str(sig);

	fprintf(stderr, "proc_agent_finalize: %s\n", signame);
    }
    if (conn_info.buf) {
      tmp_ptr = conn_info.buf;
      conn_info.buf = NULL;
      buf_finalize(tmp_ptr);
    }
    if (conn_info.cpsid_to_cxid_table) {
      tmp_ptr = conn_info.cpsid_to_cxid_table;
      conn_info.cpsid_to_cxid_table = NULL;
      g_hash_table_destroy(tmp_ptr);
    }
    if (conn_info.cxid_to_cpsid_table) {
      tmp_ptr = conn_info.cxid_to_cpsid_table;
      conn_info.cxid_to_cpsid_table = NULL;
      g_hash_table_destroy(tmp_ptr);
    }
    if (conn_info.cpsid_to_mask_table) {
      tmp_ptr = conn_info.cpsid_to_mask_table;
      conn_info.cpsid_to_mask_table = NULL;
      g_hash_table_destroy(tmp_ptr);
    }
    if (conn_info.cpsid_to_nextMask_table) {
      tmp_ptr = conn_info.cpsid_to_nextMask_table;
      conn_info.cpsid_to_nextMask_table = NULL;
      g_hash_table_destroy(tmp_ptr);
    }
    /* TODO: Set should be destroyed */

    if (conn_info.sock) {
      tmp_sock = conn_info.sock;
      conn_info.sock = 0;
      conn_insock_close(tmp_sock);
    }
    
    /* TODO: Send connection shutdown info to client */
    if (sig == 0) {
	gx_device_X *xdev = (gx_device_X *) gs_currentdevice(igs);

	if (xdev && (xdev->dname == gs_x11_device.dname)) {
	  if (xdev->dingbat_fonts)
	    DGS_free_x_fontmaps(&xdev->dingbat_fonts, xdev->memory);
	  if (xdev->symbol_fonts)
	    DGS_free_x_fontmaps(&xdev->symbol_fonts, xdev->memory);
	  if (xdev->regular_fonts)
	    DGS_free_x_fontmaps(&xdev->regular_fonts, xdev->memory);
	}

	XDestroyWindow(shared_dpy, conn_info.agent_win);
	XCloseDisplay(shared_dpy);
    }
    gs_exit(0);
}

private void
proc_agent_setup_signal(void)
{
    signal(SIGHUP, proc_agent_finalize);
    signal(SIGINT, proc_agent_finalize);
    signal(SIGTERM, proc_agent_finalize);
    signal(SIGSEGV, proc_agent_finalize);
    signal(SIGUSR1, proc_agent_control);
}

/* print agent pids */

void
proc_agent_control_ppid_print(gpointer data, gpointer user_data)
{
    dprintf1("%d\n", (int)data);
}


private void proc_agent_control_ppid(void);
private void
proc_agent_control(int sig)
{
    proc_agent_control_ppid();
}

private void
proc_agent_control_ppid(void)
{
    dprintf1("server pid: %d\n", getppid());
}

/* I wonder why this function is needed. */
private Window
proc_agent_setup_window(Display ** dpy, int agent_number)
{
    int i;
    Window tmp_win = 0;

    /* What should I do?
       XSetCloseDownMode(*dpy, RetainTemporary);
       XCloseDisplay(*dpy); */
    
    *dpy = XOpenDisplay(NULL);
    if (!*dpy) {
	fprintf(stderr, "Cannot open X Display(agent): %s\nABORTING!\n",
		XDisplayName(NULL));
	return -1;
    }
    XNoOp(*dpy);

    for (i = 0; i <= agent_number; i++) {
	tmp_win = XCreateSimpleWindow(*dpy, DefaultRootWindow(*dpy), 0, 0, 1, 1,	/* min X window size */
				      0,	/* no border either */
				      BlackPixel(shared_dpy,
						 DefaultScreen(*dpy)),
				      WhitePixel(shared_dpy,
						 DefaultScreen(*dpy)));
	if (!tmp_win) {
	    fprintf(stderr, "Unable to create agent comm window.\n");
	    XCloseDisplay(*dpy);
	    return -1;
	}
    }
    if (!tmp_win) {
	fprintf(stderr, "Unable to create agent comm window.\n");
	XCloseDisplay(*dpy);
	return -1;
    }
    XSelectInput(*dpy, tmp_win, NoEventMask);
    return tmp_win;

}

/* ---------------------- DGS interface functions to GS ------------- */

Display *
DGS_get_current_display(void) 
{

    Display * d = NULL;
    if (NULL == DGS_get_primary_device ())
      {
	/* X device is not set yet. 
	   In other words, ctx_init_parameter_initialize is
	   not invoked yet. */
	d = shared_dpy;
      }
    else if (DGS_current_device_is_x11 ())
      {
	gx_device_X *xdev = (gx_device_X *) gs_currentdevice(igs);
	d = (xdev->dpy);
      }
    else
      dprintf("Cannot find device in DGS_get_current_display\n");
    return d;
}

bool
DGS_current_device_is_x11 (void) 
{
    gx_device_X *xdev = (gx_device_X *) gs_currentdevice(igs);
    return (xdev && (xdev->dname == gs_x11_device.dname));
}

/* --- context init parameter  --- */
void
ctx_init_parameter_initialize(struct ctx_init_parameter  * parameter)
{
  parameter->cxid = 0;
  parameter->drawable = 0;
  parameter->offset_x = 0;
  parameter->offset_y = 0;
}

#include "gsdpsx.h"
#include "gsmatrix.h"		/* for gscoord.h */
#include "gscoord.h"
#include "gspath.h"

int
ctx_init_parameter_install(struct ctx_init_parameter  * parameter)
{
  /* imemory, igs   */
  float xppp, yppp;
  gx_device_X *new_dev;
  gs_matrix *initial_matrix;
  int code;

  if (DGS_get_primary_device() == NULL)
    {
      code = gs_copydevice((gx_device**)&new_dev,
			   (gx_device*)&gs_x11_device,
			   imemory);
      DGS_set_primary_device(new_dev);
    }
  else
      code = gs_copydevice((gx_device**)&new_dev,
			   (gx_device*)DGS_get_primary_device(),
			   imemory);

  if (code < 0)
    {
      fprintf(stderr,"Failed to copy device\n");
      return code;
    }
  new_dev->owner = gs_x_dpsagent;
  new_dev->pwin  = conn_info.clientWindow;

  code = gs_setdevice(igs, (gx_device*)new_dev);
  if (code < 0)
    {
      fprintf(stderr,"Fail to set new device\n");
      return code;
    }

  gdev_x_set_drawable(new_dev, parameter->drawable);
  initial_matrix = &(new_dev->initial_matrix);
  xppp = new_dev->x_pixels_per_inch / 72.0;
  yppp = new_dev->y_pixels_per_inch / 72.0;
  initial_matrix->xx = xppp;
  initial_matrix->xy = 0.0;
  initial_matrix->yx = 0.0;
  initial_matrix->yy = -yppp;
  initial_matrix->tx = 0.0;
  initial_matrix->ty = 0.0;
  gsdpsx_set_offset((gx_device*)new_dev, parameter->offset_x, 0);
  gsdpsx_set_offset((gx_device*)new_dev, parameter->offset_y, 1);
  gs_initmatrix(igs);
  gs_initclip(igs);
  return 0;
}

/* Duplicate an X11 device. Used so each gstate can have its own device */
int
DGS_copy_device(gs_state * new_state, const gs_state * pgs)
{
  /* imemory, igs   */
  float xppp, yppp;
  gx_device_X *new_dev;
  gs_matrix *initial_matrix;
  int code;
  gx_device_X *xdev = (gx_device_X *)gs_currentdevice(pgs);

  if (!(xdev && (xdev->dname == gs_x11_device.dname))) 
    {
	if (0)
	    fprintf(stderr,"Not an X11 device\n");
	return 0;
    }

  if (DGS_get_primary_device() == NULL)
    {
	if (0)
	    fprintf(stderr,"DGS_copy_device: NULL device\n");
	return 0;
    }
  else
      code = gs_copydevice((gx_device**)&new_dev,
			   (gx_device*)DGS_get_primary_device(),
			   imemory);

  if (code < 0)
    {
      fprintf(stderr,"Failed to copy device\n");
      return code;
    }
  new_dev->owner = gs_x_dpsagent;
  new_dev->pwin  = xdev->pwin;

  /*code = gs_setdevice(new_state, (gx_device*)new_dev);*/
  code = gs_setdevice_no_init(new_state, (gx_device*)new_dev);
  if (code < 0)
    {
      fprintf(stderr,"Fail to set new device\n");
      return code;
    }

  gdev_x_set_drawable(new_dev, xdev->win);
  initial_matrix = &(new_dev->initial_matrix);
  xppp = new_dev->x_pixels_per_inch / 72.0;
  yppp = new_dev->y_pixels_per_inch / 72.0;
  initial_matrix->xx = xppp;
  initial_matrix->xy = 0.0;
  initial_matrix->yx = 0.0;
  initial_matrix->yy = -yppp;
  initial_matrix->tx = 0.0;
  initial_matrix->ty = 0.0;
/****
Margins[0] and [1] should have already been copied....
  gsdpsx_set_offset((gx_device*)new_dev, parameter->offset_x, 0);
  gsdpsx_set_offset((gx_device*)new_dev, parameter->offset_y, 1);
****/
/****
 Do we need these ???
****/
  gs_initmatrix(new_state);
  gs_initclip(new_state);
  return 0;
}

int
gsdpsx_get_offset(gx_device * dev, int x0_or_y1)
{
  gx_device_X *xdev = (gx_device_X *)dev;
  if (x0_or_y1 == 0)		/* X */
    return (int)(dev_x_offset(xdev) * xdev->x_pixels_per_inch);
  else				/* Y */
    return (int)(dev_y_offset(xdev) * xdev->y_pixels_per_inch);
}

void
gsdpsx_set_offset(gx_device * dev, int offset, int x0_or_y1)
{
  gx_device_X *xdev = (gx_device_X *)dev;
  if (x0_or_y1 == 0)		/* X */
    xdev->Margins[0] = (float)offset * 
      xdev->MarginsHWResolution[0] / xdev->x_pixels_per_inch; 
  else				/* Y */
    xdev->Margins[1] = (float)offset * 
      xdev->MarginsHWResolution[1] / xdev->y_pixels_per_inch; 
}

/* ---------------------- private DGS functions  ------------- */
private gx_device_X *
DGS_get_primary_device (void) 
{
    return DGS_primary_device;
}

private void
DGS_set_primary_device (gx_device_X * dev) 
{
    DGS_primary_device = dev;
}

private void
DGS_free_x_fontmaps(x11fontmap **pmaps, gs_memory_t *mem)
{
    while (*pmaps) {
	x11fontmap *font = *pmaps;

	*pmaps = font->next;
	if (font->std_names)
	    XFreeFontNames(font->std_names);
	if (font->iso_names)
	    XFreeFontNames(font->iso_names);
	gs_free(font->x11_name, sizeof(char), strlen(font->x11_name) + 1,
		"x11_font_x11name");
	gs_free(font->ps_name, sizeof(char), strlen(font->ps_name) + 1,
		"x11_font_psname");

	gs_free((char *)font, sizeof(x11fontmap), 1, "x11_fontmap");
    }
}
