/* PlzController.m
 * Copyright (c) 1993 Detlev Droege, droege@informatik.uni-koblenz.de
 */
/* TABs auf 4 setzen - sonst sieht diese Datei schlimm aus ... */

// wie bekommen wir heraus, ob wir auf einem 3.0 System kompilieren ?:
#ifdef Keine_Ahnung 
# define NextStep3
#endif

#import "PlzController.h"
#import "Subprocess.h"
#import <appkit/Application.h>
#import <appkit/Font.h>
#import <appkit/Panel.h>
#import <appkit/Text.h>
#import <appkit/NXBrowser.h>
#import <appkit/NXBrowserCell.h>
#import <appkit/Matrix.h>
#import <string.h>
#import <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>

#ifdef NextStep3
# import <defaults/defaults.h>
#else
# import <defaults.h>
#endif

/* casts um Compiler-Warnings zu beheben. Unsigned char noetig wg. Umlauten */
#define STRNCMP(a,b,n)	(unsigned char *)strncmp((char *)(a),(char *)(b),(n))
#define INDEX(s,c)	(unsigned char *)index((char *)(s),(char)(c))

/* dieses Objekt ist (hoffentlich vergesse ich nichts):
	- Delegate von NXApp (appDidInit:, appWillTerminate:)
	- Delegate vom Preferences-Panel (windowWillClose:)
	- Delegate des subProcess (subprocessOutput:, subprocessError:,
							   subprocessDone)
	- Delegate des listBrowsers (browser:loadCell:atRow:inColumn:,
								 browser:fillMatrix:inColumn:)
   Ausserdem sind hier natuerlich die Methoden, die von den verschiedenen
   Benutzer "Controls" ausgeloest werden.
   
   Die meiste Arbeit macht das auseinanderdroeseln der Meldungen
   von plz.perl, die ja eher fuer menschliche Benutzer gedacht sind.
   Deshalb gibt's viele String-Vergleiche etc. Sonst steckt nicht
   allzuviel dahinter.
   Um plz.perl aufzurufen verwende ich die Subprocess Klasse, die bei
   NeXTSTEP 3.0 in /NextDeveloper/Examples/UNIX zu finden ist - genau
   fuer sowas ist sie ja gedacht.
*/

// fuer listType:
#define	LT_UNBEKANNT	0
#define	LT_STRASSE		1
#define	LT_ORT			2
#define	LT_HAUSNUMMER	3
#define	LT_POSTAMT		4

@implementation PlzController

- enableInput
{
	[listBrowser setEnabled:YES];
	[plzStadtField setEnabled:YES];
	[strField setEnabled:YES];
	return self;
}

- disableInput
{
	[listBrowser setEnabled:NO];
	[plzStadtField setEnabled:NO];
	[strField setEnabled:NO];
	return self;
}

- setDefaultPath:sender
{
	const char	*oldVal;
	
	oldVal = NXGetDefaultValue ([NXApp appName], "ServerDir");
	if (strcmp (oldVal, [sender stringValue]) == 0)
		return self;
	NXSetDefault ([NXApp appName], "ServerDir", [sender stringValue]);
	NXWriteDefault ([NXApp appName], "ServerDir", [sender stringValue]);
	[subProcess terminate:self];
	return self;
}

- setDefaultScript:sender
{
	const char	*oldVal;
	
	oldVal = NXGetDefaultValue ([NXApp appName], "ScriptName");
	if (strcmp (oldVal, [sender stringValue]) == 0)
		return self;
	NXSetDefault ([NXApp appName], "ScriptName", [sender stringValue]);
	NXWriteDefault ([NXApp appName], "ScriptName", [sender stringValue]);
	[subProcess terminate:self];
	return self;
}

- setDefaultOptions:sender
{
	const char	*oldVal;
	
	oldVal = NXGetDefaultValue ([NXApp appName], "ScriptOptions");
	if (strcmp (oldVal, [sender stringValue]) == 0)
		return self;
	NXSetDefault ([NXApp appName], "ScriptOptions", [sender stringValue]);
	NXWriteDefault ([NXApp appName], "ScriptOptions", [sender stringValue]);
	[subProcess terminate:self];
	return self;
}

- lookUp:sender
{
    char	buf[1024];
    
	if (sender != self) {
		[self resetList];
		listType = LT_UNBEKANNT;
	}
    sprintf (buf, "%s; %s ", [plzStadtField stringValue],
							[strField stringValue]);
    [self disableInput];
	[messageText setStringValue:"Suche ..."];
    [altAdr setStringValue:buf];
    [plzStadtFieldNeu setStringValue:""];
    if (! [self subSend:buf]) {
		[self resetList];
		[messageText setStringValue:"Bitte Konfiguration richtig einstellen"];
	    [self enableInput];
	}
    return self;
}

- startSub
{
	char		cmd[512];
	const char	*sdir, *sname, *sopt;
	struct stat	sbuf;
	int			ret;
	
	if (! subNeedsRestart)
		return self;
		
	sdir  = NXGetDefaultValue ([NXApp appName], "ServerDir");
	sname = NXGetDefaultValue ([NXApp appName], "ScriptName");
	sopt  = NXGetDefaultValue ([NXApp appName], "ScriptOptions");
		
	cmd[0] = 0;
	if (sdir && (strlen (sdir) != 0)) {
		strcpy (cmd, sdir);
		strcat (cmd, "/");
		ret = stat (sdir, &sbuf);
		if ((ret != 0) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) {
			[self showPrefPanel:self];
			[prefPathText selectText:self];
			return nil;
		}
	}
	strcat (cmd, sname);
	ret = stat (cmd, &sbuf);
	if ((ret != 0) || ((sbuf.st_mode & S_IFMT) != S_IFREG)) {
		[self showPrefPanel:self];
		[prefScriptText selectText:self];
		return nil;
	}
	
	if ((! sdir) || (strlen (sdir) == 0)) {
		sprintf (cmd, "exec %s %s", sname, sopt);
	}
	else {
		sprintf (cmd, "cd '%s' ; PLZHOME='%s'; export PLZHOME ; exec %s %s",
				sdir, sdir, sname, sopt);
	}
	[subProcess init:cmd
		withDelegate:self
		andPtySupport:NO
		andStdErr:YES];
	subNeedsRestart = 0;
	// fprintf (log_fp, "Nplz: subprocess (re-)started\n");
	return self;
}

- subSend:(char *)buf
{
	// falls kein Return in den Preferences Panel gedrueckt wurde:
	[self setDefaultPath:prefPathText];
	[self setDefaultScript:prefScriptText];
	[self setDefaultOptions:prefOptionsText];
	
	if (! [self startSub])
		return nil;
	// fprintf (log_fp, "SEND: >%s<\n", buf);
	[subProcess send:buf];
	return self;
}

- showInfoPanel:sender
{
    if (!infoPanel) {
        [NXApp loadNibSection:"InfoPanel.nib" owner:self withNames:NO];
    }
    [infoPanel makeKeyAndOrderFront:self];
    return self;
}

- showHelpPanel:sender
{
    if (!helpPanel) {
        [NXApp loadNibSection:"HelpPanel.nib" owner:self withNames:NO];
    }
    [helpPanel makeKeyAndOrderFront:self];
    return self;
}

- showPrefPanel:sender
{
    [preferencesPanel makeKeyAndOrderFront:self];
    return self;
}

- selectFromList:sender
{
	id ziel = nil;
	unsigned char *string, *pc;
	unsigned char sBuf[256];
	
#ifdef NextStep3
	string = (unsigned char *)[[sender selectedCell] stringValue];
#else /* NextStep 2.x */
	string = (unsigned char *)
		[[[sender matrixInColumn:[sender selectedColumn]]
			selectedCell] stringValue];
#endif
	if (! string)
		return self;
	// fprintf (log_fp, "selFrLst: '%s' (mode=%d)\n", string, listType);
	if (listType == LT_STRASSE)
		ziel = strField;
	else if (listType == LT_ORT)
		ziel = plzStadtField;
	else if (listType == LT_POSTAMT) {
		ziel = plzStadtField;
		strcpy ((char *)sBuf, (char *)string);
		pc = INDEX (sBuf, '(');
		if (pc)
			*pc = 0;
		string = sBuf;
	}
	else if (listType == LT_HAUSNUMMER) {
		ziel = strField;
		strcpy ((char *)sBuf, [strField stringValue]);
		pc = sBuf;
		while ((*pc) && ((*pc < '0') || (*pc > '9')))
			pc++;
		string = INDEX (string, ':');
		string++;
		if (*(pc - 1) != ' ')
			*pc++ = ' ';
		while (*string && *string != '-') {
			if (*string != ' ')
				*pc++ = *string;
			string++;
		}
		*pc = 0;
		string = sBuf;
	}
	else
		return self;
	
	[ziel setStringValue:(char *)string];
	[self lookUp:self];
	return self;
}

- resetList
{
	int	i;
	
	for (i = 0; i < [theList count]; i++)
		free ([theList objectAt:i]);
	[theList empty];
	newList = 1;
	[listBrowser setTitle:(const char *)"" ofColumn:0];
	return self;
}

// delegate Methoden fuer NXApp:

- appDidInit:sender
{
	Font	*fixedFont;
	
    const NXDefaultsVector NplzDefaults = {
		// { "ServerDir", "/usr/bs2/plz/postleitzahlen/server" },
		{ "ServerDir", "/usr/plz/server" },
		{ "ScriptName", "server.pl" },
		{ "ScriptOptions", "-n -" },
		{ NULL, NULL }
    };

    NXRegisterDefaults([NXApp appName], NplzDefaults);
	[prefPathText
		setStringValue:NXGetDefaultValue ([NXApp appName], "ServerDir")];
	[prefScriptText
		setStringValue:NXGetDefaultValue ([NXApp appName], "ScriptName")];
	[prefOptionsText
		setStringValue:NXGetDefaultValue ([NXApp appName], "ScriptOptions")];
	
	log_fp = fopen ("/dev/console", "w");
	subProcess = [Subprocess alloc];
	subNeedsRestart = 1;
	[self enableInput];
	
    theList = [[List alloc] init];
    [plzStadtField selectText:self];

#ifdef NextStep3  
    // nur ab NeXTSTEP 3.0
    fixedFont = [Font userFixedPitchFontOfSize:10.0 matrix:NX_FLIPPEDMATRIX];
    [[listBrowser cellPrototype] setFont:fixedFont];
#else
    fixedFont = [Font newFont:"Ohlfs" size:10.0];
    [[listBrowser cellPrototype] setFont:fixedFont];
#endif    
    [[listBrowser window] makeKeyAndOrderFront:self];
    newList = 0;
    return self;
}

- appWillTerminate:sender
{
	if (log_fp)
		fclose (log_fp);
    [subProcess terminate:sender];
    return self;
}

// delegate Methode fuer Preferences Panel:

- windowWillClose:sender
{
	[self setDefaultPath:prefPathText];
	[self setDefaultScript:prefScriptText];
	[self setDefaultOptions:prefOptionsText];
    return self;
}

// delegate Methoden fuer subProcess:

- subprocessOutput:(char *)buf
{
	unsigned char	*pc, *pb, *buffer;
	int	last;
	
	buffer = (unsigned char *)buf;
	// fprintf (log_fp, "RECEIVE: >%s<\n", buffer);
	
	// hier werden evtl. mehrere Zeilen als ein String gliefert, 
	// also zerlegen und dann {normal,cont}Line aufrufen.
	// Fortsetzungszeilen (contLine) fangen mit einem TAB an ...
	pb = buffer;
	last = 0;
	while (*pb) {
		pc = INDEX (pb, '\n');
		if (pc) {
			last = 0;
			*pc = 0;
		}
		else
			last = 1;
		if (*pb == ' ' || *pb == '\t')
			[self contLine:pb];
		else
			[self normalLine:pb];
		if (last)
			break;
		pb = pc + 1;
	}
	if (newList)
		[listBrowser loadColumnZero];
	newList = 0;
    return self;
}

- contLine:(unsigned char *)buffer
{
	unsigned char	*pc, *pb;
	int	last;
	
	pc = buffer;
	// fprintf (log_fp, "contLine: >%s<\n", buffer);
	while (*pc == ' ' || *pc == '\t')
		pc++;
	if (STRNCMP (pc, "Straûenangabe benðtigt.", 23) == 0) {
		// [self resetList];
		[messageText setStringValue:(char *)pc];
		[strField selectText:self];
		return self;
	}
	if (STRNCMP (pc, "Bitte vierstellige Alt-PLZ verwenden.", 36) == 0) {
		[self resetList];
		[messageText setStringValue:(char *) pc];
		[plzStadtField selectText:self];
		return self;
	}
	if (STRNCMP (pc, "Straûen mit gleichen Anfangsbuchstaben:", 38) == 0) {
		[self resetList];
		listType = LT_STRASSE;
		[listBrowser setTitle:"Straûen mit gl. Anfangsbuchst.:" ofColumn:0];
		[strField selectText:self];
		return self;
	}
	if (STRNCMP (pc, "ZustellpostÙmter:", 16) == 0) {
		[self resetList];
		listType = LT_POSTAMT;
		[listBrowser setTitle:(const char *)pc ofColumn:0];
		[plzStadtField selectText:self];
		return self;
	}
	if (STRNCMP (pc, "PostÙmter mit Postfach", 20) == 0) {
		[self resetList];
		listType = LT_POSTAMT;
		[listBrowser setTitle:(const char *)pc ofColumn:0];
		[plzStadtField selectText:self];
		return self;
	}
	if (STRNCMP (pc, "Orte mit gleichen Anfangsbuchstaben:", 35) == 0) {
		[self resetList];
		listType = LT_ORT;
		[listBrowser setTitle:"Orte mit gl. Anfangsbuchst.:" ofColumn:0];
		// [strField setStringValue:""];
		[plzStadtField selectText:self];
		return self;
	}
	if (STRNCMP (pc, "Bekannte Hausnummernbereiche:", 28) == 0) {
		[self resetList];
		listType = LT_HAUSNUMMER;
		[listBrowser setTitle:(const char *)pc ofColumn:0];
		[strField selectText:self];
		return self;
	}
	if (STRNCMP (pc, "Postfachnummer benðtigt.", 22) == 0) {
		[self resetList];
		[messageText setStringValue:(char *)pc];
		[strField setStringValue:"Postfach "];
		[strField selectText:self];
		return self;
	}
	pb = pc;
	last = 0;
	while (*pb) {
		pc = pb;
		while (*pc >= ' ' && *pc != ';')
			pc++;
		if (*pc == 0)
			last = 1;
		*pc = 0;
		newList = 1;
		if (listType == LT_HAUSNUMMER) {
				// Sonderfall: hier das Format etwas aendern
			int	gerade, plz;
			char	vonStr[32], bisStr[32],
				geradeStr[32], hnrStr[256];
				
			sscanf ((char *)pb, "%s von %s bis %s (%d)",
			  geradeStr, vonStr, bisStr, &plz);
			gerade = (strcmp (geradeStr, "gerade") == 0);
			sprintf (hnrStr, "%05d : %5s - %5s (%s)",
				plz, vonStr, bisStr,
			  	(gerade) ? "Gerade" : "Ungerade");
			[theList addObject:(id)NXCopyStringBuffer (hnrStr)];
		}
		else
			[theList addObject:(id)NXCopyStringBuffer ((char *)pb)];
		// fprintf (log_fp, " + %s\n", pb);
		if (last)
			break;
		pb = pc + 1;
		while (*pb && *pb <= ' ')
			pb++;
	}
	return self;
}

- normalLine:(unsigned char *)buffer
{
	unsigned char	*pc, *ph;
	
	// fprintf (log_fp, "normalLine: >%s<\n", buffer);
	[messageText setStringValue:""];
	[self enableInput];
	if (STRNCMP(buffer, "PLZ-Ende - Alle Angaben ohne Gewaehr!", 27)== 0) {
		subNeedsRestart = 1;
		return self;
	}
	[plzStadtFieldNeu setStringValue:""];
	pc = INDEX (buffer, '=');
	if (! pc) {
		// tritt z.B. auf bei:
		//	Adressformat falsch oder unbekannt: ;
		if (strlen ((char *)buffer) < 3)	// Leerzeile, z.B. Programmende
			return self;
		[messageText setStringValue:(char *)buffer];
		[plzStadtField selectText:self];
		if (STRNCMP (buffer, "Adressformat falsch oder unbekannt:", 31) == 0)
			return self;
		// fprintf (log_fp, "Nplz: unbekannte Rueckmeldung: >%s<\n", buffer);
		NXRunAlertPanel ("Plz.perl Subprozess meldet:",
							(char *)buffer, NULL, NULL, NULL);
		return self;
	}
	ph = pc - 1;
	while (*ph == ' ' || *ph == '\t')
		ph--;
	ph++;
	*ph = 0;
	[altAdr setStringValue:(char *)buffer];
	pc++;	// skip '='
	while (*pc == ' ' || *pc == '\t')
		pc++;
	if (*pc == 0) {
		[messageText setStringValue:" interner Fehler"];
		return self;
	}
	if (STRNCMP (pc, "PLZ/Ort nicht gefunden", 22) == 0) {
		[messageText setStringValue:"PLZ/Ort nicht gefunden"];
		[plzStadtField selectText:self];
		return self;
    }
	if (STRNCMP (pc, "Ort hat mehrere Zustell-PLZ", 27) == 0) {
		[messageText setStringValue:(char *)pc];
		[strField setStringValue:""];
		[strField selectText:self];
		return self;
    }
	if (STRNCMP (pc, "Ort hat mehrere Postfach-PLZ", 27) == 0) {
		[messageText setStringValue:(char *)pc];
		// [strField setStringValue:""];
		[strField selectText:self];
		return self;
	}
	if (STRNCMP (pc, "Straûe nicht gefunden", 21) == 0) {
		[messageText setStringValue:(char *)pc];
		[strField selectText:self];
		return self;
    }
	if (STRNCMP (pc, "Hausnummer nicht gefunden", 24) == 0) {
		[messageText setStringValue:(char *)pc];
		[strField selectText:self];
		return self;
    }
	if (STRNCMP (pc, "Straûe hat mehrere PLZ", 21) == 0) {
		[messageText setStringValue:(char *)pc];
		[strField selectText:self];
		return self;
    }
	if (STRNCMP (pc, "Zustellpostamt fehlt", 16) == 0) {
			// passt fuer Zustellpostamt f{ehlt,alsch}
		[messageText setStringValue:(char *)pc];
		[plzStadtField selectText:self];
		return self;
    }
	if (STRNCMP (pc, "Postfach nicht gefunden", 20) == 0) {
		[messageText setStringValue:(char *)pc];
		[strField selectText:self];
		return self;
    }
	
	ph = pc;
	while (*ph && *ph != ';')
		ph++;
	*ph = 0;
	[plzStadtFieldNeu setStringValue:(char *)pc];
	pc = ph + 1;
	while (*pc == ' ')
		pc++;
	ph = pc;
	while (*ph >= ' ')
		ph++;
	*ph = 0;
	[strField setStringValue:(char *)pc];
	[plzStadtField selectText:self];
	
	return self;
}

- subprocessError:(const char *)errorString
{
	// fprintf (log_fp, "Nplz: %s\n", errorString);
	[messageText setStringValue:errorString];
	subNeedsRestart = 1;
	return self;
}

- subprocessDone
{
	// fprintf (log_fp, "Nplz: subprocess terminated\n");
	subNeedsRestart = 1;
	return self;
}

// delegate Methoden fuer listBrowser:

- browser:sender loadCell:cell atRow:(int)row inColumn:(int)column
{
	int	l;
	
	if (! theList)
		return 0;
	l = [theList count];
	if (! l)
		return self;
	[cell setStringValueNoCopy:(char*)[theList objectAt:row]];
        [cell setLeaf:YES];
	return self;
}

- (int)browser:sender fillMatrix:matrix inColumn:(int)column
{
	int	l;
	
	l = 0;
	if (theList)
		l = [theList count];
	[matrix renewRows:l cols:1];
	return l;
}

@end
