/*
    SambaManger. A graphical frontend to configure the NetInfo enhanced samba.
    Copyright (C) 1998  Robert Frank

    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 2 of the License, 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.
		
		Robert Frank, frank@ifi.unibas.ch
*/

#import "Hosts.h"
#import "Controller.h"
#import "NIDirectory.h"
#import "NIProperty.h"
#import "NetInfoKeys.h"

// The property names of a host's directory:
#define H_NAME				"name"
#define H_NETBIOSNAME	"netbios name"
#define H_IPADDRESS		"ip_address"
#define H_ETHERNET		"en_address"
#define H_NETGROUPS		"netgroups"
#define H_SYSTEM			"system_type"
#define H_OWNER				"owner"
#define H_BOOTFILE		"bootfile"
#define H_BOOTPARAMS	"bootparams"

// Class variable with the string for the open/save panel's title.
static const char 		*title;
static unsigned char  hexOk[128] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
                                    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
                                    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
                                    1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,
                                    0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,
                                    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
                                    0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,
																		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

@implementation Hosts

// ************************************************************************
// ************************************************************************
// Class methods:
+ initialize
// Called once by the run time system
{
		title = [Service stringFor:"Title:Hosts"];
		return self;
}

+ new:sender at:(NXCoord *)offset
{
NIDirectory	*NIDir;
Hosts				*host = [Hosts alloc];

		if ((NIDir = [NIDirectory new:sender root:SMNI_HOSTS directory:NULL])) {
			[NIDir setDelegate:host];
			if ([host init:sender dirObj:NIDir delta:offset service:NULL]) {
				[NIDir setSaveTitle:title];
				return host;
			}
			[NIDir close];
		}
		return [host free];
}

+ open:sender at:(NXCoord *)offset
{
NIDirectory	*NIDir;
Hosts				*host = [Hosts alloc];

		if ((NIDir = [NIDirectory open:sender root:SMNI_HOSTS withTitle:title])) {		
			[NIDir setDelegate:host];
			if ([host init:sender dirObj:NIDir delta:offset service:[NIDir baseName]])
				return host;

			[NIDir close];
		}
		return [host free];
}


- getBootParms:parameter
{
int								i;
NIProperty				*prop;
const ni_namelist	*values;

		if (!(prop = [ni_dirObj property:H_BOOTPARAMS]))
			return self;
		values = [prop valueList];
		[textRootDirectory setStringValue:""];
		[textPrivateDirectory setStringValue:""];
		
		for (i = 0; i < values->ni_namelist_len; i++)
			if (!strncasecmp(values->ni_namelist_val[i], "root", 4))
				[textRootDirectory setStringValue:values->ni_namelist_val[i]+5];
			else if (!strncasecmp(values->ni_namelist_val[i], "private", 7))
				[textPrivateDirectory setStringValue:values->ni_namelist_val[i]+8];

		return self;
}

- setBootParms
{
char							*root = NULL, *private = NULL;
const char				*s = NULL;
NIProperty				*prop;

		if (!(prop = [ni_dirObj property:H_BOOTPARAMS]))
			return nil;
		
		s = [textRootDirectory stringValue];
		if (s && *s) {
			root = (char *)NXZoneMalloc([self zone], strlen(s) + 6);
			strcpy(root, "root=");
			strcat(root, s);
		}
		s = [textPrivateDirectory stringValue];
		if (s && *s) {
			private = (char *)NXZoneMalloc([self zone], strlen(s) + 9);
			strcpy(private, "private=");
			strcat(private, s);
		}
		
		// Search. If inexistant, create! If exists and root/private non-null,
		// update, otherwise delete
		[prop updateValue:root at:[prop findValue:"^root.*" how:NI_FIND_REGEX]];
		[prop updateValue:private at:[prop findValue:"^private.*" how:NI_FIND_REGEX]];
		if (root)
			NXZoneFree([self zone], root);
		if (private)
			NXZoneFree([self zone], private);

		return self;
}

- selectAlias:sender
{
		[textAliases setStringValue:[[sender selectedCell] stringValue]];
		[textAliases selectText:self];
		return self;
}

- (BOOL)minimumOK
{
char	value[32], *c = value;
int		l, ival;

		if (!*[textHostname stringValue] || !*[textInternetAddress stringValue]) {
			NXRunAlertPanel(getString("Alert:Alert"),
											getString("Message:Must have a name and address"),
											getString("Button:OK"),NULL,NULL);
			return NO;
		}
		
		// Let's check a few values for correctnes:
		// The Netbios name may be atmost 15 characters
		if (strlen([textNetbiosname stringValue]) > 15 ) {
			NXRunAlertPanel(getString("Alert:Alert"),
											getString("Message:Netbios Name may have atmost 15 characters."),
											getString("Button:OK"),NULL,NULL);
			return NO;
		}

		// The IP address must be four values with three dots:
		// nnn.nnn.nnn.nnn
		bzero(value, 32);
		if (*strncpy(value, [textInternetAddress stringValue], 30)) {
			value[strlen(value)] = '.';
			for (l = 4; l && c; l--) {
				ival = atoi(c);
				if ((ival > 0) && (ival < 256)) {
					if (c = strchr(c, '.'))
						c++;
				} else
					c = NULL;
			}
			if (!c) {
				NXRunAlertPanel(getString("Alert:Alert"),
												getString("Message:Invalid IP address.\nMust be of the form: `nnn.nnn.nnn.nnn'."),
												getString("Button:OK"),NULL,NULL);
				return NO;
			}
		}
		
		// The Ethernet address must be six hex values with five colons,
		// no leading zeros: 0:0:f:0:81:f9
		bzero(value, 32);
		c = value;
		if (*strncpy(value, [textEthernetAddress stringValue], 30)) {
			value[strlen(value)] = ':';
			for (l = 6; l && c; l--) {
				if (!hexOk[*c++&0x7f]) {
					c = NULL;
					break;
				}
				if (*c != ':')
					if (!((*(c-1) != '0') && hexOk[*c++&0x7f])) {
						c = NULL;
						break;
					}
				if (*c != ':')
					c = NULL;
				else
					c++;
			}
			if (!c) {
				NXRunAlertPanel(getString("Alert:Alert"),
												getString("Message:Invalid Ethernet address.\nMust be: `hh:hh:hh:hh:hh:hh' with no leading zeros."),
												getString("Button:OK"),NULL,NULL);
				return NO;
			}
		}

		return YES;
}

// ************************************************************************
//	Methods:
- revert:sender
{
		[super revert:sender];
    [browserAliases loadColumnZero];
		return self;
}

- delete:sender
{
		if (!strcmp([ni_dirObj baseName], "localhost") ||
				!strcmp([ni_dirObj baseName], "broadcasthost")) {
			NXRunAlertPanel(getString("Alert:Alert"),
											getString("Message:Cannot delete localhost or broadcasthost!"),
											getString("Button:OK"), NULL, NULL);
			return self;
		}

		return [super delete:self];
}

- setupAndLoad
{
NIProperty	*nbname, *name;
char				*NBName, *p;

		// Initialize the parameter list
		name = [ni_dirObj addString:H_NAME outlet:textHostname];
		nbname = [ni_dirObj addString:H_NETBIOSNAME outlet:textNetbiosname];
		[ni_dirObj addString:H_IPADDRESS outlet:textInternetAddress];
		[ni_dirObj addString:H_ETHERNET outlet:textEthernetAddress];
		[ni_dirObj addString:H_OWNER outlet:textSystemOwner];
		[ni_dirObj addPopup:H_SYSTEM outlet:popupSystemType default:"Unknown"];
		[ni_dirObj addBrowser:H_NETGROUPS browser:browserNetGroups text:textNetgroups
							 add:buttonAddNetGroup remove:buttonDelNetGroup];
		[ni_dirObj addString:H_BOOTFILE outlet:textKernel];
		[ni_dirObj addCall:H_BOOTPARAMS displayAction:@selector(getBootParms:)];

		// Scan the loaded NetInfo values and update the GUI.
		[ni_dirObj scan];

		// Check for a netbios name. If none exists, uppercase the hostname.
		if (([nbname index] == NI_VALUE_NOT_FOUND) && (*[name valueAt:0])) {
			NBName = NXCopyStringBufferFromZone([name valueAt:0],[self zone]);
			for (p = NBName; *p; p++)
				*p = NXToUpper(*p);
			[nbname insertValue:NBName];
			[textNetbiosname setStringValue:NBName];
			NXZoneFree([self zone], NBName);
		}
		
		// Set up the alias browser and text.
		[browserAliases setDelegate:self];
		[browserAliases setAction:@selector(selectAlias:)];
    [browserAliases loadColumnZero];
		[textAliases setTextDelegate:self];
		[textRootDirectory setTextDelegate:self];
		[textPrivateDirectory setTextDelegate:self];
		
		// Select the top field.
		[textHostname selectText:self];
		return self;
}

// ************************************************************************
// Actions:

- addAlias:sender
{
const char 				*v;
int								i;
NIProperty				*prop;
const ni_namelist	*values;

		if (!(prop = [ni_dirObj property:H_NAME]))
			return 0;
		values = [prop valueList];
		v = [textAliases stringValue];
		
		if (values->ni_namelist_len) {
			for (i = 1; i < values->ni_namelist_len; i++)
				if (!strcasecmp(values->ni_namelist_val[i], v)) {
					[[browserAliases matrixInColumn:0] selectCellAt:i-1 :0];
					return self;
				} else if (strcasecmp(values->ni_namelist_val[i], v) > 0)
					break;
			[prop insertValue:v at:i--];
			[window setDocEdited:YES];
			[browserAliases reloadColumn:0];
			[[browserAliases matrixInColumn:0] selectCellAt:i :0];
			[textAliases selectText:self];
		}

    return self;
}

- removeAlias:sender
{
int									i;
NIProperty				*prop;
const ni_namelist	*values;
id								cell = [browserAliases selectedCell];

		if (!cell || !(prop = [ni_dirObj property:H_NAME]))
			return self;
		values = [prop valueList];

		i = ni_namelist_match(*values, [[browserAliases selectedCell] stringValue]);
    if ((i != NI_INDEX_NULL) && (i > 0)) { // Must not delete the name!
			[prop deleteValue:i];
			[window setDocEdited:YES];
			[browserAliases reloadColumn:0];
			i-= 2; // The browser starts with value index 1!
			if (i > 0)
				[[browserAliases matrixInColumn:0] selectCellAt:i :0];
			else if (values->ni_namelist_len)
				[[browserAliases matrixInColumn:0] selectCellAt:0 :0];
		}
    return self;
}

- setNetBoot:sender
{
BOOL	edited = [window isDocEdited];
int		signal;
char	*bootFile = NXCopyStringBufferFromZone([textKernel stringValue],[self zone]),
			*bootParms1 = NXCopyStringBufferFromZone([textRootDirectory stringValue],[self zone]),
			*bootParms2 = NXCopyStringBufferFromZone([textPrivateDirectory stringValue],[self zone]);

		[textKernel selectText:self];

		// Run a modal for the panel.
		signal = [NXApp runModalFor:panel];
		[panel close];
		
		switch (signal) {
			case NX_RUNABORTED:
				// Restore to previos values
				[[ni_dirObj property:H_BOOTFILE] updateProperty:bootFile default:(!bootFile || !*bootFile)];
				[textKernel setStringValue:bootFile];
				[textRootDirectory setStringValue:bootParms1];
				[textPrivateDirectory setStringValue:bootParms2];
				[window setDocEdited:edited];
				break;
			case NX_RUNSTOPPED: 
			default: ;
		}
		
		NXZoneFree([self zone], bootFile);
		NXZoneFree([self zone], bootParms1);
		NXZoneFree([self zone], bootParms2);
    return self;
}

// Actions from the Net Boot configuration panel
- configOK:sender
{
		[NXApp stopModal];
		[self setBootParms];
		return self;
}

- configCancel:sender
{
		[NXApp abortModal];
		return self;
}

// ************************************************************************
// Delegates:

- (BOOL)textWillEnd:textObject
{
const char	*alert = NULL,
						*path = [[textObject superview] stringValue],
						*p = strchr(path,':');

		if ([textObject superview] == textRootDirectory)
			alert = getString("Alert:Invalid root directory.");
		else if ([textObject superview] == textPrivateDirectory)
			alert = getString("Alert:Invalid private directory.");

		// Accept the empty input and check for ':/'.
		if (alert && path && *path && ((p && (*(p+1) != '/')) || !p)) {
			NXRunAlertPanel(getString("Alert:Alert"), alert, getString("Button:OK"), NULL, NULL);
			return YES;
		}
		return NO;
}

- textDidEnd:textObject endChar:(unsigned short)whyEnd
{
		if (([textObject superview] == textAliases) && (whyEnd == NX_RETURN))
			[self addAlias:self];

		return self;
}

- (int) browser:sender fillMatrix:matrix inColumn:(int) column
{
NIProperty				*prop;
const ni_namelist	*values;
int								rows = 0, i = 0;
id								cell;

		if (!(prop = [ni_dirObj property:H_NAME]))  // Can be called before ni_dirObj is set.
			return 0;
		if ([prop index] == NI_INDEX_NULL)
			return 0;
		
		values = [prop valueList];

		for (i = 1; i < values->ni_namelist_len; i++) {
			[matrix addRow];
			cell = [matrix cellAt:rows :0];
			[cell setStringValue:values->ni_namelist_val[i]];
			[cell setLoaded:YES];
			[cell setLeaf:YES];
			rows++;
		}

		return rows;
}

@end
