/*---------------------------------------------------------------------------
Servers.m -- Copyright (c) 1991 Rex Pruess

   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, or send
   electronic mail to the the author.

Servers manages the Nameservers window.  It build the scrollView of servers
based on data it receives from the default Nameserver.

Rex Pruess <Rex-Pruess@uiowa.edu>

$Header: /rpruess/apps/Ph/qiServers.subproj/RCS/Servers.m,v 2.3 92/04/27 14:00:25 rpruess Exp $
-----------------------------------------------------------------------------
$Log:	Servers.m,v $
Revision 2.3  92/04/27  14:00:25  rpruess
Fixed a bug that caused Ph to crash for remote help.  The bug was obscure.
If the Nameserver did not maintain a site name for the default Nameserver,
then Ph would crash.  This should not happen anymore.

Revision 2.2  91/12/15  15:55:58  rpruess
Renames the startTimer/stopTimer methods so they are unique within the
Ph group of files.  This change wasn't really needed except to help
clarify which methods belonged to which object files.

Revision 2.1  91/12/10  16:20:13  rpruess
Added code to support the "hide" preference options.  This code was
added prior to sending the first production release to the archive sites.

Revision 2.0  91/11/19  08:26:28  rpruess
Revision 2.0 is the initial production release of Ph.

-----------------------------------------------------------------------------*/
#define MAXSECS	1.0		/* Max # of seconds before alert panel */

#define MINSERVERSHEIGHT 115.0
#define MINSERVERSWIDTH  150.0

#define TIMERSECS 0.2		/* Animator wakeup interval */

/* Standard C header files */
#include <libc.h>
#include <strings.h>

/* Objective-C & appkit header files */
#import <objc/List.h>
#import <appkit/Application.h>
#import <appkit/Cursor.h>
#import <defaults/defaults.h>
#import <appkit/Form.h>
#import <appkit/Matrix.h>
#import <appkit/Panel.h>
#import <appkit/ScrollView.h>
#import <appkit/SelectionCell.h>
#import <appkit/Window.h>

/* Application class header files */
#import "Servers.h"
#import "Qi.h"
#import "QiManager.h"
#import "Site.h"
#import "../PhShare.h"
#import "../info.h"
#import "../query.h"

/* Qi Server header file (Qi return codes) */
#import "QiReplies.h"

@implementation Servers

/*---------------------------------------------------------------------------
Load the Servers.nib file, initialize variables, create the Servers matrix,
and set up the ScrollView.
-----------------------------------------------------------------------------*/
- init
{
   NXSize          cSize;
   NXRect          mRect;

   self = [super init];
   [NXApp loadNibSection:"Servers.nib" owner:self withNames:NO];
   [serversWindow setMiniwindowIcon:"app"];

   qiManager = nil;
   queryManager = nil;
   serversMenuCell = nil;

   curSite = 0;
   hasServers = NO;
   
   serversAnimator = [[Animator alloc] initChronon:TIMERSECS
      adaptation:0.0
      target:self
      action:@selector (serversTimeCheck)
      autoStart:NO
      eventMask:0];

   siteList = [[List alloc] init];

   /*** Create the Servers matrix.  This matrix will hold the site names
        of all the NameServers. */

   mRect.origin.x = 0;
   mRect.origin.y = 0;
   [serversScrollView getContentSize:&mRect.size];
   
   serversMatrix= [[Matrix alloc] initFrame:&mRect
      mode:NX_RADIOMODE
      cellClass:[SelectionCell class]
      numRows:0
      numCols:1];

   [serversMatrix setAutosizing:NX_WIDTHSIZABLE];
   [serversMatrix setAutosizeCells:YES];

   [serversMatrix setTarget:self];
   [serversMatrix setAction:@selector(singleClick)];
   [serversMatrix setDoubleAction:@selector(doubleClick)];
   
   [serversMatrix getCellSize:&cSize];
   
   cSize.width = mRect.size.width;
   [serversMatrix setCellSize:&cSize];

   [[serversScrollView setDocView:serversMatrix] free];
   [serversScrollView setDocCursor:NXArrow];

   return self;
}

/*---------------------------------------------------------------------------
Set ID of objects we need later on.
-----------------------------------------------------------------------------*/
- initIDs:aMenuCell queryManager:aQueryManager qiManager:aQiManager
{
   serversMenuCell = aMenuCell;
   queryManager = aQueryManager;
   qiManager = aQiManager;
   return self;
}

/*---------------------------------------------------------------------------
Build the serversMatrix based on the site names which were retrieved earlier
from the default Nameserver.
-----------------------------------------------------------------------------*/
- buildMatrix
{
   int             i;
   int             nServers;

   nServers = [siteList count];

   /*** Add each Nameserver site to the serversMatrix. */

   for (i = 0; i < nServers; i++) {
      [serversMatrix addRow];
      [[serversMatrix cellAt:i:0] setStringValue:[[siteList objectAt:i] site]];
   }

   [serversMatrix sizeToCells];
   [serversMatrix display];

   /*** We finally have the name of the site for the default server.  Select
        the default server in the Servers matrix so it is selected & visible
        when the Servers window is brought forward.  */

   for (i = 0; i < nServers; i++) {
      if (strcmp (defaultServer, [[siteList objectAt:i] server]) == 0) {
	 [serversMatrix selectCellAt:i:0];
	 [serversMatrix scrollCellToVisible:i:0];

	 [serversTextField setStringValue:defaultServer];
	 [domainTextField setStringValue:[[siteList objectAt:i] domain]];
	    
	 break;
      }
   }
   
   /*** Enable the Servers menu item. */

   [serversMenuCell setEnabled:YES];

   return self;
}
   
/*---------------------------------------------------------------------------
Save the defaultServer object for later use.  Start the timer and wait for the
earlier "fields" command to finish.  When the "fields" are back, the timer
will stop, & the query ns-servers" command will be issued to generate the
entire list of Nameservers.
-----------------------------------------------------------------------------*/
- fetchServers:(const char *)aServer
{
   defaultServer = malloc (strlen (aServer) + 1);
   strcpy (defaultServer, aServer);

   if ((defaultQi = [qiManager open:defaultServer]) == nil)
      return self;

   [self startServersTimer:self];

   return self;
}

/*---------------------------------------------------------------------------
Update the Servers & Domain text fields to reflect the newly selected entry.
-----------------------------------------------------------------------------*/
- singleClick
{
   id              aCell;
   int             i;

   aCell = [serversMatrix selectedCell];

   for (i = 0; i < [siteList count]; i++) {

      if (strcmp ([aCell stringValue], [[siteList objectAt:i] site]) == 0) {

	 [serversTextField setStringValue:[[siteList objectAt:i] server]];
	 [domainTextField setStringValue:[[siteList objectAt:i] domain]];

	 break;

      }
   }
   
   return self;
}

/*---------------------------------------------------------------------------
User has doubleClicked a Server.  Must bring it to life.
-----------------------------------------------------------------------------*/
- doubleClick
{
   [queryManager startQuery:[serversTextField stringValue]];
   return self;
}

/*---------------------------------------------------------------------------
Display the Servers window.
-----------------------------------------------------------------------------*/
- showServersWindow:sender
{
   [serversWindow makeKeyAndOrderFront:self];
   return self;
}

/*---------------------------------------------------------------------------
Don't let the Servers window be too small.
-----------------------------------------------------------------------------*/
- windowWillResize:sender toSize:(NXSize *) frameSize
{
   if (frameSize -> width < MINSERVERSWIDTH)
      frameSize -> width = MINSERVERSWIDTH;

   if (frameSize -> height < MINSERVERSHEIGHT)
      frameSize -> height = MINSERVERSHEIGHT;

   return self;
}

/*---------------------------------------------------------------------------
This method is set to be Qi's delegate.  It processes one line of Qi output
at a time.  This output is the result of the "ns-servers" inquiry.  Upon end
of output, it is responsible for telling Qi to stop and for lanuching a
Query window for the default server.
-----------------------------------------------------------------------------*/
- qiOutput:(char *)aBuf
{
   int             len;
   int             theCode;
   char           *theData;
   id              theSite;

   theCode = atoi (aBuf);
   if (theCode == LR_NUMRET)
      return self;

   if (theCode >= LR_OK) {	/* Tell the Qi object to stop watching FD */

      [self qiOutputIsDone:self];
      return self;
   }

   if (theCode == -LR_OK || theCode == -LR_AINFO || theCode == -LR_ABSENT
      || theCode == -LR_ISCRYPT) {

      len = strlen (aBuf) - 1;
      if (aBuf[len] == '\n')
	 aBuf[len] = '\0';

      /*** Insert new sites alphabetically in the list. */

      theData = strstr (aBuf, "site:");

      if (theData != NULL) {
	 theData += strlen ("site:");
	 
	 theSite = [[Site alloc] init];
	 [theSite setSite:theData];

	 for (curSite = 0; curSite < [siteList count]; curSite++) {
	    if (strcmp (theData, [[siteList objectAt:curSite] site]) < 0)
	       break;
	 }
	 
	 [siteList insertObject:theSite at:curSite];

	 return self;
      }

      /*** Assign "server" & "domain" lines to the curSite object. */

      theData = strstr (aBuf, "server:");
	 
      if (theData != NULL) {
	 theData += strlen ("server:");
	 [[siteList objectAt:curSite] setServer:theData];
	 return self;
      }
     
      theData = strstr (aBuf, "domain:");
	    
      if (theData != NULL) {
	 theData += strlen ("domain:");
	 [[siteList objectAt:curSite] setDomain:theData];
	 return self;
      }
   }	 

   return self;
}

/*---------------------------------------------------------------------------
The "ns-servers" output has been received.  
-----------------------------------------------------------------------------*/
- qiOutputIsDone:sender
{
   id              theSite;

   hasServers = YES;
   [defaultQi stopFd:self];

   /*** Insist that the default server be in the siteList.  This should not
        happen, provided the Ph administrator maintains the default server
        in the "ns-servers" entry.  If we must add it in, then we don't
        bother to insert it alphabetically (we tack it to the front). */

   if ([self getSiteName:defaultServer] == NULL) {
      theSite = [[Site alloc] init];

      [theSite setSite:defaultServer];
      [theSite setServer:defaultServer];

      [siteList insertObject:theSite at:0];
   }

   /*** Build the Servers matrix. */

   [self buildMatrix];

   /*** If the user has requested not to see the default Query window, then
        simply return.  */

   if (strcmp (NXGetDefaultValue ([NXApp appName], HIDEQUERY), "YES") == 0)
      return self;

   /*** If Ph was auto-launched & the user has requested it to hide, then
        simply return.  The default Query session will be started when the
        user first unhides Ph. */

   if (strcmp (NXGetDefaultValue ([NXApp appName], "NXAutoLaunch"), "YES") == 0 &&
       strcmp (NXGetDefaultValue ([NXApp appName], HIDELAUNCH), "YES") == 0)
      return self;

   /*** Start the default Query session. */

   [queryManager startQuery:defaultServer];

   return self;
}

/*---------------------------------------------------------------------------
The animator is responsible for periodically invoking this method.  This
code checks to see if the Qi object has been initialized.  Qi objects
need to get "fields" data so there is some delay before they come to life.
-----------------------------------------------------------------------------*/
- serversTimeCheck
{
   if ([defaultQi hasQiFields] == YES) {
      [self stopServersTimer:self];
      [defaultQi qiSend:"query ns-servers type=serverlist return text\n" delegate:self];
   }
   
   return self;
}

/*---------------------------------------------------------------------------
Wait for the Qi object to initialize.
-----------------------------------------------------------------------------*/
- startServersTimer:sender
{
   [serversAnimator startEntry];

   return self;
}

/*---------------------------------------------------------------------------
The Qi object has initialized itself or we've given up on it.  The timer
is only needed once so free it after its been stopped.
-----------------------------------------------------------------------------*/
- stopServersTimer:sender
{
   [serversAnimator stopEntry];
   [serversAnimator free];
   
   return self;
}

/*---------------------------------------------------------------------------
Add a new server/site. This method is invoked via the Speaker/Listener code.
If another app requests a connection with a server that we don't know about,
we honor the request anyway.
-----------------------------------------------------------------------------*/
- addServerSite:(const char *)aServer site:(const char *)aSite
{
   int             ind;
   int             nServers;
   id              theSite;

   /*** If we already have an entry for this server, then skip the new
        request. */

   if ([self getSiteName:aServer] != NULL)
      return self;
   
   /*** Find the alphabetical position for the new entry. */

   nServers = [siteList count];
   
   for (ind = 0; ind < nServers; ind++) {
      if (strcmp (aSite, [[siteList objectAt:ind] site]) < 0)
	 break;
   }

   /*** Create the new site & insert it into siteList. */

   theSite = [[Site alloc] init];

   [theSite setServer:aServer];
   [theSite setSite:aSite];

   [siteList insertObject:theSite at:ind];

   /*** Add it to the end of the Servers matrix too. */

   [serversMatrix insertRowAt:ind];
   [[serversMatrix cellAt:ind :0] setStringValue:aSite];

   [serversMatrix sizeToCells];
   [serversMatrix display];
   
   return self;
}

/*---------------------------------------------------------------------------
Return the domain name for the specified server.
-----------------------------------------------------------------------------*/
- (const char *)getDomainName:(const char *)aServer
{
   int             i;
   int             nSites;
   
   nSites = [siteList count];

   for (i = 0; i < nSites; i++) {
      if (strcmp (aServer, [[siteList objectAt:i] server]) == 0)
	 return [[siteList objectAt:i] domain];
   }

   return NULL;
}

/*---------------------------------------------------------------------------
Return the server name for the specified site.
-----------------------------------------------------------------------------*/
- (const char *)getServerName:(const char *)aSite
{
   int             i;
   int             nSites;

   if (aSite == NULL)
      return NULL;
   
   nSites = [siteList count];

   for (i = 0; i < nSites; i++) {
      if (strcmp (aSite, [[siteList objectAt:i] site]) == 0)
	 return [[siteList objectAt:i] server];
   }

   return NULL;
}

/*---------------------------------------------------------------------------
Return the site name for the specified server.
-----------------------------------------------------------------------------*/
- (const char *)getSiteName:(const char *)aServer
{
   int             i;
   int             nSites;

   if (aServer == NULL)
      return NULL;
   
   nSites = [siteList count];

   for (i = 0; i < nSites; i++) {
      if (strcmp (aServer, [[siteList objectAt:i] server]) == 0)
	 return [[siteList objectAt:i] site];
   }

   return NULL;
}

/*---------------------------------------------------------------------------
Return a tab-separated list of the server names and the site names.  This
method is for the Speaker/Listener support.  Some other application may
need to know what sites/servers Ph knows about.
-----------------------------------------------------------------------------*/
- getServersAndSites:(char **)serverNames sites:(char **)siteNames
{
   int             i;
   int             nSites;
   int             serverLen;
   int             siteLen;
   
   serverLen = 1;		/* Need one byte for the trailing '\0'  */
   siteLen = 1;
   
   /*** Make a pass through all the site objects and figure out the
        malloc sizes. */

   nSites = [siteList count];
   
   for (i = 0; i < nSites; i++) {
      serverLen += strlen ([[siteList objectAt:i] server]) + 1;
      siteLen += strlen ([[siteList objectAt:i] site]) + 1;
   }

   /*** Create the variables exactly as big as we need them. */

   *serverNames = malloc (serverLen);
   *siteNames = malloc (siteLen);

   *serverNames[0] = '\0';
   *siteNames[0] = '\0';

   /*** Build the server & site strings. */

   for (i = 0; i < nSites; i++) {
      strcat (*serverNames, [[siteList objectAt:i] server]);
      strcat (*serverNames, "\t");
      
      strcat (*siteNames, [[siteList objectAt:i] site]);
      strcat (*siteNames, "\t");
   }

   return self;
}

/*---------------------------------------------------------------------------
Return flag indicating whether or not we have received the server names.
-----------------------------------------------------------------------------*/
- (BOOL)hasServers
{
   return hasServers;
}

@end
