#import "GopherClient.h"
#import "StuartSpeaker.h"
#import "GopherDispatcher.h"
#import <appkit/Listener.h>
#import <appkit/Speaker.h>
#import "PhSpeaker.h"

#include <stdio.h>

// Declared in GopherDispatcher.m
extern char defaultHost[256];
extern int defaultPort;

static int isIndexedText = NO;
static char lastIndexWord[256];

@implementation GopherClient

///////////////////////////////////////////////////
//

- (char *)currentCellName
{
    id CurrentCell;

    CurrentCell = [[MainScroller matrixInColumn:[MainScroller selectedColumn]] 
                   selectedCell];
    return  (char *)[CurrentCell stringValue];
}

//////////////////////////////////////////////////
// Compute a new location for a window.

static void newLocation(NXPoint *p)
{
    static count = 0;

    p->x += (20.0 * count);
    p->y -= (25.0 * count);
    count = (count > 10)? 0 : count+1;
}

//////////////////////////////////////////////////
// Automatically called by the system when an instance is created.

- init
{
    NXRect 	theFrame;

    [super init];

    strncpy(currenthostname, defaultHost, 250);
    strcpy(currentfilename, "");  
    currentport = defaultPort;

    [NXApp loadNibSection:"GopherClient.nib" owner:self];

    [MainScroller setCellClass:[GopherBrowserCell class]];
	[MainScroller setDelegate:self];
    [MainScroller useScrollButtons:YES];
    [MainScroller loadColumnZero];

    //[myWindow setDelegate:self];
    [myWindow getFrame:&theFrame];
    newLocation(&theFrame.origin);
    [myWindow moveTo:theFrame.origin.x :theFrame.origin.y];
    [myWindow makeKeyAndOrderFront:nil];

    [[MainText docView] setAutodisplay:YES];


    theText = (char *)malloc(MAXTEXTLENGTH);

    return self;
}

/////////////////////////////////////////////////
// Close the window.

- close
{
    [myWindow performClose:self];
    [self clean];
    [myDispatcher oldFrontWindow:self];		// Theoretically unnecessary

    return self;
}

/////////////////////////////////////////////////
// Clean things up.

- clean
{
    [ItemInfoPanel performClose:self];
    return self;
}

/////////////////////////////////////////////////
// Target method for the browser.

- cellClicked:sender
{
    id 		CurrentCell;
    int		remotePort;
    ThreadArgument *theArg;
    extern int	defaultApp;
    char        *remotePath;

// Stuff borrowed from Rex Pruess's Remotes program
    id            stuart;
    int     	  code, ind;
    char      	  *command;
    char          *comArgs[10];
    int           pid;
    char          portStr[16];
    char          loginStr[256];
    char          errMsg[256];
    extern char   **environ;     /* User environment */
#ifdef DEBUG
    char          	**tempPtr;
#endif

// Stuff for Rex Pruess's Ph CSO Nameserver client
   int            flag;
   int            msgDelivered;
   char           server[128];
   char           site[128];



    CurrentCell = [[sender matrixInColumn:[sender selectedColumn]] selectedCell];

    [ItemInfoHostName setStringValue:[CurrentCell remoteHost]];
    [ItemInfoFileName setStringValue:[CurrentCell remoteName]];
    [ItemInfoPortNumber setIntValue:[CurrentCell remotePort]];

    switch ([CurrentCell cellType]) {
	case '0' : 			// Document : retrieve and display
	    strncpy(currenthostname, [CurrentCell remoteHost], 255);
	    strcpy(currentfilename, [CurrentCell remoteName]);
	    currentport = [CurrentCell remotePort];
	    [ItemInfoType setStringValue:"ASCII text"];
	    [self displayDocument];
	    break;
	case '1' :			// Directory : do nothing, taken care of in 
					// browser:fillMatrix:inColumn:
	    isIndexedText = NO;
	    [ItemInfoType setStringValue:"Directory"];
	    [[MainText docView] setText:""];
	    break;
	case '2':                       // CSO Nameserver
	     if (![self createPort:self])
                 break;

             strncpy (server, [CurrentCell remoteHost], 128);
             strncpy (site, [CurrentCell stringValue], 128);

             msgDelivered = [mySpeaker showServer:server site:site ok:&flag];

             if (msgDelivered != 0) 
                fprintf (stderr, "Sorry, showServer message not delivered.\n");
             else
                if (!flag) {
	            fprintf (stderr, "\n*** showServer call failed. ***\n");
	            fprintf (stderr, "*** Should only happen if 'server' is empty or too long.\n");
                }
   
             free (mySpeaker);
	     [ItemInfoType setStringValue:"Phone Server"];
	     break;
	case '7' :			// Index search
	    [ItemInfoType setStringValue:"Index"];
	    isIndexedText = YES;
	    break;
	case 'R':
	case 'T':
	case '8':			// Telnet session.  Revised by David Lacey		
	    [ItemInfoType setStringValue:"Telnet Session"];
	    isIndexedText = NO;
	    [[MainText docView] setText:""];
	    if ([CurrentCell remotePort] == 0)       // Seems common, so fix it
		remotePort = 23;
	    else
		remotePort = [CurrentCell remotePort];

            // If there is a login ID specified and we're telnetting, alert the user
            if (([CurrentCell cellType] == '8') && 
	         *(remotePath = [CurrentCell remoteName])) {
		    sprintf (errMsg, "Login to remote system as '%s'.", remotePath);
		    NXRunAlertPanel (NULL, errMsg, NULL, NULL, NULL, NULL);
                }

	    if (defaultApp == 0) { // Stewart or Terminal?
		ind = 0;			
    		command = "/NextApps/Terminal";
		comArgs[ind++] = "Terminal";

		comArgs[ind++] = "-Shell";

		switch ([CurrentCell cellType]) {
        	    case 'R':  // RLOGIN
			strcpy (loginStr, "/usr/ucb/rlogin ");
			if (*[CurrentCell remoteName] != '\0') {
		  	    strcat (loginStr, " -l ");
               		    strcat (loginStr,  [CurrentCell remoteName]);
            		}
 			strcat (loginStr, " ");
			strcat (loginStr, [CurrentCell remoteHost]);
			break;

		    case '8':  // TELNET:
			strcpy (loginStr, "/usr/ucb/telnet ");
			strcat (loginStr, [CurrentCell remoteHost]);
			if (remotePort != 23) {
			    sprintf (portStr, " %d", remotePort);
			    strcat (loginStr, portStr);
			}
		 	break;

		    case 'T': // TN3270:
			strcpy (loginStr, "/usr/ucb/tn3270 ");
			strcat (loginStr, [CurrentCell remoteHost]);
			break;

		}
		comArgs[ind++] = loginStr;
		comArgs[ind++] = NULL;
#ifdef DEBUG
/*      		printf ("%s: %s",[NXApp appName], command);
		tempPtr = comArgs;
		while (*++tempPtr != NULL)
		    printf (" %s", *tempPtr);
		    printf ("\n");
		}
*/
#endif
		pid = vfork ();
 		if (pid == 0) {
		    code = execve (command, comArgs, environ);
		    perror ([NXApp appName]);
		    fprintf (stderr, "%s: Execve error code=%d.\n",[NXApp appName], code);
		    _exit (0);
		}

	    } else {  // Send a message to Stuart

		stuart = [StuartSpeaker new];

		switch ([CurrentCell cellType]) {
		    case 'R':  // RLOGIN
			strcpy (loginStr, "rlogin ");
			if (*[CurrentCell remoteName] != '\0') {
			    strcat (loginStr, " -l ");
               		    strcat (loginStr,  [CurrentCell remoteName]);
            		}
 			strcat (loginStr, " ");
			strcat (loginStr, [CurrentCell remoteHost]);
	    		break;

		    case '8':  // TELNET:
			strcpy (loginStr, "telnet ");
			strcat (loginStr, [CurrentCell remoteHost]);
			if (remotePort != 23) {
			    sprintf (portStr, " %d", remotePort);
			    strcat (loginStr, portStr);
			}
		 	break;

		    case 'T': // TN3270:
			strcpy (loginStr, "tn3270 ");
			strcat (loginStr, [CurrentCell remoteHost]);
			break;

		}
		[stuart default:"Shell" as:loginStr];
		[stuart default:"WriteStuartrc" as:"NO"];

		[stuart setActivate:TRUE];
			
		if ([stuart stuartConnectAndNew]) {
		    strcpy (errMsg, "There was a problem communicating with the Stuart ");
		    strcat (errMsg, "application.  (Speaker port unavailable.)  This may ");
		    strcat (errMsg, "have occurred because Stuart is not installed on ");
		    strcat (errMsg, "your system or because Stuart was slow in starting.");

		    NXRunAlertPanel (NULL, errMsg, NULL, NULL, NULL, NULL);
		}       
	        [stuart free];
	    }
		
		
	    break;

	case 's' :			// Sound
	    isIndexedText = NO;
	    [ItemInfoType setStringValue:"Sound"];
	    [[MainText docView] setText:""];
	    // Check that we're the only thread. We do it here because
	    // we can't have NXRunAlertPanel in a thread (why not ?)
	    if (mutex_try_lock(soundIsPlaying) == 0) {
		NXRunAlertPanel("Gopher error", "Only one sound at a time !", "OK", NULL, NULL);
		break;
	    }
		
	    mutex_unlock(soundIsPlaying);
		
	    soundShouldStopFlag = 0;
	    // The argument is malloc'ed because we may have several threads.
	    theArg = malloc(sizeof(ThreadArgument));
	    strncpy(theArg->hostname, [CurrentCell remoteHost], 255);
	    theArg->portnumber = [CurrentCell remotePort];
	    strncpy(theArg->filename, [CurrentCell remoteName], 1023);
	    cthread_detach(cthread_fork((cthread_fn_t)PlaySound, (any_t)theArg));
	    break;
	default :
	    break;
    }

    return self;
}

///////////////////////////////////////////////////////////////////
// Highlight next occurance of search string, if it's indexed text.
//

- highlightNextWord
{
     int findIndex, spaceIndex;
     int minIndex = -1;
     char *word, *cp = NULL, *textBuffer;
     char wordStowage[256];
     int done = 0;
     NXSelPt begin, end;

     strcpy(wordStowage, lastIndexWord);
     word = wordStowage;

     [[MainText docView] getSel:&begin :&end];

     textBuffer = theText + end.cp;

     while (!done) {

	  cp = strchr(word, ' ');

	  if (cp == NULL)
	       done = 1;
	  else
	       *cp='\0';

#ifdef DEBUG
	fprintf(stderr, "Word is: %s\n", word);
#endif

	  // Don't highlight stupid words
	  
	  if ((strcasecmp(word, "and") != 0) &&
	      (strcasecmp(word, "or")  != 0) &&
	      (strcasecmp(word, "not") != 0) &&
              (strcasecmp(word, "the") != 0))  {
	       

	       findIndex = (int)strcasestr(textBuffer, word);

               if (findIndex == 0)
	               ;		// Not found
               else {
	             if (minIndex == -1)
		          minIndex = findIndex;
	             else if (minIndex > findIndex) 
		          minIndex = findIndex;
	       }
        }

	word = cp +1;  // Move on to the next word.

     }
     

     if (minIndex != -1) {

	 spaceIndex = (int)index((char *)minIndex, ' ');

         if (spaceIndex > (int)index((char*)minIndex, '\r')) 
		spaceIndex = (int)index((char*)minIndex, '\r');

         findIndex = (int)minIndex - (int)theText;
         spaceIndex = (int)spaceIndex - (int)theText;

         // Highlight the found text, make it visible.

         [[MainText docView] setSel:findIndex :spaceIndex];
         [[MainText docView] scrollSelToVisible];
     }
     else
       NXBeep();
     return self;
}

////////////////////////////////////////////////////////////////
// Connect to currenthostname on port currentport, retrieve
// document currentfilename and display it.

- displayDocument
{
    int iLength = 1, i;
    int currentsocket;

    currentsocket = create_socket(currenthostname, currentport);
    if (currentsocket < 0)
	{
	switch (currentsocket)
	    {
	    case -1 :
		NXRunAlertPanel("Gopher error", "Unknown host", "OK", NULL, NULL);
		return self;
		break;
	    case -2 :
 		NXRunAlertPanel("Gopher error", "Socket call failed",												
		                "OK", NULL, NULL);
		return self;
		break;
  	    case -3 :
		NXRunAlertPanel("Gopher error", "Connect call failed", "OK", NULL, NULL);
		return self;
		break;
	    default :
		break;
	    }
	}

    writestring(currentsocket, currentfilename);
    writestring(currentsocket, "\r\n");

    for (i=0; (i < MAXTEXTLENGTH) && iLength; i += iLength)
	iLength = read(currentsocket, theText + i, 10000);
	
    theText[i - 3] = '\0';		// -3 to get rid of final period
    [[MainText docView] setText:theText];

    close(currentsocket);

    [[MainText docView] setSel:0 :0];
    [[MainText docView] scrollSelToVisible];

    if (isIndexedText)
	[self highlightNextWord];



/*	findIndex = (int)strstr(theText, lastIndexWord);
	if (findIndex == 0)
	    ;		// Not found
	else {
	    spaceIndex = (int)index((char *)findIndex, ' ');
	    findIndex = (int)findIndex - (int)theText;
	    spaceIndex = (int)spaceIndex - (int)theText;
	    [[MainText docView] setSel:findIndex :spaceIndex];
	    [[MainText docView] scrollSelToVisible];
	}
    }
*/
    return self;
}

/////////////////////////////////////////////////
// Find our dispatcher

- setDispatcher:sender
{
    myDispatcher = sender;
    return self;
}

/////////////////////////////////////////////////
// Print the contents of the text window.

- print
{
    [[MainText docView] printPSCode:self];

    return self;
}

/////////////////////////////////////////////////
// Saves the contents of the text window.

- saveAs:(char *)filename
{
    NXStream *tmpstream;

    tmpstream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
    [[MainText docView] writeText:tmpstream];
    NXSaveToFile(tmpstream, filename);
    NXCloseMemory(tmpstream, NX_FREEBUFFER);

    return self;
}

/////////////////////////////////////////////////
// 

- searchIndex:sender
{
    [myIndexPanel orderOut:self];
    [NXApp stopModal:1];

    return self;
}

/////////////////////////////////////////////////
// 

- cancelIndex:sender
{
    [myIndexPanel orderOut:self];
    [NXApp stopModal:2];

    return self;
}

/////////////////////////////////////////////////
// 

- getIndexString:(char *)theString maxLength:(int)maxlength
{
    [myIndexPanel makeKeyAndOrderFront:self];
    [IndexTextItem selectText:self];
    if ([NXApp runModalFor:myIndexPanel] == 1)	// User clicked OK
	strncpy(theString, [IndexTextItem stringValue], maxlength);
    else
	strcpy(theString, "");			// User clicked Cancel

    return self;
}

/////////////////////////////////////////////////
//

- showItemInfo
{
    [ItemInfoPanel makeKeyAndOrderFront:self];
    return self;
}

/////////////////////////////////////////////////
// Delegate method for the browser

- (int)browser:sender fillMatrix:matrix inColumn:(int)column
{
    id		tmpcell;
    int 	iLength;
    char 	inputline[512];
    char 	*remotename, *remotehost, *remoteport;
    int 	currentsocket;
    id 		CurrentCell;

    CurrentCell = [[sender matrixInColumn:[sender selectedColumn]] selectedCell];

    switch ([CurrentCell cellType]) {
	case '0' : 			// Document : should never happen
	    break;
	case '1' :			// Directory : the browser will
					// automatically update
	    strncpy(currenthostname, [CurrentCell remoteHost], 250);
	    strcpy(currentfilename, [CurrentCell remoteName]);
	    currentport = [CurrentCell remotePort];
	    break;
	case '7' :			// Index search
	    strncpy(currenthostname, [CurrentCell remoteHost], 250);
	    [self getIndexString:(char *)&lastIndexWord maxLength:250];
	    if (strlen(lastIndexWord) == 0)		// User clicked Cancel
		return 0;
	    strcpy(currentfilename, [CurrentCell remoteName]);
            strcat(currentfilename, "\t");
            strcat(currentfilename, lastIndexWord);
	    currentport = [CurrentCell remotePort];
	    break;
	case '8' :			// Telnet session : should never happen
	    break;
	case 's' :			// Sound : should never happen
	    break;
	default :
	    break;
	}

    currentsocket = create_socket(currenthostname, currentport);
    if (currentsocket < 0) 
    	switch (currentsocket) {
	    case -1 :
		NXRunAlertPanel("Gopher error", "Unknown host",	"OK", NULL, NULL);
		return(0);
		break;
	    case -2 :
		NXRunAlertPanel("Gopher error", "Socket call failed", "OK", NULL, NULL);
		return(0);
		break;
	    case -3 :
		NXRunAlertPanel("Gopher error", "Connect call failed", "OK", NULL, NULL);
		return(0);
		break;
	    default :
		break;
	}

    writestring(currentsocket, currentfilename);
    writestring(currentsocket, "\r\n");

    for(;;) {
	iLength = readline(currentsocket, inputline, 512);
	  
	if (iLength == -1)
	    break;
	if (iLength == 0)
	    break;

	ZapCRLF(inputline);
	  
	if ((inputline[0] == '0') || (inputline[0] == '1') ||
	      (inputline[0] == '7') || (inputline[0] == '8') ||
	      (inputline[0] == 'R') || (inputline[0] == 'T') ||
	      (inputline[0] == 's') || (inputline[0] == '2')) {
   	    if ((inputline[0] == 's') && (shouldUseSound == 0))
		continue;

   	    [matrix addRow];
	    tmpcell = [matrix cellAt:([matrix cellCount] -1) :0];
		
	    remotename = index(inputline, '\t') + 1;
	    *(remotename-1) = '\0';
		
	    remotehost = index(remotename, '\t') + 1;
	    *(remotehost-1) = '\0';
		
	    remoteport = index(remotehost, '\t') + 1;
	    *(remoteport-1) = '\0';

	    [tmpcell setRemoteName:remotename];
	    [tmpcell setRemoteHost:remotehost];
	    [tmpcell setRemotePort:atoi(remoteport)];
	    [tmpcell setStringValue:(inputline+1)];
	    [tmpcell setLoaded:YES];
	    if (inputline[0] == '0') {		// Document
		[tmpcell setLeaf:YES];
		[tmpcell setCellType:'0'];
	    }
	    else if (inputline[0] == '1') {	// Directory
	        [tmpcell setLeaf:NO];
		[tmpcell setCellType:'1'];
	    }
	    else if (inputline[0] == '2') {     // CSO Nameserver
		[tmpcell setLeaf:YES];
		[tmpcell setCellType:'2'];
	    }

	    else if (inputline[0] == '7') {	// Index
		[tmpcell setLeaf:NO];
		[tmpcell setCellType:'7'];
	    }
	    else if (inputline[0] == '8') {	// Telnet
		[tmpcell setLeaf:YES];
		[tmpcell setCellType:'8'];
	    }
	    else if (inputline[0] == 'R') {	// Rlogin
		[tmpcell setLeaf:YES];
		[tmpcell setCellType:'R'];
	    }		
	    else if (inputline[0] == 'T') {	// TN3270
		[tmpcell setLeaf:YES];
		[tmpcell setCellType:'T'];
	    }		
	    else if (inputline[0] == 's') {	// Sound
		[tmpcell setLeaf:YES];
		[tmpcell setCellType:'s'];
	    }
	}
	
	if ((inputline[0] == '.') && (inputline[1] == '\0'))
		break;
    }

    close(currentsocket);

    return([matrix cellCount]);
}

////////////////////////////////////////////////////////////////////////////
// Delegate methods for the window

/////////////////////////////////////////////////
// Called when our window becomes the key window.

- windowDidBecomeKey:sender
{
   [myDispatcher newFrontWindow:self];

   return self;
}

/////////////////////////////////////////////////
// Called when our window has stopped being the key window.

- windowDidResignKey:sender
{
    [myDispatcher oldFrontWindow:self];

    return self;
}

/////////////////////////////////////////////////
// Called when our window is going to close.

- windowWillClose:sender
{
    [self clean];
    [myDispatcher oldFrontWindow:self];

    return self;
}


//  Stuff from Rex Pruess's Ph CSO Nameserver App

/*---------------------------------------------------------------------------
This internal support method sets the Speaker port and creates the PhSpeaker
object.
-----------------------------------------------------------------------------*/
- (BOOL) createPort:sender
{
   port_t         thePort;

   thePort = NXPortFromName ("Ph", NULL);
   
   if (thePort == PORT_NULL) {
      fprintf (stderr, "\n*** PhSpeaker port is NULL.\n");
      fprintf (stderr, "*** Perhaps Ph is not installed in a standard directory.\n");
      return NO;
   }

   mySpeaker = [[PhSpeaker alloc] init];

   [mySpeaker setSendPort:thePort];

   return YES;
}


@end
