/*
 * Copyright (C) 1986 Free Software Foundation, Inc.
 * Copyright (C) 1991-1999 C.H.Lindsey, University of Manchester
 *
 * This file is derived from the program emacstool, which is itself part of the
 * GNU Emacs system.
 *
 * In the same way as GNU Emacs itself, this file 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.
 *
 * Everyone is granted permission to copy, modify and redistribute this file,
 * but only under the conditions described in the document "GNU Emacs copying
 * permission notice".   A copy of that document is distributed along with
 * GNU Emacs and other GNU Products so that you can know how you may
 * redistribute it all. In particular and among other things, this copyright
 * notice must be preserved on all copies of this file and of files derived
 * from it.
 *
 *
 * For Jove in SunView/Sun-Windows: (supported by Sun Unix) Insert a notifier
 * filter-function to convert all useful input to "key" sequences that jove
 * can understand.  See: Jovetool(1).
 *
 * Author (of Jovetool): C. H. Lindsey, University of Manchester
 * <chl@clw.cs.man.ac.uk>
 * Author (of Emacstool): Jeff Peck, Sun Microsystems, Inc. <peck@sun.com>
 *
 * Original Idea: Ian Batten
 *
 */

#include <suntool/sunview.h>
#include <suntool/tty.h>
#include <suntool/ttysw.h>
#include <stdio.h>
#include <sys/file.h>
#ifdef	__STDC__
#include <stdlib.h>
#endif
#include <string.h>

#include "exts.h"
#include "mousemsg.h"

#define BUFFER_SIZE 128		/* Size of all the buffers */

#define CLICK	400		/* max time in ms for click, or gap between
				 * double click */

/* define WANT_CAPS_LOCK to make f-key T1 (aka F1) behave as CapsLock */
/* #define WANT_CAPS_LOCK */

private const char mouse_prefix[] = "\030m";	/* ^x m */
private const char key_prefix[] = "\033[";	/* ESC-[ */

private char    jove_name[] = "jove";	/* default run command */
private char    buffer[BUFFER_SIZE];	/* send to ttysw_input */
private const char title[] = "Jovetool - ";	/* initial title */

private Frame   frame;		/* Base frame for system */
Tty             ttysw;		/* Where jove is */
private int     font_width, font_height;	/* For translating pixels to
						 * chars */

private FILE   *console = NULL;	/* for debugging: setenv DEBUGJOVETOOL */

private Icon    frame_icon;
/* make an icon_image */
private unsigned short default_image[] =
{
#include "jove.icon"
};
mpr_static_static(icon_image, 64, 64, 1, default_image);

private Cursor  current_cursor, jove_cursor, cut_cursor, copy_cursor, paste_cursor, default_cursor;

private short   cursor_image[] = {
#include "jove.cursor"
};
mpr_static_static(cursor_pix, 16, 16, 1, cursor_image);

private unsigned short cut_image[] = {
#include "cut.cursor"
};
mpr_static_static(cut_pix, 16, 16, 1, cut_image);

private unsigned short copy_image[] = {
#include "copy.cursor"
};
mpr_static_static(copy_pix, 16, 16, 1, copy_image);

private unsigned short paste_image[] = {
#include "paste.cursor"
};
mpr_static_static(paste_pix, 16, 16, 1, paste_image);



private void
set_cursor(cursor)
	Cursor          cursor;
{
	if (current_cursor != cursor) {
		window_set(ttysw, WIN_CURSOR, cursor, 0);
		current_cursor = cursor;
	}
}


/*
 * button_value: encode mouse event into a jovetool event code (the return
 * value) and a button state (stored in last_but).  If the event ought to be
 * ignored due to error, -1 is returned in place of the event code. If it is
 * to be ignored silently, -2 is returned.
 */
private int
                last_but = JT_UPEVENT,	/* button state */
                event_shift;	/* shift state */
private char
                last_event_string[7];	/* string from last F-key down event,
					 * 6 being the longest string
					 * expected from an F-key */

private int
button_value(event)
	Event          *event;
{
	static struct timeval
	                mouse_time = {0, 0},
	                prev_mouse_time;

	static int      multiclick_state = 0;

	int
	                this_but = event_shift,	/* button state, after this
						 * event */
	                mouse_code;	/* event code for this event */

	switch (event_id(event)) {
	case LOC_MOVEWHILEBUTDOWN:
		if (last_but & JT_UPEVENT)
			return -1;
		else if (multiclick_state)
			return -2;
		last_but |= JT_DRAGEVENT;
		{
			/*
			 * Generate event code for leftmost depressed button.
			 * Currently, only one bit will be on, so this is
			 * overkill.
			 */
			static short    DragCode[8] = {
				-1, JTEC_LEFT, JTEC_MIDDLE, JTEC_LEFT,
			JTEC_RIGHT, JTEC_LEFT, JTEC_MIDDLE, JTEC_LEFT};

			return DragCode[last_but & JT_BUTMASK] + JTEC_DRAG;
		}
	case MS_LEFT:
		this_but |= JT_LEFT;
		mouse_code = JTEC_LEFT;
		break;
	case MS_MIDDLE:
		this_but |= JT_MIDDLE;
		mouse_code = JTEC_MIDDLE;
		break;
	case MS_RIGHT:
		this_but |= JT_RIGHT;
		mouse_code = JTEC_RIGHT;
		break;
	default:
		return -1;
	}

	/*
	 * Clicking too fast can sometimes beat the system. Also, a user
	 * might manage to get two buttons down at the same time.  The
	 * following tests detect such situations. Note that up events after
	 * JT_RIGHT are absorbed by the menu software.
	 */
	if (event_is_up(event)) {
		/* ignore up not following a down or up of a different button */
		if ((last_but & JT_UPEVENT) != 0
		    || ((last_but ^ this_but) & JT_BUTMASK) != 0)
			return -1;
		this_but |= JT_UPEVENT;
		mouse_code += JTEC_UP;
	} else if (event_is_down(event)) {
		if ((last_but & (JT_UPEVENT | JT_RIGHT)) == 0) {
			/* down event not preceded by an up event */
			return -1;
		}
	} else {		/* drag event */
		if ((last_but & (JT_DOWNEVENT | JT_DRAGEVENT)) == 0) {
			/* drag event not preceded by a down or drag event */
			return -1;
		}
	}

	/*
	 * The only way to get back into sync is to supply whatever up or
	 * down event it was waiting for.  Thus jove should always see a
	 * consistent picture. ??? Good theory.  How is this implemented? --
	 * DHR
	 */

	/*
	 * Detect and encode multi-clicks.
	 *
	 * - a multiclick is a train of alternating mouse-down and mouse-up
	 * events - the first must be a mouse-down - Each mouse up and down
	 * must be of the same button - each of which happens within CLICK
	 * milliseconds of its predecessor - Drags are not taken into
	 * consideration.
	 */

	prev_mouse_time = mouse_time;
	mouse_time = event_time(event);

	multiclick_state += 1;
	if (((multiclick_state & 1) == 0) == ((this_but & JT_UPEVENT) == 0)
	    && ((last_but ^ this_but) & JT_BUTMASK) == 0
	    && (mouse_time.tv_sec - prev_mouse_time.tv_sec) * 1000
	 + (mouse_time.tv_usec - prev_mouse_time.tv_usec) / 1000 <= CLICK) {
		switch (multiclick_state) {
		case 1:	/* first up */
			break;
		case 2:	/* second down */
			mouse_code += JTEC_CLICK2;
			/* FALLTHROUGH */
		case 3:	/* second up */
			this_but |= JT_CLICK2;
			break;
		case 4:	/* third down */
			mouse_code += JTEC_CLICK3;
			/* FALLTHROUGH */
		case 5:	/* third up */
			this_but |= JT_CLICK3;
			break;
		default:	/* too many multi-clicks */
			multiclick_state = 0;
			break;
		}
	} else {
		multiclick_state = 0;
	}

	last_but = this_but;
	return mouse_code;
}



/*
 * Filter function to translate selected input events for jove Mouse button
 * events become ^XmC(button-state x-col y-line font-width) . - C is an
 * eventcode (explained in mousemsg.h) - button-state is as described in
 * mousemsg.h for this_but - x-col is horizontal column, in pixels (for
 * smooth scrollbar working) - y-col is height, in characters - font-width
 * allows conversion of x-col to characters Sun function keys (left, top, and
 * right) are converted to the ANSI sequence ESC [ P z, where P is a number
 * above 192
 */

private Notify_value
input_event_filter_function(window, event, arg, type)
	Window          window;
	Event          *event;
	Notify_arg      arg;
	Notify_event_type type;
{
#ifdef WANT_CAPS_LOCK
	static int      caps_lock = 0;	/* toggle indicater for f-key T1 caps
					 * lock */
#endif
	int             id = event_id(event);
	int             mouse_code = -3;	/* not -1 and not -2 and not
						 * >=0 */
	int             fkey;

	event_shift &= ~JT_CSMASK;
	if (event_shift_is_down(event))
		event_shift |= JT_SHIFT;
	if (event_ctrl_is_down(event))
		event_shift |= JT_CONTROL;
	if (event_action(event) == ACTION_PASTE || event_action(event) == ACTION_CUT) {
		if (event_is_up(event))
			event_shift &= ~(event_action(event) == ACTION_PASTE ?
					 JT_PASTE : JT_CUT);
		else
			event_shift |= (event_action(event) == ACTION_PASTE ?
					JT_PASTE : JT_CUT);
	}
	if (console != NULL) {
		fprintf(console, "Event %d", id);
		if (event_shift != 0)
			fprintf(console, "; shift %d", event_shift);
		if (event_is_up(event))
			fprintf(console, "; up");
		if (event_is_button(event))
			fprintf(console, "; button %d", id - BUT(0));
		fprintf(console, "\n");
	}
	switch (id) {
	case LOC_MOVEWHILEBUTDOWN:
		mouse_code = button_value(event);
		/* no need to set cursor */
		break;
	case MS_LEFT:
	case MS_MIDDLE:
	case MS_RIGHT:
		mouse_code = button_value(event);
		if (last_but & JT_PASTEMASK)
			last_event_string[0] = 0;
		/*
		 * We do not wish to see Left keys (esp. PASTE & CUT)
		 * associated with buttons
		 */
		/* FALLTHROUGH */
	case SHIFT_LEFT:
	case SHIFT_RIGHT:
	case SHIFT_CTRL:
		/* set cursor appropriately */
		if (event_shift == JT_SHIFT) {
			set_cursor(default_cursor);
		} else {
			switch (event_shift | (last_but & (JT_UPEVENT | JT_BUTMASK))) {
			case JT_SHIFT | JT_CONTROL | JT_MIDDLE:
				set_cursor(cut_cursor);
				break;
			case JT_CONTROL | JT_MIDDLE:
				set_cursor(copy_cursor);
				break;
			case JT_CONTROL | JT_LEFT:
				set_cursor(paste_cursor);
				break;
			default:
				set_cursor(jove_cursor);
				break;
			}
		}
		break;
	case KEY_LEFT(5):	/* EXPOSE key */
	case KEY_LEFT(7):	/* OPEN key */
		/*
		 * UP L5 & L7 is Expose & Open, let them pass to SunView. ???
		 * Apparently it only cares about up events.
		 */
		return event_is_up(event)
			? notify_next_event_func(window, event, arg, type)
			: NOTIFY_IGNORED;
	}

	/*
	 * Treat shifted events as normal Shelltool events. This includes
	 * shifted characters which is a bit naughty (xjove must deal with
	 * them separately because it needs to clear the shiftmask for
	 * non-characters).
	 */
	if (event_shift == JT_SHIFT) {
		/*
		 * NOTE: at least under SunOS4.0.3, the shelltool routine
		 * turns off LOC_MOVEWHILEBUTDOWN, so we must restore it.
		 */
		Notify_value    result = notify_next_event_func(window, event, arg, type);

		window_set(ttysw, WIN_CONSUME_PICK_EVENT, LOC_MOVEWHILEBUTDOWN, 0);
		return result;
	}
	/* do Mouse Button events */

	if (mouse_code >= 0) {
		int
		                this_x = event_x(event), this_y = event_y(event) / font_height;
		static int      last_x, last_y;

		if (last_but & JT_RIGHT) {
			/*
			 * ??? avoid passing up-event, if we catch it --
			 * would cause deadlock.
			 */
			if (last_but == JT_RIGHT)
				menu_show(main_menu, window, event, 0);
		} else {
			if (this_x == last_x && this_y == last_y) {
				if (id == LOC_MOVEWHILEBUTDOWN)
					return NOTIFY_IGNORED;
				sprintf(buffer, "%s%d(%d)\015",
					mouse_prefix, mouse_code, last_but);
			} else {
				sprintf(buffer, "%s%d(%d %d %d %d)\015",
					mouse_prefix, mouse_code, last_but,
					this_x, this_y, font_width);
				last_x = this_x;
				last_y = this_y;
			}
			ttysw_input(ttysw, buffer, strlen(buffer));
		}
		return NOTIFY_IGNORED;
	} else if (mouse_code == -1) {
		ttysw_output(ttysw, "\007", 1);	/* beep */
		return NOTIFY_IGNORED;
	} else if (mouse_code == -2) {
		return NOTIFY_IGNORED;
	}
	/* process event if it was a function key stroke */

	fkey = 0;

	/* In JoveTool, WIN_STOP is handled separately, as a synonym for L1. */
	if (id == WIN_STOP)
		fkey = 192;
	else if (event_is_key_left(event))
		fkey = id - KEY_LEFT(1) + 192;
	else if (event_is_key_right(event))
		fkey = id - KEY_RIGHT(1) + 208;
	else if (event_is_key_top(event))
		fkey = id - KEY_TOP(1) + 224;

	if (fkey != 0) {
		if (event_is_down(event) || id == WIN_STOP)
			sprintf(last_event_string, "%s%dz", key_prefix, fkey);
		/* test should be !event_button_is_down(event) */
		if (last_but & JT_UPEVENT) {
			/*
			 * we don't want any function keys (esp PASTE & CUT)
			 * seen when a button is down
			 */
			if (event_action(event) == ACTION_PASTE
			    || event_action(event) == ACTION_CUT
			    || id == WIN_STOP) {
				if (event_is_down(event)) {
					/*
					 * we see PASTE and CUT and WIN_STOP
					 * when they come up
					 */
					return NOTIFY_IGNORED;
				}
			} else if (event_is_up(event)) {
				/* we see other F-keys when they go down */
				return NOTIFY_IGNORED;
			}
#ifdef WANT_CAPS_LOCK
			/*
			 * set a toggle and relabel window so T1 can act like
			 * caps-lock
			 */
			if (id == KEY_TOP(1)) {
				/* make a frame label with and without CAPS */
				static const char Caps[] = "[CAPS] ";
				char           *p = (char *) window_get(frame, FRAME_LABEL);

				strcpy(buffer, Caps);
				strncpy(buffer + sizeof(Caps) - 1,
					strncmp(p, Caps, sizeof(Caps) - 1) == 0 ? p + sizeof(Caps) - 1 : p,
					BUFFERSIZE - (sizeof(Caps)));
				buffer[BUFFER_SIZE - 1] = '\0';
				caps_lock = !caps_lock;
				window_set(frame, FRAME_LABEL,
					   buffer + (caps_lock ? 0 : sizeof(Caps) - 1), 0);
				return NOTIFY_IGNORED;
			}
#endif
			ttysw_input(ttysw, last_event_string, strlen(last_event_string));
		}
		return NOTIFY_IGNORED;
	}
	/* Discard key-up ascii or meta events */
	/*
	 * ??? is this needed?  Will the someone down the line not do this?
	 * We aren't even catching ascii up-events.
	 */
	if ((event_is_ascii(event) || event_is_meta(event))
	    && event_is_up(event))
		return NOTIFY_IGNORED;

#ifdef WANT_CAPS_LOCK
	/* shift alpha chars to upper case if capslock is set */
	if (caps_lock && event_is_ascii(event) && isupper(id))
		event_set_id(event, tolower(id));
#endif

	if (event_is_ascii(event)) {
		/*
		 * Force bit 8 if meta key is down. But it will only be
		 * noticed by the controlled process if it does a stty
		 * -istrip, or equivalent.
		 */
		if (event_shiftmask(event) & META_SHIFT_MASK)
			event->ie_code |= META_FIRST;	/* ??? META_KEY_MASK not
							 * in SunView */
	}
	/* If we get here, pass event off to next handler */
	return notify_next_event_func(window, event, arg, type);
}


extern char    *getenv proto((const char *));	/* fudge -- must be in some
						 * header */

int
main(argc, argv)
	int             argc;
	char          **argv;
{
	int             error_code;	/* Error codes */
	int             forking;
	char           *debug;

	if ((debug = getenv("DEBUGJOVETOOL")) != NULL) {
		/*
		 * DEBUGJOVETOOL specifies the file or device to receive
		 * debugging output, if any.  The null name signifies stderr.
		 */
		console = *debug == '\0' ? stderr : fopen(debug, "w");
		forking = 0;
	} else {
		forking = isatty(0);
	}

	if (argc>1 && (!strcmp("-f", argv[1]) || !strcmp("-nf", argv[1]))) {
		char	**p = &argv[1];

		forking = strcmp("-f", *p) == 0;
		do ; while ((p[0] = p[1]) != NULL);
	}

	if (forking) {
		switch (fork()) {
		case -1:
			fprintf(stderr, "fork failed");
			exit(1);
			/* NOTREACHED */
		case 0:	/* the child */
			setpgrp(0, 0);	/* so it doesn't get killed when
					 * parent dies */
			break;
		default:	/* the parent */
			exit(0);
			/* NOTREACHED */
		}
	}

	putenv("IN_JOVETOOL=t");/* notify subprocess that it is in jovetool */
	putenv("TERMCAP=");
	putenv("TERM=sun");	/* the TTYSW will be a sun terminal to
				 * override these, try % jovetool -rc script */
	if ((argv[0] = getenv("JOVETOOL")) == NULL)	/* Set jove command name */
		argv[0] = jove_name;
	for (argc = 1; argv[argc]; argc++)	/* Use last one on line */
		if (!(strcmp("-rc", argv[argc]))) {	/* Override if -rc given */
			int             i = argc;
			argv[argc--] = 0;	/* kill the -rc argument */
			if (argv[i + 1]) {	/* move to argv[0] and
						 * squeeze the rest */
				argv[0] = argv[i + 1];
				for (; argv[i + 2]; (argv[i] = argv[i + 2], argv[++i] = 0));
			}
		}
	frame_icon = icon_create(ICON_LABEL, argv[0],
				 ICON_IMAGE, &icon_image,
				 0);


	strcpy(buffer, title);
	strncat(buffer, argv[0],/* append run command name */
		(BUFFER_SIZE - (strlen(buffer)) - (strlen(argv[0]))) - 1);

	/* Build a frame to run in */
	frame = window_create((Window) NULL, FRAME,
			      FRAME_LABEL, buffer,
			      FRAME_ICON, frame_icon,
			      FRAME_ARGC_PTR_ARGV, &argc, argv,
			      0);

	/* Create a tty with jove in it */
	ttysw = window_create(frame, TTY,
			      TTY_QUIT_ON_CHILD_DEATH, TRUE,
	/* TTY_BOLDSTYLE, 4, */
			      TTY_ARGV, argv,
			      0);

	window_set(ttysw,
		   WIN_CONSUME_PICK_EVENTS,
		   WIN_STOP,
		   WIN_MOUSE_BUTTONS, WIN_UP_EVENTS,
	/* LOC_WINENTER, LOC_WINEXIT, LOC_MOVE, */
		   LOC_MOVEWHILEBUTDOWN,
		   0,

		   WIN_CONSUME_KBD_EVENTS,
		   WIN_STOP, SHIFT_LEFT, SHIFT_RIGHT, SHIFT_CTRL,
		   WIN_ASCII_EVENTS,
		   WIN_LEFT_KEYS, WIN_TOP_KEYS, WIN_RIGHT_KEYS,
	/* WIN_UP_ASCII_EVENTS, */
		   0,

		   0);

	default_cursor = window_get(ttysw, WIN_CURSOR);
	jove_cursor = cursor_create(
				    CURSOR_IMAGE, &cursor_pix,
				    CURSOR_XHOT, 8,
				    CURSOR_YHOT, 8,
				    0);
	cut_cursor = cursor_create(
				   CURSOR_IMAGE, &cut_pix,
				   CURSOR_XHOT, 8,
				   CURSOR_YHOT, 8,
				   0);
	copy_cursor = cursor_create(
				    CURSOR_IMAGE, &copy_pix,
				    CURSOR_XHOT, 8,
				    CURSOR_YHOT, 8,
				    0);
	paste_cursor = cursor_create(
				     CURSOR_IMAGE, &paste_pix,
				     CURSOR_XHOT, 8,
				     CURSOR_YHOT, 8,
				     0);
	set_cursor(jove_cursor);

	font_height = (int) window_get(ttysw, WIN_ROW_HEIGHT);
	font_width = (int) window_get(ttysw, WIN_COLUMN_WIDTH);

	/* Interpose my event function */
	error_code = (int) notify_interpose_event_func
		(ttysw, input_event_filter_function, NOTIFY_SAFE);

	if (error_code != 0) {	/* Barf */
		fprintf(stderr, "notify_interpose_event_func got %d.\n", error_code);
		exit(1);
	}
	menu_init();
	window_main_loop(frame);/* And away we go */
	exit(0);		/* if this is ever reached */
	/* NOTREACHED */
}
