/* 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: dgsstrm.c,v 1.3 2000/04/23 15:40:44 masata-y Exp $ */
/* Display Ghostscript stream functions */


#define DUMP_UNHANDLED_MESSAGES 1

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

/* for the DPS/X11 streams */
#include "std.h"
#include "stream.h"
#include "gxiodev.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 "DPS/XDPSproto.h"
#include "DPSCAPproto.h"
#include "DPSCAP.h"
#include "DPS/XDPS.h"

#include <netinet/in.h>		/* for ntohs */

/* headers for select(2) */
#include <sys/time.h>

#include "dgs.h"
#include "dgsstrm.h"
#include "dgsdps.h"
#include "dgsmisc.h"
#include "dgsagent.h"
#include "dgsconn.h"
#include "gsdpsx.h"

/* These are hidden in interp.c - YUCK */
void ticks_left_set(int value);
void ticks_left_zero(void);
int ticks_left_decrement(void);
int ticks_left_value(void);
int *ticks_left_refer(void);

/* --- forward prototypes --- */

/* yield_op */
private const int yield_op_length = 12;
private const byte yield_op_data[] =
    { 129, 1, 12, 0, 131, 0, 255, 255, 115, 1, 0, 0, };

/* fork op */
private const int fork_op_length = 50;
private const byte fork_op_data[] = {
    129, 3, 50,
    0, 131, 0, 255, 255, 62, 1, 0,
    0, 137, 0, 2, 0, 24, 0, 0,
    0, 131, 0, 255, 255, 48, 1, 0,
    0, 5, 0, 6, 0, 40, 0, 0,
    0, 131, 0, 255, 255, 79, 1, 0,
    0, 37, 115, 116, 100, 105, 110
};

/* detach op */
private const int detach_op_length = 20;
private const byte detach_op_data[] = {
    129, 2, 20,
    0, 131, 0, 255, 255, 22, 1, 0,	/* currentcontext */
    0, 131, 0, 255, 255, 36, 1, 0,	/* detach */
    0
};

/* --- gsdpsx stream IO --- */
/* code to deal with DPS/X11 input streams! */

private int read_process_handle(gsdpsx_conn_info * conn,
				unsigned char *pmsg,
				int remaining, enum scheduling_action *);

private void read_process_const(stream_cursor_write * pw,
				const int length, const byte * data);

private void read_process_dump_unhandled(int used,
					 int len, unsigned char *cur);

private void read_process_input_forward(enum scheduling_action sched_act,
					gsdpsx_conn_buf * buf,
					stream_cursor_write * pw);

private int read_process_try(gsdpsx_conn_info * conn);

private int
gsdpsx_std_read_process(stream_state * st, stream_cursor_read * ignore_pr,
			stream_cursor_write * pw, bool last)
{
    gsdpsx_conn_info *conn = &conn_info;
    gsdpsx_conn_buf *buf = conn->buf;
    enum scheduling_action sched_act = SCHED_ACT_NOTHING;
    Display *curr_dpy;
    int len;

    int cpsid = gsdpsx_get_current_context_index();
    CONTEXTXID cxid =
	(CONTEXTXID) g_hash_table_lookup(conn->cpsid_to_cxid_table,
					 (void *)cpsid);
    int newest_cpsid = gsdpsx_get_newest_context_index();

    /* Context switing occured */
    /* why context switching happens?  */
    extern int reschedule_interval;
    int ticks_left_value(void);


    if (pw->limit - pw->ptr < fork_op_length) {
	/* Buffer is too small */
	if (DGS_DEBUG)
	    dprintf("Stream buffer is too small.\n");
	return 1;
    }

    if (cpsid == conn->context_state_index_primary
	&& conn->context_state_index_last !=
	conn->context_state_index_primary) {
	static int detach = 1;

	if (detach) {
	    sched_act = SCHED_ACT_DETACH;
	    detach = 0;
	} else
	    sched_act = SCHED_ACT_YIELD;
	goto forward;
    } else if (cpsid != conn->context_state_index_last) {
	dpscap_Notify_pause_check(&conn_info, 0, 0);
	if (conn->ctx_init_parameter.cxid != 0) {
	    /* Reason: fork */
	    /* Associate gs_interp_context_state_current<->cxid */
	    g_hash_table_insert(conn->cxid_to_cpsid_table,
				(void *)(conn->ctx_init_parameter.cxid),
				(void *)cpsid);
	    g_hash_table_insert(conn->cpsid_to_cxid_table,
				(void *)cpsid,
				(void *)(conn->ctx_init_parameter.cxid));
	    /* Status mask table */
	    g_hash_table_insert(conn->cpsid_to_mask_table,
				(void *)cpsid, (void *)0);
	    g_hash_table_insert(conn->cpsid_to_nextMask_table,
				(void *)cpsid, (void *)0);
	    /* Initialize context using ctx_init_parameter */
	    ctx_init_parameter_install(&(conn->ctx_init_parameter));
	    /* Answer to the client */
	    dps_CreateContext_reply(conn, (int)cpsid);
	    /* Destory parameter */
	    ctx_init_parameter_initialize(&(conn->ctx_init_parameter));
	    gsdpsx_clear_newest_context();
	    gsdpsx_request_context_index(0);
	    conn->context_state_index_last = cpsid;
	} else if (ticks_left_value() == (reschedule_interval - 1)) {
	  if (0)
	    g_message("+ ticks: %d cpsid: %d last: %d",
		      ticks_left_value(), cpsid,
		      conn->context_state_index_last);
	  gsdpsx_request_context_index(conn->context_state_index_last);
	  sched_act = SCHED_ACT_YIELD;
	  goto forward;
	} 
    } else if (newest_cpsid && (conn->ctx_init_parameter.cxid != 0)) {
	gsdpsx_request_context_index(newest_cpsid);
	sched_act = SCHED_ACT_YIELD;
	goto forward;
    } else if (g_set_member(conn->pause_cxids_set, (gpointer) cxid)) {
	int sync = 0;

	if (gsdpsx_count_ready_context() == 1)
	    sync = 1;
	if (!dpscap_Notify_pause_check(&conn_info, cxid, sync)) {
	    sched_act = SCHED_ACT_YIELD;
	    goto forward;
	}
    } else if (g_set_member(conn->kill_cpsids_set, (gpointer) cpsid)) {
	sched_act = SCHED_ACT_YIELD;
	conn->kill_cpsids_set = g_set_remove(conn->kill_cpsids_set,
					     (gpointer) cpsid);
	goto forward;
    } else {
	int ticks_left_value(void);

	if (0)
	    g_message("- ticks: %d cpsid: %d last: %d",
		      ticks_left_value(), cpsid,
		      conn->context_state_index_last);
    }
    if (0)
	g_message("cpsid: %d", cpsid);
    conn->context_state_index_last = cpsid;

    /* flush out any drawing commands in the queue */
    if ((curr_dpy = DGS_get_current_display()) && DGS_current_device_is_x11())
	XFlush(curr_dpy);

    while (sched_act == SCHED_ACT_NOTHING && !(buf->inputstart)) {
	unsigned char *cur;
	int used = 0;

	if (buf->len && !buf->isfrag) {
	    /* Data is available in the buffer */
	    len = buf->len;
	    cur = buf->curpos;
	} else {
	    /* Data is NOT available in the buffer 
	       => Get data via socket */
	    if (!read_process_try(conn)) {
		int next = 0;
		int steady = 1;

		if (!status_send_if_masked(conn, cpsid, PSNEEDSINPUT, next))
		    status_send_if_masked(conn, cpsid, PSNEEDSINPUT, steady);
	    }

	    len = buf_recv(buf, conn);
	    if (-1 == len)
		break;
	    if (0)
		dprintf1("actually got data block of %d bytes.\n", len);
	    len += buf->len;
	    cur = buf->buf;
	    if (!len)
		break;
	}

	/* Consume the data.
	   + If the data that should be consumed in the gs interpreter,
	   break the loop. 
	   + If the data is exhausted, 
	   break the loop to get new data via socket. */
	while (sched_act == SCHED_ACT_NOTHING && (!(buf->inputstart) && len)) {
	    used = read_process_handle(conn, cur, len, &sched_act);

	    /* if !used, then we had a controlled underflow, we need more data
	       if used < 0, then an error */
	    if (used <= 0)
		break;

	    cur += used;
	    len -= used;
	}

	/* error? */
	if (used < 0) {
	    dprintf1("error occured (used = %d).  Trying to continue.\n",
		     (int)used);
	    len = 0;
	}

	/* there was some leftover data, shuffle it around */
	if (len) {
	    if (!(buf->inputstart) && (sched_act == SCHED_ACT_NOTHING))
		buf_pack(buf, cur, len);
	    else {
		buf_move_cursor(buf, cur, len);
		buf->isfrag = 0;
		if (0)
		    dprintf1("Queing %d bytes of messages for next trip!\n",
			     len);
	    }
	} else			/* len == 0 */
	    buf_reset(buf);

	/* flush out any drawing commands in the queue */
	if ((curr_dpy = DGS_get_current_display()) && DGS_current_device_is_x11())
	    XFlush(curr_dpy);
    }				/* while data */

    if (sched_act == SCHED_ACT_NOTHING && (!(buf->inputstart))) {
	dprintf("returning EOFC\n");
	proc_agent_finalize(0);
	return EOFC;
    }

    /*  Copying socket buffer -> interpreter stream buffer */
  forward:
    if (sched_act == SCHED_ACT_NOTHING) {
	int next = 0;
	int steady = 1;

	if (!status_send_if_masked(conn, cpsid, PSRUNNING, next))
	    status_send_if_masked(conn, cpsid, PSRUNNING, steady);
    }
    read_process_input_forward(sched_act, buf, pw);
    return 1;			/* See strimpl.h */
    /* return (count == tocopy) ? 1 : 0; */
}

private int
read_process_handle(gsdpsx_conn_info * conn,
		    unsigned char *pmsg,
		    int remaining, enum scheduling_action *sched_act)
{
    int used = 0, dealt_with = 1;

    if (pmsg[0] == DPSXOPCODEBASE) {
	switch (pmsg[1]) {
	    case X_PSGiveInput:
		used = dps_GiveInput(conn, pmsg, remaining, sched_act);
		break;
	    case X_PSCreateContext:
		used = dps_CreateContext(conn, pmsg, remaining, sched_act);
		break;
	    case X_PSSetStatusMask:
		used = dps_SetStatusMask(conn, pmsg, remaining);
		break;
	    case X_PSNotifyContext:
		used = dps_NotifyContext(conn, pmsg, remaining, sched_act);
		break;
	    case X_PSDestroySpace:
		used = dps_DestroySpace(conn, pmsg, remaining);
		break;
	    case X_PSInit:
		used = dps_Init(conn, pmsg, remaining);
		break;
	    case X_PSCreateSpace:
		used = dps_CreateSpace(conn, pmsg, remaining);
		break;
	    case X_PSGetStatus:
		used = dps_GetStatus(conn, pmsg, remaining);
		break;
	    case X_PSReset:
		used = dps_Reset(conn, pmsg, remaining);
		break;
	    case X_PSCreateContextFromID:
		used = dps_CreateContextFromID(conn, pmsg, remaining);
		break;
	    case X_PSXIDFromContext:
		used = dps_XIDFromContext(conn, pmsg, remaining);
		break;
	    case X_PSContextFromXID:
		used = dps_ContextFromXID(conn, pmsg, remaining);
		break;
	    case X_PSCreateSecureContext:
		used = dps_CreateSecureContext(conn, pmsg, remaining);
		break;
	    case X_PSNotifyWhenReady:
		used = dps_NotifyWhenReady(conn, pmsg, remaining);
		break;
	    default:
		{
		    const char *name = util_x_ps_request_enum2str(pmsg[1]);

		    if (name == NULL)
			dprintf1("received unknown DPSX message %X.\n",
				 (unsigned)pmsg[1]);
		    else
			dprintf2("received unhandled DPSX message %X:%s.\n",
				 (unsigned)pmsg[1], name);
		    dealt_with = 0;
		}
	}
    } else if (pmsg[0] == DPSCAPOPCODEBASE) {
	switch (pmsg[1]) {
	    case X_CAPFlushAgent:
		used = dpscap_FlushAgent(conn, pmsg, remaining);
		break;
	    case X_CAPNotify:
		used = dpscap_Notify(conn, pmsg, remaining, sched_act);
		break;
	    case X_CAPSetArg:
		used = dpscap_SetArg(conn, pmsg, remaining);
		break;
	    default:
		{
		    const char *name = util_x_cap_request_enum2str(pmsg[1]);

		    if (name == NULL)
			dprintf1("received unknown DPSCAP message %X.\n",
				 (unsigned)pmsg[1]);
		    else
			dprintf2("received unhandled DPSCAP message %X:%s.\n",
				 (unsigned)pmsg[1], name);
		    dealt_with = 0;
		}
	}
    } else if (pmsg[0] == X_ChangeGC)
	used = dpsx_ChangeGC(conn, pmsg, remaining);
    else
	dealt_with = 0;

#if DUMP_UNHANDLED_MESSAGES
    if (!dealt_with) {
	used = (((xPSGiveInputReq *) pmsg)->length) * 4;	/* remove all data */
	/*! send error */
	dprintf1("got data of %d bytes!\n", remaining);
	read_process_dump_unhandled(used, remaining, pmsg);
    }				/* if (!dealt_with) */
#endif /* DUMP_UNHANDLED_MESSAGES */

    if (used > 0 && remaining - used < 0) {
	dprintf("ERROR: buffer underflow! something went haywire!\n");
	dprintf1("ERROR: used: %d\n", used);
	dprintf1("ERROR: remaining: %d\n", remaining);
	used = remaining;
    }

    return used;
}

private void
read_process_const(stream_cursor_write * pw, const int length,
		   const byte * data)
{
    byte *dist = (pw->ptr + 1);

    memcpy(dist, data, sizeof(byte) * length);
    pw->ptr += length;
}

private void
read_process_dump_unhandled(int used, int len, unsigned char *cur)
{
    int j, k;

    for (j = 0; j < used;) {
	for (k = 0; k < 16; k++) {
	    if ((j + k) >= len)
		dprintf("   ");
	    else
		dprintf1("%02X ", cur[j + k]);

	    if (k == 7)
		dprintf("- ");
	    else if (k == 3 || k == 11)
		dprintf(" ");
	}
	dprintf("    ");
	for (k = 0; k < 16; k++) {
	    unsigned char c = cur[j + k];

	    if ((j + k) < len)
		dprintf1("%c", (c >= 32 && c < 128) ? c : '.');

	    if (k == 7)
		dprintf("-");
	}
	dprintf("\n");
	j += 16;
    }
}

private void
read_process_input_forward(enum scheduling_action sched_act,
			   gsdpsx_conn_buf * buf, stream_cursor_write * pw)
{
DGS_stream_cursor_write local_pw;
local_pw.limit = pw->limit; local_pw.ptr = pw->ptr;

    switch (sched_act) {
	case SCHED_ACT_NOTHING:
	    /*buf_input_forward(buf, pw);*/
	    buf_input_forward(buf, &local_pw);
	    pw->ptr = local_pw.ptr;
	    break;
	case SCHED_ACT_FORK:
	    read_process_const(pw, fork_op_length, fork_op_data);
	    break;
	case SCHED_ACT_YIELD:
	    read_process_const(pw, yield_op_length, yield_op_data);
	    break;
	case SCHED_ACT_DETACH:
	    read_process_const(pw, detach_op_length, detach_op_data);
	    break;
	default:
	    dprintf("Wrong sched_act...abort\n");
	    dprintf("Function: read_process_input_forward\n");
	    read_process_const(pw, yield_op_length, yield_op_data);
    }
}

private int
read_process_try(gsdpsx_conn_info * conn)
{
    fd_set rfds;
    struct timeval tv;

    FD_ZERO(&rfds);
    FD_SET(conn->sock, &rfds);
    tv.tv_sec = 0;
    tv.tv_usec = 0;
    return select(conn->sock + 1, &rfds, NULL, NULL, &tv);
}

/* code to deal with DPS/X11 result streams! */
private int
gsdpsx_std_write_process(stream_state * st, stream_cursor_read * pr,
			 stream_cursor_write * ignore_pw, bool last)
{
    uint count = pr->limit - pr->ptr;
    stream *s = (stream *) st;
    gsdpsx_conn_info *conn = (gsdpsx_conn_info *) (s->file);
    static Atom outputAtom = 0;
    Display *curr_dpy;
    XEvent event;
    int cpsid = gsdpsx_get_current_context_index();
    CONTEXTXID cxid =
	(CONTEXTXID) g_hash_table_lookup(conn->cpsid_to_cxid_table,
					 (void *)cpsid);
    DPSCAPOutputEvent *out;

    if (!(curr_dpy = DGS_get_current_display()) || !DGS_current_device_is_x11())
	return -1;

    if (!outputAtom)
	outputAtom = XInternAtom(curr_dpy, DPSCAP_TYPE_PSOUTPUT_LEN, False);

    event.xclient.type = ClientMessage;
    event.xclient.serial = 0;
    event.xclient.send_event = True;
    event.xclient.message_type = outputAtom;
    event.xclient.format = 8;
    event.xclient.display = curr_dpy;
    event.xclient.window = conn->clientWindow;
    out = (DPSCAPOutputEvent *) event.xclient.data.l;

    if (util_byteorder_check(conn))
	out->cxid = htonl(cxid);
    else
	out->cxid = cxid;

    while (count != 0) {
	int towrite = min(count, DPSCAP_DATA_LEN);

	memcpy(out->data, pr->ptr + 1, towrite);
	out->data[DPSCAP_DATA_LEN] = towrite;
	if (0) {
	    char data[21];

	    memcpy(data, pr->ptr + 1, towrite);
	    data[towrite] = '\0';
	    dprintf2("sent %d bytes of results\n  ->%s<-\n", towrite, data);
	}
	if (0)
	    g_message("[%s]", "gsdpsx_std_write_process");
	XSendEvent(event.xclient.display, event.xclient.window, False, 0,
		   &event);
	pr->ptr += towrite;
	count -= towrite;
    }

    XSync(curr_dpy, False);
    return 0;
}

extern int iodev_stdin_open(P4(gx_io_device *, const char *, stream **,
			       gs_memory_t *));
private int
gsdpsx_stdin_open(gx_io_device * iodev, const char *access, stream ** ps,
		  gs_memory_t * mem)
{
    int code = iodev_stdin_open(iodev, access, ps, mem);
    stream *s = *ps;

    if (code < 0)
	return code;
    /* #error FLUSH THE QUEUE, maybe that will help
       #error also check that we need all the below */
    s->procs.process = gsdpsx_std_read_process;
    s->procs.seek = s_std_noseek;
    s->procs.available = s_std_noavailable;
    s->procs.switch_mode = s_std_switch_mode;
    s->procs.close = s_std_close;
    s->procs.flush = s_std_read_flush;
    s->file = (FILE *) (&conn_info);
				 /***** GET RIGHT INFO! *****/
    return 0;
}

extern int iodev_stderr_open(P4(gx_io_device *, const char *, stream **,
				gs_memory_t *));
private int
gsdpsx_stderr_open(gx_io_device * iodev, const char *access, stream ** ps,
		   gs_memory_t * mem)
{
    int code = iodev_stderr_open(iodev, access, ps, mem);
    stream *s = *ps;

    if (code < 0)
	return code;
    s->procs.process = gsdpsx_std_write_process;
    s->procs.seek = s_std_noseek;
    s->procs.available = s_std_noavailable;
    s->procs.switch_mode = s_std_switch_mode;
    s->procs.close = s_std_close;
    s->procs.flush = s_std_write_flush;
    s->file = (FILE *) (&conn_info);
				 /***** GET RIGHT INFO! *****/
    return 0;
}

extern int iodev_stdout_open(P4(gx_io_device *, const char *, stream **,
				gs_memory_t *));
private int
gsdpsx_stdout_open(gx_io_device * iodev, const char *access, stream ** ps,
		   gs_memory_t * mem)
{
    int code = iodev_stdout_open(iodev, access, ps, mem);
    stream *s = *ps;

    if (code < 0)
	return code;
    s->procs.process = gsdpsx_std_write_process;
    s->procs.seek = s_std_noseek;
    s->procs.available = s_std_noavailable;
    s->procs.switch_mode = s_std_switch_mode;
    s->procs.close = s_std_close;
    s->procs.flush = s_std_write_flush;
    s->file = (FILE *) (&conn_info);
				/***** GET RIGHT INFO! *****/
    return 0;
}

/* Patch stdout/err to use our stream. */
void
gsdpsx_std_init(void)
{
    gs_findiodevice((const byte *)"%stdout", 7)->procs.open_device =
	gsdpsx_stdout_open;

    gs_findiodevice((const byte *)"%stderr", 7)->procs.open_device =
	gsdpsx_stderr_open;

    gs_findiodevice((const byte *)"%stdin", 6)->procs.open_device =
	gsdpsx_stdin_open;
}
