/*
 * Object to peck on a remote network weather service.
 *
 * Michael Hawley
 * MIT Media Laboratory
 * 20 Ames Street
 * Cambridge, MA 02139
 * mike@media-lab.mit.edu
 * Copyright (c) 1991 MIT Media Laboratory
 * Burn before reading!  This means you!!
 *
 * Please forward any comments/revisions.
 * Keep in mind that this was a one-day knock off;
 * much of the complexity is due to the fact that
 * we have to parse a telnet conversation that was
 * never intended for such.
 */

#import "Storm.h"
#import "Process.h"
#import <appkit/Form.h>
#import <appkit/Font.h>
#import <appkit/Text.h>
#import <appkit/TextField.h>
#import <appkit/Button.h>
#import <appkit/PopUpList.h>
#import <appkit/ScrollView.h>
#import <appkit/Matrix.h>
#import <appkit/NXBrowser.h>
#import <appkit/Speaker.h>
#import <appkit/Listener.h>
#import "MyMenuCell.h"
#import "Defaults.h"
#import "License.h"

@implementation Storm

static int blank(char *s){
    while (*s && (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')) s++;
    return *s? 0 : 1;
}

int StormDebug = 1;
id _status;
extern int Verbose;

char *
path(char *s)
/*
 * if 's' is in [NXArgv[0]], return [NXArgv[0]]/s.
 * (for looking up internal commands and files in .../Opener.app/...).
 */
{
    char t[1024], *q, *r;
    static char p[1024];
    extern char **NXArgv;
    *t = '\0';
    sscanf(s,"%s",t);
    if (!*t || *t=='(') return s;
    r = s + strlen(t);
    strcpy(p,*NXArgv);
    if (q=rindex(p,'/'))
        strcpy(q+1,t);
    else
        strcpy(p,t);
    if (access(p,0)==0){
        strcpy(p+strlen(p),r);
        return p;
    }
    return s;
}

static void say(s, a,b,c,d) id s; char *a,*b,*c,*d; {
    char p[1024];
    sprintf(p,a,b,c,d);
    [s setStringValue:p];
    [s display];
    NXPing();
}

void message(a,b,c,d) char *a,*b,*c,*d; {
    char p[1024];
    sprintf(p,a,b,c,d);
    [_status setStringValue:p];
    [_status display];
    NXPing();
}

static int eq(char *a, char *b){ return strncmp(a,b,strlen(b))==0; }
static int eqtail(char *s, char *t){ /* true if t appears at end of s */
    int tn = strlen(t), sn = strlen(s);
    if (tn > sn) return 0;
    return strcmp(s+(sn-tn), t)==0;
}
static int streq(char *a, char *b){ return strcmp(a,b)==0; }
static char *strindex(char *s, char *t){
    int n = strlen(t);
    if (s) while (*s)
	if (!strncmp(s, t, n))
	    return s;
	else
	    s++;
    return (char *)0;
}
static int match(char *a, char *b){
    return eq(a,b) || strindex(a,b) || strindex(b,a);
}
static char *stripnl(char *s){
    char *p;
    for (p=s;*p;p++) if (*p == '\n' || *p == '\r') *p = '\0';
    return s;
}

id _P;

static void put(P, fmt, a, b, c, d) id P; char *fmt, *a, *b, *c, *d; {
    char p[1024];
    sprintf(p,fmt,a,b,c,d);
    if (Verbose) printf("+ %s\n",p);
    [P puts:p]; 
}

void Put(fmt, a, b, c, d) char *fmt, *a, *b, *c, *d; {
    char p[1024];
    sprintf(p,fmt,a,b,c,d);
    if (Verbose) printf("+ %s\n",p);
    if (_P) [_P puts:p]; 
}


static char *
get(P, s, n) id P; char *s; int n; {
    [P gets:s:n];
    if (*s && Verbose) printf("> %s",s);
    return *s? s : (char *)0; 
}

char *
pgets(s,n) char *s; int n; {
    [_P gets:s:n];
    return *s? s : (char *)0; 
}

static char *
flush(P,s) id P; char *s; {
    static char t[1024];
    while (get(P,t,sizeof t))
        if (strindex(t,s)) return t;
    sleep(1);
    while (get(P,t,sizeof t))
        if (strindex(t,s)) return t;
    return (char *)0;
}

char *
Flush(s) char *s; {
    static char t[1024];
    if (_P) return;
    while (get(_P,t,sizeof t))
        if (strindex(t,s)) return t;
    sleep(1);
    while (get(_P,t,sizeof t))
        if (strindex(t,s)) return t;
    return (char *)0;
}

- enable:(BOOL)b {
    [attachB setIcon:b? "plug" : "unplug"];
    return self;
}

static id _text, _scroll;

- initMatrix {
    // from the nib file, we have the help text; keep a pointer to it.
    _text = helpText = [scrollView docView];
    [_text setDelegate:self];
    [_text setMarginLeft:6.0 right:6.0 top:0.0 bottom:0.0];
    [_text notifyAncestorWhenFrameChanged:YES];
    return self;
}

- setScrollView:anObject {
    _scroll = scrollView = anObject;
    [self initMatrix];
    return self;
}

extern char DefaultFont[];
extern char DefaultFontSize[];
static id fid = (id)0;
static int canconvert = 0;
double atof();

- textWillConvert:textObject fromFont:from toFont:to {
    if (canconvert && strcmp(DefaultFont,[from name])==0 &&
	atof(DefaultFontSize) == [from pointSize]){
	strcpy(DefaultFont,[to name]);
	sprintf(DefaultFontSize,"%f",[to pointSize]);
	fid = to;
	writeDefaults();
    }
    return to;
}

void
setText(s) char *s; {
    static NXPoint origin = {0.0,0.0};
    id w = [_scroll window];

    if (!fid){
        fid = [Font newFont:DefaultFont size:atof(DefaultFontSize)];
    }
    canconvert=0;
    [w disableFlushWindow];
    [_text setText:s];
    [_text setSel:0:999999];
    [_text setSelGray:0.0];
    [_text setSelFont:fid];
    [_text selectNull];
    [_text scrollPoint:&origin];
    [_scroll display];
    [[w reenableFlushWindow] flushWindow];
    NXPing();
    canconvert=1;
}

void openFile(char *s){ /* open 's' in workspace */
    int ok = 0;
    id p = [NXApp appSpeaker];
    char t[512];

    sprintf(t,"\nThe file\n        %s\nwill open in the workspace.",s);
    setText(t);
    [p setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
    [p openFile:s ok:&ok];
    if (!ok) sprintf(t,"\nCouldn't open: %s",s), setText(t);
}

isRTF(s) char *s; {
    int f = open(s,0);
    if (f > 0){
        char t[128];
        int n = read(f,t,sizeof(t));
        close(f);
        if (n <= 0) return 0;
        return strncmp(t,"{\\rtf",5)==0;
    } else
        return 0;
}

void
setFile(s) char *s; {
    char *p = path(s);
    NXStream *f = NXMapFile(p,NX_READONLY);
    static NXPoint origin = {0.0,0.0};
    id w = [_scroll window];

    canconvert=0;
    if (!f) return ;
    [w disableFlushWindow];

    if (isRTF(p)){
	[_text setMonoFont:NO];
	[_text readRichText:f];
    } else {
	[_text readText:f];
        [_text setSel:0:999999];
        [_text setSelGray:0.0];
	if (!fid)
	    fid = [Font newFont:DefaultFont size:atof(DefaultFontSize)];
        [_text setSelFont:fid];
        [_text selectNull];
        canconvert=1;
    }
    NXCloseMemory(f,NX_FREEBUFFER);
    [_text scrollPoint:&origin];
    [_scroll display];
    [[w reenableFlushWindow] flushWindow];
}

- showHelp:sender {
    setFile("info");
    return self;
}

#define Case break; case

static char *prompt = "archie>";

squishRtn(char *s){
    char *p = s;

    while (*p = *s++)
        if (*p != '\r') ++p;
}

static char *
getLine(char *s, char *t){ /* read ...\n from s into t; return new s */
    char *p = t;
    *t = '\0';
    squishRtn(s);
    while (*s && *s != '\n') *p++ = *s++;
    if (*s == '\n') *p++ = *s++;
    *p++ = '\0';
    if (*t == '\0') return (char *)0;
    if (Verbose) printf("- %s",t);
    return s;
}

- processOutput:(char *)s {
    static char prev[8192]="";
    char t[1024], *p=prev;

    strcat(prev,s); s = t;
    while ((p=getLine(p,s)) && 
           (index(s,'\n') || strcmp(s+strlen(s)-2,": ")==0 ||
                             strcmp(s+strlen(s)-1,":")==0))
        execState(0,s);
    strcpy(prev,blank(s)?s:"");
    return self;
}

static id me;

Command(s) char *s; {
    if (!_P) {
        substr(s,"$Site",Site);
        _P = [Process new:s delegate:me];
    }
}

- click:sender {
    id c =  [[browser matrixInColumn:[browser lastColumn]] selectedCell];
    if ([c isLeaf])
        [c click:self];
    return self;
}

- click2:sender {
    return self;
}

ensurelogin(){
    [me ensurelogin];
    return _P? 1 : 0;
}

- ensurelogin {
    //if (!_P) [self login:self];
    return self;
}

- login:sender {
    if (_P) [_P free]; P = _P = (id)0;
    _status = status;
    [self setFetch];
    ReadState(path("menus"));
    [browser setDelegate:self];
    [browser setCellPrototype:[[MyMenuCell alloc] init]];
    [browser loadColumnZero];
    [browser setAction:@selector(click:)];
    [browser setDoubleAction:@selector(click2:)];
    [browser setTarget:self];
    [attachB setIcon:"plug"];
    runState("");
    return self;
}

void
logout(){
    [me logout:me];
}

- logout:sender {
    if (P && _P) setState("Detach"), runState("");
    if (P) [P terminate:self];
    _P = P = (id)0;
    [self enable:NO];
    [attachB setIcon:"unplug"];
    message("not connected.");
    return self;
}

extern int sleep();

- attach:sender {
    if (eq((char *)[sender icon],"unplug")){
        [self login:sender];
    } else {
        [self logout:sender];
    }
    return self;
}

/*
 * the buttons and commands to run at startup time.
 */
id Fetch[NumFetch];

int FetchSelected = -1;

- setFetch {
    int i;
    Fetch[0] = fetch1;
    Fetch[1] = fetch2;
    Fetch[2] = fetch3;
    for (i=0;i<NumFetch;i++) [self setFetchText:i:FetchText[i]];
    return self;
}

- setFetchText:(int)i:(char *)s {
    [Fetch[i] setStringValue:s];
    strcpy(FetchText[i],s);
    [Fetch[i] display];
    return self;
}

- setFetchText:(char *)s {
    if (FetchSelected >= 0)
        [self setFetchText:FetchSelected:s];
    return self;
}

void
setFetchText(s) char *s; {
    [me setFetchText:s];
}

- setFetch:(int)i:(int)state {
    [Fetch[i] setBackgroundGray:state?1.0:.666];
    if (state) [self setFetchText:i:""];
    else [Fetch[i] display];
    return self;
}

- hitRadio:sender {
    int i;
    int n = [sender selectedRow], state = [[sender selectedCell] state];
    [self setFetch:n:state];
    FetchSelected = state? n : -1;
    for (i=0; i<NumFetch; i++){
        if (i != n || !state){
            [[sender cellAt:i:0] setState:0];
            [self setFetch:i:0];
        }
    }
    [sender display];
    return self;
}

fetchReports() {
    int i;
    char buf[80000], *p = buf;
    while (pgets(buf,1024) && !strindex(buf,curMenuLast())) ;
    *buf = '\0';
    for (i=0;i<NumFetch;i++){
       if (!blank(FetchText[i])){
           message("%s. . .",FetchText[i]);
           fetchReport(FetchText[i],p);
           if (p[0]){
               p += strlen(p);
               if (i < (NumFetch-1))
                   strcpy(p,"\n===============================\n");
               p += strlen(p);
           }
       }
    }
    squishRtn(buf);
    squishwhite(buf);
    if (*buf) setText(buf);
    message("connected!");
}

- setSite:thing {
    openDefaults();
    site = thing;
    [site setTitle:Site];
    return self;
}

- hitSite:sender {
    char *s = (char *)[[sender selectedCell] title];
    strcpy(Site,s);
    writeDefaults();
    return self;
}

/*
 * telnet:
 */
#import <appkit/Pasteboard.h>
#import <appkit/Speaker.h>
#import <appkit/Listener.h>
- copyString:(char *)s {
  id p = [Pasteboard new];
  [p declareTypes:&NXAsciiPboard num:1 owner:self];
  [p writeType:NXAsciiPboard data:s length:strlen(s)];
  return self;
}

- launchTerminal:(char *)program {
  id p = [NXApp appSpeaker];
  port_t t = NXPortFromName("Terminal",NULL);
  int ok;
  if (t==PORT_NULL) return self;
  [p setSendPort:t];
  [self copyString:program];
  (void)[p msgPaste:&ok];
  return self;
}

- telnet:sender {
    char p[1024];
    sprintf(p,"telnet %s 3000\n",Site);
    [self launchTerminal:p];
    return self;
}

- sendComments:sender {
    return self;
}

#import <appkit/PrintInfo.h>

- print:sender {
    static int p = 0;
    if (!p){
        p++;
        [[NXApp printInfo] setScalingFactor:.9];
    }
    return [_text printPSCode:sender];
}

- pageLayout:sender {
    return [NXApp runPageLayout:sender];
}

+ new {
    me = self = [[Storm alloc] init];
    return self;
}

- free {
    [self logout:self];
    return [super free];
}


- appDidInit:sender {
    openDefaults();
    if (FirstUse()) [license show:self];
    [attachB performClick:sender];
    return self;
}

- appWillTerminate:sender {
    [self logout:self];
    System("/bin/rm -rf /tmp/W_maps /tmp/W_scratch &");
    writeDefaults();
    return self;
}

- (int)browser:sender fillMatrix:matrix inColumn:(int)column {
    char *s = "Main";
    if (column>0){
        id c;
        c = [[browser matrixInColumn:[browser lastColumn]] selectedCell];
        s = [c get];
    }
    setMenu(s);
    return numItem(s);
}

- browser:sender loadCell:c atRow:(int)row inColumn:(int)column {
    [c setStringValueNoCopy:curMenuItem(row)];
    return self;
}

- browserDidScroll:sender {
    static int pc = -1;
    int c = [browser firstVisibleColumn];
    ensurelogin();
    if (pc == c) return self;
    pc = c;
    if (c == 0) Put("m\n"), Flush("Change scroll"), setMenu("Main");
    else
    [[[browser matrixInColumn:0] selectedCell] click:self];
    return self;
}


@end
