/*
 * Copyright 1990, John G. Myers
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 1, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#import <stdio.h>
#import <stdlib.h>
#import <libc.h>
#import <sys/ioctl.h>
#import <mach.h>
#import <appkit/defaults.h>
#import <appkit/Window.h>
#import <appkit/Text.h>
#import <appkit/Application.h>
#import <appkit/Pasteboard.h>
#import <dpsclient/dpsclient.h>
#import "EtermView.h"
#import "etermSupport.h"

extern char **environ;

#define MINWIDTH 10		/*  these are */
#define MINHEIGHT 5		/* the limits */
#define MAXWIDTH 300		/* imposed by */
#define MAXHEIGHT 300		/* gnu-emacs. */

extern void *new_display_rock(EtermView *theView);
extern void input_from_emacs(int fd, void *theDisplay);
extern void kc_init(void);
extern void obscure_cursor(void);
extern void fix_font(int width);
extern void set_font(void);
extern int kc_keyforcode(int code, int flags);

/*
 * write(2), except it will keep retrying upon partial success.
 */
static int write_safely(int fd, char *buf, int n)
{
    int i, start = 0;

    while (start < n) {
	i = write(fd, buf+start, n - start);
	if (i == -1) return i; /* Some error */
	start += i;
    }
    return n;
}

/*
 * Handle a connection to the event server socket
 */
static void connection_from_emacs(int fd, void *rock)
{
    EtermView *view = (EtermView *)rock;
    int newfd = accept_server_connection(fd);

    if (newfd == -1) return;

    if ([view eventChannel]) {
	close(newfd);
    }
    else {
	[view setEventChannel: fdopen(newfd, "w")];
    }
}

@implementation EtermView

+ initialize
{
    static NXDefaultsVector etermDefaults = {
	{"NXAutoLaunch", "NO" },
	{"NXFixedPitchFont", "Ohlfs" },
	{"NXFixedPitchFontSize", "10" },
	{"Columns", "80" },
	{"Rows", "24" },
	{"WinLocX", "205"},
	{"WinLocY", "85"},
	{"HideOnAutoLaunch", "NO" },
	{NULL}
    };

    NXRegisterDefaults("Emacs", etermDefaults);
    kc_init();
    
    return self;
}

+ new
{
    const char *fontname;
    Font *screenfont;
    NXCoord ascender, descender, lineHeight;
    int leading;
    int fontsize;
    float ffontWidth;

    self = [super new];
    [self setFlipped:YES];
    [self setAutosizing:NX_WIDTHSIZABLE|NX_HEIGHTSIZABLE];
    [self notifyAncestorWhenFrameChanged:YES];

    fontname = NXGetDefaultValue("Emacs", "NXFixedPitchFont");
    fontsize = atoi(NXGetDefaultValue("Emacs", "NXFixedPitchFontSize"));

    /* TODO - Check non-zero, etc. */

    displayFont = [Font newFont: fontname size:(float)fontsize];
    
    NXTextFontInfo( displayFont , &ascender, &descender, &lineHeight);

    /*
     * NXTextFontInfo appears to be broken for screen fonts, so wait
     * until now to get it.
     */
    screenfont = [displayFont screenFont];
    if (screenfont)
      displayFont = screenfont;

    leading = -0.0 * lineHeight;
    fontHeight = (int) (lineHeight + leading);
    ffontWidth =  ([displayFont getWidthOf : "  "] -
		   [displayFont getWidthOf : " "]);
    fontWidth = ffontWidth + 0.5;

    [displayFont set];
    if (screenfont) {
	set_font();
    }
    else {
	fix_font(fontWidth);
    }

    fontDescender = (int) (descender + leading);

    return self;
}

- (FILE *)eventChannel;
{
    return eventChannel;
}

- setEventChannel: (FILE *)fp;
{
    eventChannel = fp;
    return self;
}

/*
 * Constrain window so that the content view has an integral
 * width in terms of characters.
 */
- windowWillResize: sender toSize: (NXSize *)frameSize;
{
    NXRect frameRect, contentRect;
    int newwidth, newheight;
    int style = [window style];

    NXSetRect(&frameRect, 0.0, 0.0, frameSize->width, frameSize->height);
    [Window getContentRect:&contentRect forFrameRect:&frameRect style:style];

    newwidth = (int) (contentRect.size.width + fontWidth / 2 - BORDER_WIDTH*2);
    newheight = (int) (contentRect.size.height + fontHeight / 2 - BORDER_WIDTH*2);

    newwidth -= newwidth % fontWidth;
    newheight -= newheight % fontHeight;
    
    if (newwidth < MINWIDTH * fontWidth) newwidth = MINWIDTH * fontWidth;
    if (newwidth > MAXWIDTH * fontWidth) newwidth = MAXWIDTH * fontWidth;

    if (newheight < MINHEIGHT * fontHeight) newheight = MINHEIGHT * fontHeight;
    if (newheight > MAXHEIGHT * fontHeight) newheight = MAXHEIGHT * fontHeight;

    contentRect.size.width = (NXCoord) newwidth + BORDER_WIDTH*2;
    contentRect.size.height = (NXCoord) newheight + BORDER_WIDTH*2;
    [Window getFrameRect:&frameRect forContentRect:&contentRect style:style];

    frameSize->width = frameRect.size.width;
    frameSize->height = frameRect.size.height;

    return self;
}

/*
 * Convert size to number of characters in content view.
 * Inform child emacs process of our new size.
 */
- windowDidResize: sender
{
    NXRect frameRect, contentRect;
    int style = [window style];
    struct winsize winsize;

    [window getFrame:&frameRect];
    [Window getContentRect:&contentRect forFrameRect:&frameRect style:style];

    lines = (int) ((contentRect.size.height - BORDER_WIDTH*2) / fontHeight);
    cols =  (int) ((contentRect.size.width - BORDER_WIDTH*2) / fontWidth);
    
    winsize.ws_row = lines;
    winsize.ws_col = cols;
    winsize.ws_xpixel = (int) contentRect.size.width;
    winsize.ws_ypixel = (int) contentRect.size.height;
    ioctl (masterChannel, TIOCSWINSZ, &winsize);

    return self;
}

/*
 * Resize our parent window so that we have a given number of
 * characters in our content view.
 */
- sizeWindowTo: (int)newCols : (int)newLines
{
    NXRect contentRect, frameRect;

    cols = newCols;
    lines = newLines;
    [window sizeWindow: (NXCoord) (int) (newCols * fontWidth + BORDER_WIDTH*2) :
     			(NXCoord) (int) (newLines * fontHeight + BORDER_WIDTH*2)];
    return self;
}

/*
 * Start up the child emacs process, perhaps on a file
 */
- startEmacs: (char *)withFile
{
    int master, slave, ptynumber, portno;
    char **env;
    static char *args[] = {
	"emacs",
	NULL,
	NULL
      };
  
    /* Grab a pty/tty pair */
    create_channel (&master, &slave, &ptynumber);
    masterChannel = master;

    /* Create the server socket */
    eventServerSocket = create_server_socket(&portno);
    DPSAddFD(eventServerSocket, connection_from_emacs, (void *)self, NX_RUNMODALTHRESHOLD);

    /* Frob the environment */
    env = patch_env(environ, portno);
    
    /* Make sure the size is set correctly */
    [self windowDidResize: self];

    args[1] = withFile;

    /* Fork off the Emacs */
    fork_shell("/usr/bin/emacs", args, env, slave);
    (void) close(slave);

    DPSAddFD(master, input_from_emacs, new_display_rock(self), NX_RUNMODALTHRESHOLD);
    [window display];
    [window makeKeyAndOrderFront:self];
}

- (BOOL)acceptsFirstResponder
{
    return YES;
}

/*
 * Keyboard input is given to the child emacs process.
 */
- keyDown: (NXEvent *)theEvent;
{
    NXEvent eventbuf;
    char buf[1024];
    int i = 0;
    int code;
    int start=0, n;

    if (theEvent->flags & NX_NUMERICPADMASK &&
	theEvent->data.key.charCode >= 0xac &&
	theEvent->data.key.charCode <= 0xaf) {
	/* Arrow keys (left, up, right, down) */
	buf[i++] = "\002\020\006\016"[theEvent->data.key.charCode - 0xac];
    }
    else if (theEvent->flags & NX_NUMERICPADMASK &&
	     theEvent->data.key.charCode == 0x03) {
	/* Enter key */
	buf[i++] = '\r';
    }
    else if (theEvent->flags & NX_ALTERNATEMASK) {
	/* Handle ALT key as a META key */
	code = kc_keyforcode(theEvent->data.key.keyCode, theEvent->flags);
	if (code != -1) {
	    buf[i++] = code | 0x80;
	}
    }
    else buf[i++] = theEvent->data.key.charCode;

    /* Grab as many keypressed events as we can from a modal event loop */
    while (i < sizeof(buf) &&
	   (theEvent = [NXApp peekNextEvent:NX_ALLEVENTS into:&eventbuf]) &&
	   theEvent->type == NX_KEYDOWN &&
	   !(theEvent->flags & NX_COMMANDMASK)) {
	theEvent = [NXApp getNextEvent:NX_ALLEVENTS];

	if (theEvent->flags & NX_NUMERICPADMASK &&
	    theEvent->data.key.charCode >= 0xac &&
	    theEvent->data.key.charCode <= 0xaf) {
	    /* Arrow keys (left, up, right, down) */
	    buf[i++] = "\002\020\006\016"[theEvent->data.key.charCode - 0xac];
	}
	else if (theEvent->flags & NX_NUMERICPADMASK &&
		 theEvent->data.key.charCode == 0x03) {
	    /* Enter key */
	    buf[i++] = '\r';
	}
	else if (theEvent->flags & NX_ALTERNATEMASK) {
	    /* Handle ALT key as a META key */
	    code = kc_keyforcode(theEvent->data.key.keyCode, theEvent->flags);
	    if (code != -1) {
		buf[i++] = code | 0x80;
	    }
	}
	else buf[i++] = theEvent->data.key.charCode;
    }

    write_safely(masterChannel, buf, i);
    obscure_cursor();

    return self;
}

/*
 * Mouse input is converted to a character position and given to the child
 * emacs process, but only if the emacs has opened an event port.
 */
- mouseEvent: (NXEvent *)theEvent : (int) button;
{
    int x, y;
    char buf[35];

    if (!eventChannel) return self;

    [self convertPoint: &theEvent->location fromView:nil];
    x = (int)((theEvent->location.x - BORDER_WIDTH)/fontWidth);
    y = (int)((theEvent->location.y - BORDER_WIDTH)/fontHeight);

    if (x < 0) x = 0;
    if (y < 0) y = 0;
    if (x >= cols) x = cols - 1;
    if (y >= lines) y = lines - 1;

    sprintf(buf, "\030%c(%d %d %d 1)\r", 0,
	    (button +
	     ((theEvent->flags & NX_SHIFTMASK) ? 8 : 0) +
	     ((theEvent->flags & NX_CONTROLMASK) ? 16 : 0) +
	     ((theEvent->flags & NX_ALTERNATEMASK) ? 32 : 0)),
	    x, y);
    write_safely(masterChannel, buf, 2+strlen(buf+2));

    return self;
}

- mouseDown: (NXEvent *)theEvent;
{
    [self mouseEvent: theEvent: 1];
    return self;
}

- mouseUp: (NXEvent *)theEvent;
{
    [self mouseEvent: theEvent: 1+128];
    return self;
}

- rightMouseDown: (NXEvent *)theEvent;
{
    [self mouseEvent: theEvent: 4];
    return self;
}

- rightMouseUp: (NXEvent *)theEvent;
{
    [self mouseEvent: theEvent: 4+128];
    return self;
}

- (BOOL)emacsOn
/* Added by J. Gregory on 9-Oct-91. */
{
  if (eventChannel) {	/* I don't trust anyone */
    return YES;
  } else {
    return NO;
  }
}

- openFile:(const char *)path
/* Added by J. Gregory on 9-Oct-91. */
{
    if (!eventChannel) return self;
    fprintf(eventChannel, "(find-file \"%s\")\n", path);
    fflush(eventChannel);
    return self;
}
    
/*
 * Quit, cut, copy, paste, and undo messages are sent to the child's
 * event port.
 */
- quitEmacs
{
    if (!eventChannel) return nil;
    fprintf(eventChannel, "(event-quit)\n");
    fflush(eventChannel);
    return self;
}

- cut: sender;
{
    if (!eventChannel) return self;
    fprintf(eventChannel, "(event-cut)\n");
    fflush(eventChannel);
    return self;
}

- copy: sender;
{
    if (!eventChannel) return self;
    fprintf(eventChannel, "(event-copy)\n");
    fflush(eventChannel);
    return self;
}
    
- paste: sender;
{
    static id pasteboard;
    const char *const *p, *const *types;
    char *data;
    int len;

    if (!eventChannel) return self;

    if (!pasteboard) pasteboard = [Pasteboard new];
    types = [pasteboard types];
    for (p=types; *p && strcmp(*p, NXAsciiPboard); p++);

    if (*p &&
	 [pasteboard readType:NXAsciiPboard data:&data length:&len] &&
	 len) {
	int c;
	char *tosend=data, *quote, *backslash;

	fputs("(event-paste \"", eventChannel);

	/* Write out string, quoting quote, backslash, and newline */
	for (tosend = data; len--; tosend++) {
	    switch (c = *tosend) {
	    case '\n':
		fputs("\\n", eventChannel);
		break;
	    case '\\':
	    case '\"':
		putc('\\', eventChannel);
		/* FALL THROUGH */
	    default:
		putc(c, eventChannel);
		break;
	    }
	}
		  
	fputs("\")\n", eventChannel);
	fflush(eventChannel);

	vm_deallocate (task_self(), (vm_address_t)data, len);
    }

    return self;
}

- undo: sender;
{
    if (!eventChannel) return self;
    fprintf(eventChannel, "(event-undo)\n");
    fflush(eventChannel);
    return self;
}
    
- getDimensions: (int *)linesPtr : (int *) colsPtr
{
    *linesPtr = lines;
    *colsPtr = cols;
    return self;
}

- (Font *)getDisplayFont:
	(int *) heightPtr :
	(int *) widthPtr :
	(int *) descenderPtr
{
    *heightPtr = fontHeight;
    *widthPtr = fontWidth;
    *descenderPtr = fontDescender;
    return displayFont;
}

@end
