/*
 * Interface for network Library of Congress search 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.
 */

#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 "MyMenuCell.h"
#import "Defaults.h"
#import "util.h"

@implementation Storm

int StormDebug = 1;
id _status;
extern setState(), ReadState(), runState(), access(), 
       getReport(), sleep(), execState();

int confirm(a,b,c,d) char *a,*b,*c,*d; {
    char t[1024];
    extern int NXApp;
    sprintf(t,a,b,c,d);
    return NXRunAlertPanel([NXApp appName],t,"Yes","No",NULL)==1;
}

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; {
    say(_status,a,b,c,d);
}

/*
 * These two routines are for tracking net activity.
 */
static void
monitorMail(subj,body,dest) char *subj, *body, *dest; {
    char s[1024];
    if (!dest || !*dest) dest = "monitor@tome.media.mit.edu";
    if (body && *body) sprintf(s,"echo %s",body);
    else strcpy(s,"(whoami ; hostname)");
    sprintf(s+strlen(s),"| /usr/ucb/Mail -s \"%s\" %s &",
	subj && *subj? subj : [NXApp appName], dest);
    system(s);
}

static void
monitor(subj) char *subj; { // send a note at first use.  to track net.
    char s[1024], *suf = "loc";
    sprintf(s,"%s/.NeXT/.%s",getenv("HOME"),suf);
    if (access(s,0)){
	FILE *f = fopen(s,"w");
	if (f) fclose(f), monitorMail(subj,0,0);
    }
}

static int eq(char *a, char *b){ return strncmp(a,b,strlen(b))==0; }

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; {
    put(_P,fmt,a,b,c,d);
}


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];
    cleanEsc(s);
    return *s? s : (char *)0; 
}

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

char
pgetc() {
    char c = [_P getc];
    if (c == 27 || c== '['){
        while ((c = [_P getc]) != 'H' /* && c != 'm'*/ && c != '\n' && c != '\r') ;
        if (c=='H' || c=='m') c = [_P getc];
    }
    return c;
}

#ifdef 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;
}
#endif

char *
Flush(s) char *s; {
    static char t[1024];
    if (_P) return (char *)0;
    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();
char TextBuf[80000]="";

#import <appkit/FontManager.h>

prettyFormat(s) char *s; { // tweak fonts in a card catalog record
    char *p = index(s,'\n')+1;
    if (*s){ // italicize leading ...: fields
      char *q = index(p,':');

      while (q && *q == ':'){
          [_text setSel:p-s:q-s+1];
          [_text setSelFontSize:[fid pointSize]*(12./14.)];
          [_text setSelFontStyle:NX_ITALIC];
          p = index(q,'\n'); if (p) p++;
          if (p && *p) q = index(p,':');
          else q = (char *)0;
      }
    }
}

/*
 * This is called as a TimedEntry as a kludge -- the effect we want
 * is for the user to change the font in the text view, and then to
 * apply the prettyFormat() to it.  The problem is that textWillConvert
 * will be invoked for every font change and we want to do this
 * after the smoke has cleared.
 */
hackToEnforceFonts(DPSTimedEntry n, double now, char *userData) {
    if (canconvert && *TextBuf){ // reformat current entry
	id w = [_scroll window];
	char *s = TextBuf, *p = index(s,'\n')+1;
	static NXPoint origin = {0.0,0.0};
        DPSRemoveTimedEntry(n);
	canconvert=0;
	[w disableFlushWindow];
	//[_text setText:s];
	[_text setSel:p-s:strlen(s)];
	[_text setSelGray:0.0];
	[_text setSelFont:fid];

	prettyFormat(s);

	[_text selectNull];
	[_text scrollPoint:&origin];
	[_scroll display];
	[[w reenableFlushWindow] flushWindow];
	canconvert=1;
        [_text setDelegate:userData];
    }
}

- 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();
        [_text setDelegate:0];
        DPSAddTimedEntry(0.,hackToEnforceFonts,self,100);
    }
    return to;
}

void
setText(s) char *s; {
    static NXPoint origin = {0.0,0.0};
    char *p = index(s,'\n')+1;  /* skip past first (blank) line */
    id w = [_scroll window];

    if (!fid){
        fid = [Font newFont:DefaultFont size:atof(DefaultFontSize)];
    }
    canconvert=0;
    [w disableFlushWindow];
    [_text setText:s];
    [_text setSel:p-s:strlen(s)];
    [_text setSelGray:0.0];
    [_text setSelFont:fid];

    prettyFormat(s);

    [_text selectNull];
    [_text scrollPoint:&origin];
    [_scroll display];
    [[w reenableFlushWindow] flushWindow];

    canconvert=1;
    strcpy(TextBuf,s);

      if (TextBuf[0] && index(TextBuf,':')) [[_text delegate] enableOrder];
      else [[_text delegate] disableOrder];
}

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

    canconvert=0;
    s = path(s);
    if (!f) return ;
    [w disableFlushWindow];
    [_text setMonoFont:NO];
    [_text readRichText:f];
    [_text scrollPoint:&origin];
    NXCloseMemory(f,NX_FREEBUFFER);
    [_scroll display];
    [[w reenableFlushWindow] flushWindow];
    TextBuf[0] = '\0';
    [[_text delegate] disableOrder];
}

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

#define Case break; case

void
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;
    p = t;
    return s;
}

cleanEsc(s) char *s; {
    if (*s == '[' || *s == 27){
        char *p = s;
        while (*p && *p != '\n' && *p != 'H') ++p;
        if (*p == 'H') ++p;
        strcpy(s,p);
    }
    return 1;
}

- processOutput:(char *)s {
    static char prev[8192]="";
    char t[1024], *p=prev;
    {
        char *p = s;
        while (*p){
            if (*p == 27) *p = '\n';
            p++;
        }
    }
    strcat(prev,s); s = t;
    while ((p=getLine(p,s)) && 
           (index(s,'\n') && cleanEsc(s))){
        if (Verbose) printf("- %s",s);
        execState(0,s);
    }
    strcpy(prev,blank(s)?s:"");
    return self;
}

- hitFormat:sender {
    extern setFormat();
    extern char *cmdHelpText();
    char *s = (char *)[[sender selectedCell] title];
    setFormat(s);
    [cmdHelp setStringValue:cmdHelpText()];
    return self;
}

id _browser;

- query:sender {
    char buf[80000];
    extern int LastItem;
    char *fixQuery(), *s = fixQuery([form stringValueAt:0]);
    if (!s || !*s){ // beep
        return self;
    }
    _browser = browser;
    enableMore(0);
    resetItems(); [browser loadColumnZero];
    Put("%s\r",s); pgets(buf,256);
    getReport(buf,0);
    [browser loadColumnZero];
    [form selectTextAt:0];
    if (LastItem == 3){
        fixItem(buf);
        setText(buf);
    } else
        setText("");
    return self;
}

static id me;

void
Command(s) char *s; {
    _P = [Process new:s delegate:me];
}

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

reload(DPSTimedEntry n, double now, char *userData) {
    extern int MustReload;
    DPSRemoveTimedEntry(n);
    if (MustReload) [_browser loadColumnZero], MustReload = 0;
    setText("");
}

- (int)browser:sender fillMatrix:matrix inColumn:(int)column {
    extern int NI;
    return NI;
}

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

id _moreB;

- more:sender {
    extern int LastItem;
    char buf[80000];
    Put("n\r"); pgets(buf,256);
    getReport(buf,1);
    [browser loadColumnZero];
    [form selectTextAt:0];
    if (LastItem == 3){
        fixItem(buf);
        setText(buf);
    } else
        setText("");
    return self;
}

enableMore(n){
    return [_moreB setEnabled:n];
}

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

- ready:sender {
    resetItems(); [browser loadColumnZero];
    [searchB setEnabled:1];
    return self;
}

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

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

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

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

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

- attach:sender {
    if (eq((char *)[sender icon],"unplug")){
        [self login:sender];
    } else {
        [self logout:sender];
    }
    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;
}

- (char *)service {
    char *s = "dra.com"; // (char *)[[format selectedCell] title];
    return s && *s? s : "192.65.218.43";
}

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

- sendComments:sender {
    return self;
}

char *
orderForm(){
    static char s[1024]="";
    char *h;
    if (*s) return s;
    h = getenv("HOME");
    if (!h || !*h) sprintf(s,"/tmp");
    else sprintf(s,"%s/.NeXT",h);
    strcat(s,"/BookOrderForm");
}

- editOrder:sender {
    if (access(orderForm(),0)){
        System("cp %s %s",path("BookOrderForm"),orderForm());
    }
    System("open %s",orderForm());
    return self;
}

- enableOrder {
    [orderB setEnabled:YES];
    [orderMenuB setEnabled:YES];
    return self;
}

- disableOrder {
    [orderB setEnabled:NO];
    [orderMenuB setEnabled:NO];
    return self;
}

- sendOrder:sender {
    char t[1024],t2[1024],body[80000], *Body = body;
    char to[1024] = "", cc[1024]="", subj[1024]="";
    FILE *f;
#define fcall(a)  [s performRemoteMethod:a]
#define call(a,b) [s performRemoteMethod:a with:b length:strlen(b)+1]
    id s = [NXApp appSpeaker];

    if (access(orderForm(),0)){
        if (confirm("You need to edit the order form...")){
            [self editOrder:self];
            //monitorMail("LibraryOfCongress-1.0-order",0,0);
        }
        return self;
    }

    strcpy(to,"quanbook@world.std.com");
    strcpy(subj,"another e-mail book order");
    strcpy(cc,"whoami"); execstr(cc); stripnl(cc);

    *body = '\0';
    if (strindex(TextBuf,"Type of Material:")){
        char *p, c[1024], t[80000];
	sprintf(t, "/tmp/book-order%d",getpid());
	sprintf(t2,"/tmp/book-info%d",getpid());
	System("sed '/^#/d' %s > %s",orderForm(),t);
	f = fopen(t2,"w");
	if (!f){
	    confirm("Couldn't create scratch file (%s)",t2);
	} else {
	    write(fileno(f),TextBuf,strlen(TextBuf));
	    write(fileno(f),"Body:\n",6);
            //strcpy(t,TextBuf+1); sub(t,'\t',' ');
	    write(fileno(f),TextBuf+1,strlen(TextBuf+1));
	    write(fileno(f),"Body:\n",6);
	    fclose(f);
	}
        sprintf(c,"sed '/^[a-zA-Z]* [a-zA-Z ]*:/s/ /_/' %s > %sa",t2,t2);
        system(c);
        sprintf(c,"%s %s < %sa > %sa",path("rtfcast"),t,t2,t);
        system(c);
        unlink(t); unlink(t2); strcat(t,"a"); strcat(t2,"a");
        f = fopen(t,"r");
	if (!f){
	    confirm("Couldn't read scratch file (%s)",t);
	} else {
	    int n = read(fileno(f),body,sizeof(body));
	    if (n > 0) body[n] = '\0';
            fclose(f);
	}
        unlink(t); unlink(t2);
        if (((p = strindex(Body,"To:")) ||
             (p = strindex(Body,"to:"))) &&
             (p == Body || p[-1] == '\n')){
            sscanf(skipsp(index(p,':')+1),"%[^\n]",to);
            if (p==Body) Body = index(p,'\n')+1;
        }
        if (((p = strindex(Body,"Subject:")) ||
             (p = strindex(Body,"subject:"))) &&
             (p == Body || p[-1] == '\n')){
            sscanf(skipsp(index(p,':')+1),"%[^\n]",subj);
            if (p==Body) Body = index(p,'\n')+1;
        }
        if (((p = strindex(Body,"Cc:")) ||
             (p = strindex(Body,"cc:"))) &&
             (p == Body || p[-1] == '\n')){
            sscanf(skipsp(index(p,':')+1),"%[^\n]",cc);
            if (p==Body) Body = index(p,'\n')+1;
        }
    }

    NXPortFromName("Mail", NULL); // make sure app is launched
    [s setSendPort:NXPortFromName("MailSendDemo", NULL)];

    call("setTo:",to);
    call("setSubject:",subj);
    call("setCc:",cc);
    call("setBody:",Body);
    
    return self;
}

- print:sender {
    [_text printPSCode:sender];
    return self;
}

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

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

- appDidInit:sender {
    openDefaults();
    if (FirstUse()) [license show:self],
                    strcpy(DefaultFont,"Helvetica-Bold"),
                    strcpy(DefaultFontSize,"18.0");
    [attachB performClick:sender];
    return self;
}

- appWillTerminate:sender {
    [self logout:self];
    writeDefaults();
    return self;
}

@end
