/* "Whois" mini example
 * Eric P. Scott, San Francisco State University, October 1994
 * (thanks to Arman Khalili for many good suggestions)
 */

/* Ideas for future enhancement:
 * multiple sessions (split into Controller and Session classes)
 * nonblocking gethostbyname(), connect()
 */

/* Generated by Interface Builder */

#import "Controller.h"
#import "SelectableText.h"
#ifdef NX_COMPILER_RELEASE_3_0
#include <bsd/libc.h>
#include <bsd/netdb.h>
#include <bsd/errno.h>
#include <bsd/sys/types.h>
#include <bsd/sys/socket.h>
#include <bsd/netinet/in.h>
#include <bsd/arpa/inet.h>
#else
#include <libc.h>
#include <netdb.h>
#include <sys/errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#import <appkit/Application.h>
#import <appkit/ButtonCell.h>
#import <appkit/Matrix.h>
#import <appkit/ScrollView.h>
#import <appkit/Text.h>
#import <appkit/TextFieldCell.h>
#import <appkit/TextField.h>
#import <appkit/View.h>
#import <appkit/Window.h>
#import <dpsclient/dpsclient.h>

@implementation Controller

#ifndef NX_COMPILER_RELEASE_3_0
- setEditMenu:anObject
{
    editMenu = [anObject target];	// stupid IB
    return self;
}

- setOut:anObject
{
    out = [anObject docView];		// stupid IB
    return self;
}
#endif

// these tables perform a "best effort" conversion between
// "ISO Latin 1" and "NEXTSTEP Encoding"
//
// 0..127 are not changed
// in the upper half, 32 means that no equivalent exists

const unsigned char iso1tonse[]={
	  0,   1,   2,   3,   4,   5,   6,   7,
	  8,   9,  10,  11,  12,  13,  14,  15,
	 16,  17,  18,  19,  20,  21,  22,  23,
	 24,  25,  26,  27,  28,  29,  30,  31,
	 32,  33,  34,  35,  36,  37,  38,  39,
	 40,  41,  42,  43,  44,  45,  46,  47,
	 48,  49,  50,  51,  52,  53,  54,  55,
	 56,  57,  58,  59,  60,  61,  62,  63,
	 64,  65,  66,  67,  68,  69,  70,  71,
	 72,  73,  74,  75,  76,  77,  78,  79,
	 80,  81,  82,  83,  84,  85,  86,  87,
	 88,  89,  90,  91,  92,  93,  94,  95,
	 96,  97,  98,  99, 100, 101, 102, 103,
	104, 105, 106, 107, 108, 109, 110, 111,
	112, 113, 114, 115, 116, 117, 118, 119,
	120, 121, 122, 123, 124, 125, 126, 127,
	 32,  32,  32,  32,  32,  32,  32,  32,
	 32,  32,  32,  32,  32,  32,  32,  32,
	245, 193, 194, 195, 196, 197, 198, 199,
	200,  32, 202, 203,  32, 205, 206, 207,
	128, 161, 162, 163, 168, 165, 181, 167,
	200, 160, 227, 171, 190, 177, 176, 197,
	202, 209, 201, 204, 194, 157, 182, 180,
	203, 192, 235, 187, 210, 211, 212, 191,
	129, 130, 131, 132, 133, 134, 225, 135,
	136, 137, 138, 139, 140, 141, 142, 143,
	144, 145, 146, 147, 148, 149, 150, 158,
	233, 151, 152, 153, 154, 155, 156, 251,
	213, 214, 215, 216, 217, 218, 241, 219,
	220, 221, 222, 223, 224, 226, 228, 229,
	230, 231, 236, 237, 238, 239, 240, 159,
	249, 242, 243, 244, 246, 247, 252, 253
};

const unsigned char nsetoiso1[]={
	  0,   1,   2,   3,   4,   5,   6,   7,
	  8,   9,  10,  11,  12,  13,  14,  15,
	 16,  17,  18,  19,  20,  21,  22,  23,
	 24,  25,  26,  27,  28,  29,  30,  31,
	 32,  33,  34,  35,  36,  37,  38,  39,
	 40,  41,  42,  43,  44,  45,  46,  47,
	 48,  49,  50,  51,  52,  53,  54,  55,
	 56,  57,  58,  59,  60,  61,  62,  63,
	 64,  65,  66,  67,  68,  69,  70,  71,
	 72,  73,  74,  75,  76,  77,  78,  79,
	 80,  81,  82,  83,  84,  85,  86,  87,
	 88,  89,  90,  91,  92,  93,  94,  95,
	 96,  97,  98,  99, 100, 101, 102, 103,
	104, 105, 106, 107, 108, 109, 110, 111,
	112, 113, 114, 115, 116, 117, 118, 119,
	120, 121, 122, 123, 124, 125, 126, 127,
	160, 192, 193, 194, 195, 196, 197, 199,
	200, 201, 202, 203, 204, 205, 206, 207,
	208, 209, 210, 211, 212, 213, 214, 217,
	218, 219, 220, 221, 222, 181, 215, 247,
	169, 161, 162, 163,  32, 165,  32, 167,
	164,  32,  32, 171,  32,  32,  32,  32,
	174, 173,  32,  32, 183, 166, 182,  32,
	 32,  32,  32, 187,  32,  32, 172, 191,
	185, 145, 180, 147, 148, 175, 150, 151,
	168, 178, 176, 184, 179, 157, 158, 159,
	 32, 177, 188, 189, 190, 224, 225, 226,
	227, 228, 229, 231, 232, 233, 234, 235,
	236, 198, 237, 170, 238, 239, 240, 241,
	 32, 216,  32, 186, 242, 243, 244, 245,
	246, 230, 249, 250, 251, 144, 252, 253,
	 32, 248,  32, 223, 254, 255,  32,  32
};

static unsigned short stomp(unsigned short theChar, int flags,
    unsigned short charSet)
{
    if ((flags&NX_COMMANDMASK)==0) switch ([[[[NXApp keyWindow]
	firstResponder] delegate] tag]) {	// TextField's tag
    default:
	if (theChar==' ') return NX_TAB;
	if (theChar>127) return NX_ILLEGAL;
	break;
    case 2:
	if (theChar>127&&theChar<=255&&nsetoiso1[theChar]==32)
		return NX_ILLEGAL;
	break;
    }
    return NXFieldFilter(theChar, flags, charSet);
}

- appDidInit:sender
{
    [query setNextText:out];
    abortView=[abortButton superview];
    [abortView getBounds:&oldBounds];
    [abortButton removeFromSuperview];
    fd= -1;
    [query selectText:self];
    [window makeKeyAndOrderFront:self];
    [[window getFieldEditor:YES for:query] setCharFilter:stomp];

    return self;
}

- windowWillClose:sender
{
    [NXApp perform:@selector(terminate:) with:nil afterDelay:1
	cancelPrevious:NO];
    return self;
}

#ifndef NX_COMPILER_RELEASE_3_0
- windowWillResize:sender toSize:(NXSize *)frameSize
{
    if (frameSize->width<590.0) frameSize->width=590.0;
    if (frameSize->height<228.0) frameSize->height=228.0;
    return self;
}
#endif

- windowDidResize:sender
{
    if (![abortButton superview]) {
	[sender disableDisplay];
	[abortView addSubview:abortButton];
	[abortButton superviewSizeChanged:&oldBounds.size];
	[abortButton removeFromSuperview];
	[sender reenableDisplay];
    }
    [abortView getBounds:&oldBounds];
    return self;
}

- textDidChange:textObject
{
    switch ([[textObject delegate] tag]) {
    case 0:
	if ([hostMatrix selectedRow]!=2) [hostMatrix selectCellAt:2:0];
	break;
    case 1:
	if ([portMatrix selectedRow]!=2) [portMatrix selectCellAt:2:0];
	break;
    case 2:
	if (state==0&&[out textLength]>0) [out setText:""];
	break;
    default:
	break;
    }
    return self;
}

- quiet:sender
{
    id f;

    switch ([sender tag]) {
    case 0:
	f=[window firstResponder];
	if (f&&[f delegate]==otherHost) [query selectText:self];
	break;
    case 1:
	f=[window firstResponder];
	if (f&&[f delegate]==otherPort) [query selectText:self];
	break;
    default:
	break;
    }
    return self;
}

/* convert string to TCP port number, return in host order */
static unsigned short portfrom(const char *n, Text *t)
{
    register struct servent *se;
    int port, pos;
    char buf[256];

    pos=0;
    if (sscanf(n, "%d%n", &port, &pos)==1&&(pos==0||n[pos]=='\0')&&
	(unsigned)port<=65535) {
	if (port==0) [t setText:"bad port\n"];
	return port;
    }
    if (!(se=getservbyname((char *)n, "tcp"))) {
	(void)sprintf(buf, "%s/tcp: unknown service\n", n);
	[t setText:buf];
	return 0;
    }
    return(ntohs(se->s_port));
}

static inline void append(Text *out, const char *buf)
{
    register int e;

    if ((e=[out textLength])<=0) [out setText:buf];
    else [[out setSel:e:e] replaceSel:buf];
    [out scrollSelToVisible];
}

// state
//   0 nothing open
//   1 awaiting first data
//   2 awaiting more data
//   3 last char was CR

void suck(int s, void *userData)
{
    Controller *self;
    register unsigned char *p, *q, *r;
    register int c;
    NXRect fr;
    unsigned char buf[8192], buf2[8194];

    self=(Controller *)userData;

    /* read from socket, output anything we receive */
    /* convert NVT end-of-line convention to UNIX newline */
    c=read(s, buf, sizeof buf);
    if (c<=0) {
	if (self->state==3) {
	    buf2[0]='\r', buf2[1]='\0';
	    append(self->out, (const char *)buf2);
	}
    croak:
	DPSRemoveFD(s);
	(void)close(s);
	if (self->state==1) [self->query selectText:self];
	self->state=0;
	[self->abortButton getFrame:&fr];
	[self->abortButton removeFromSuperview];
	[self->abortView displayFromOpaqueAncestor:&fr:1:NO];
	return;
    }
    if (self->state==1) {
	if (buf[0]==255&&(c==1||buf[1]>=236)) {
	    [self->out setTextGray:NX_DKGRAY];
	    [self->out setText:"Try telnet instead"];
	    goto croak;
	}
	[self->out setText:""];
	self->state=2;
    }
    p=buf;
    q=buf2;
    r=p+c;
    while (p<r) {
	c= *p++;
	if (c=='\r') {
	    if (p>=r) {
		self->state=3;
		break;
	    }
	    c= *p++;
	    if (c!='\n') *q++='\r';
	    if (c=='\0') continue;
	}
	if (c!=0) *q++=iso1tonse[c];
    }
    *q='\0';
    append(self->out, (const char *)buf2);
    [self->query selectText:self];
}

/* try to establish session with given host; return on failure */
BOOL try(Controller *self, Text *t,
    struct sockaddr_in *sin, char *buf, int len)
{
    int s;
    char ebuf[256];

    [t setTextGray:NX_DKGRAY];
    (void)sprintf(ebuf, "Trying [%s]... ", inet_ntoa(sin->sin_addr));
    [t setText:ebuf];

    if ((s=socket(PF_INET, SOCK_STREAM, 0))<0) {
	[t setText:strerror(errno)];
	return NO;
    }
    self->fd=s;
    if (connect(s, (struct sockaddr *)sin, sizeof(*sin))<0) {
	[t setText:strerror(errno)];
	(void)close(s);
	return NO;
    }
    append(self->out, "OK\n");

    /* send one-line message to server */
    if (write(s, buf, len)<0) {
	[t setText:strerror(errno)];
	(void)close(s);
	return NO;
    }

    [t setTextGray:NX_BLACK];

    self->state=1;
    DPSAddFD(s, suck, (void *)self, NX_BASETHRESHOLD);
    [self->abortButton setEnabled:YES];
    return YES;
}

- doit:sender
{
    register struct hostent *h;
    register int i;
    register const char *p;
    register char *q;
    struct sockaddr_in sin;
    NXRect fr;
    char buf[1024];

    p=[query stringValue];
    q=buf;
    while (*p) {
	if (q>=&buf[sizeof buf-2]) break;
	if (*p=='\n') {
	    if (q>=&buf[sizeof buf-3]) break;
	    *q++='\r';
	}
	*q++=(char)(nsetoiso1[*(unsigned char *)p++]);
    }
    *q++='\r';
    *q++='\n';

    if (state!=0) {
	if (write(fd, buf, q-buf)<0) {
	    [out setTextGray:NX_DKGRAY];
	    [out setText:strerror(errno)];
	}
	return self;
    }
    [abortButton setEnabled:NO];
    [abortView addSubview:abortButton];
    [abortButton display];
    DPSFlush();

    bzero((char *)&sin, sizeof sin);
    p=[[hostMatrix selectedCell] title];
    if (p&&*p) p++;	// titles have leading space
    else p=[otherHost stringValue];
    sin.sin_addr.s_addr=inet_addr((char *)p);	/* try x.x.x.x format */
    if (sin.sin_addr.s_addr!=0xffffffff) {
	sin.sin_family=AF_INET;
	p=[[portMatrix selectedCell] title];
	if (p&&*p) p++;	// titles have leading space
	else p=[otherPort stringValue];
	if ((sin.sin_port=htons(portfrom(p, out)))!=0)
	    (void)try(self, out, &sin, buf, q-buf);
	else [otherPort selectText:self];
    }
    else {
	[out setTextGray:NX_DKGRAY];
	[out setText:"Resolving...\n"];
	DPSFlush();
	if (!(h=gethostbyname((char *)p))) {	/* name lookup */
	    switch (h_errno) {
	    case 1:
	    	[out setText:"Unknown host\n"];
		break;
	    case 2:
	    	[out setText:"Host name lookup failure\n"];
		break;
	    case 3:
	    	[out setText:"Unknown server error\n"];
		break;
	    case 4:
	    	[out setText:"No address associated with name\n"];
		break;
	    default:
		[out setText:"gethostbyname() failed\n"];
		break;
	    }
	    [otherHost selectText:self];
	}
	else {
	    sin.sin_family=h->h_addrtype;
	    p=[[portMatrix selectedCell] title];
	    if (p&&*p) p++;	// titles have leading space
	    else p=[otherPort stringValue];
	    if ((sin.sin_port=htons(portfrom(p, out)))!=0) {
		i=0; do {	/* some hosts have multiple IP addresses */
		    bcopy(h->h_addr_list[i], (char *)&sin.sin_addr,
			h->h_length);
		    if (try(self, out, &sin, buf, q-buf)) break;
		} while (h->h_addr_list[++i]);
	    }
	    else [otherPort selectText:self];
	}
    }
    if (state==0) {
	[abortButton getFrame:&fr];
	[abortButton removeFromSuperview];
	[abortView displayFromOpaqueAncestor:&fr:1:NO];
    }
    return self;
}

- undoit:sender
{
    NXRect fr;

    if (!sender) {
	[abortButton getFrame:&fr];
	[abortButton removeFromSuperview];
	[abortView displayFromOpaqueAncestor:&fr:1:NO];
    }
    else if (state>0) {
	DPSRemoveFD(fd);
	(void)close(fd);
	state=0;
	[self perform:@selector(undoit:) with:nil afterDelay:1
	    cancelPrevious:NO];
	[query selectText:self];
    }
    return self;
}

- reflectEditable:(BOOL)flag
{
    [editMenu disableFlushWindow];
    if (flag) {
        if (![editCut isEnabled]) [editCut setEnabled:YES];
        if (![editPaste isEnabled]) [editPaste setEnabled:YES];
        if (![editDelete isEnabled]) [editDelete setEnabled:YES];
    }
    else {
        if ([editCut isEnabled]) [editCut setEnabled:NO];
        if ([editPaste isEnabled]) [editPaste setEnabled:NO];
        if ([editDelete isEnabled]) [editDelete setEnabled:NO];
    }
    [[editMenu reenableFlushWindow] flushWindowIfNeeded];
    return self;
}


@end
