/*
 * Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

/*!
 * @header PathNode
 */


#import "PathNode.h"
#import "PathRecordType.h"
#import "DSoDirectory.h"
#import "DSoNode.h"
#import "DSoException.h"
#import <DirectoryService/DirServicesConst.h>

static NSString *kNSStdRecordTypePrefix		= @"dsRecTypeStandard:";
static NSString *kNSNativeRecordTypePrefix  = @"dsRecTypeNative:";
static NSString *kNSStdAttrTypePrefix		= @"dsAttrTypeStandard:";
static NSString *kNSNativeAttrTypePrefix	= @"dsAttrTypeNative:";

@interface PathNode (PathNodePrivate)
// Print the search results from the searchForKey:withValue:matchType: routine.
- (void)printSearch:(NSString*)inKey Results:(NSArray*)inResults;
@end


@implementation PathNode

// ----------------------------------------------------------------------------
// Initialization / teardown
#pragma mark ******** Initialization / teardown ********


- init
{
    [super init];
    _pathName = nil;
    _dir = nil;
    _node = nil;
    _enableSubNodes = YES;
    return self;
}

- initWithDir:(DSoDirectory*)inDir path:(NSString*)inPath
{
    [self init];
    _pathName = inPath;
    [_pathName retain];
    _dir = inDir;
    return self;
}

- initWithNode:(DSoNode*)inNode path:(NSString*)inPath
{
    [self init];
    _pathName = inPath;
    [_pathName retain];
    _node = [inNode retain];
    _dir = [_node directory];
    return self;
}

- (void)dealloc
{
    [_pathName release];
    [_node release];
    [super dealloc];
}

// ----------------------------------------------------------------------------
// PathItemProtocol implementations
#pragma mark ******** PathItemProtocol implementations ********

- (NSString*)name
{
    NSAutoreleasePool      *pool;
    NSString			   *name	= nil;
    
    if ([_pathName isEqualToString:@"/NetInfo/root"])
        return @"NetInfo/root";
    else
    {
        pool = [[NSAutoreleasePool alloc] init];
        name = [[[_pathName componentsSeparatedByString:@"/"] lastObject] retain];
        [pool release];
        return [name autorelease];
    }
}

- (NSArray*)getList
{
	NSArray    *recordList  = [self getRecordList];
	NSArray    *subnodeList = nil;
	
	if (_enableSubNodes)
		subnodeList = [self getSubnodeList];

	if (recordList != nil && !gRawMode && [recordList count] > 0)
	{
		NSMutableArray *newList = [NSMutableArray arrayWithCapacity:[recordList count]];
		unsigned int	i			= 0;
		unsigned int	cntLimit	= [recordList count];
		for (i = 0; i < cntLimit; i++)
			[newList addObject:[self stripDSPrefixOffValue:[recordList objectAtIndex:i]]];
		recordList = newList;
	}
	if (recordList != nil && subnodeList != nil
		&& [recordList count] > 0 && [subnodeList count] > 0)
		return [subnodeList arrayByAddingObjectsFromArray:recordList];
	else if (recordList != nil && [recordList count] > 0)
		return recordList;
	else if (subnodeList != nil && [subnodeList count] > 0)
		return subnodeList;
	else
		return nil;
}

- (tDirStatus)list:(NSString*)inPath key:(NSString*)inKey
{
    NSAutoreleasePool      *pool		= [[NSAutoreleasePool alloc] init];
    NSArray				   *recordList  = [self getRecordList];
    NSString			   *recType		= nil;
    unsigned long			i			= 0;
	unsigned long			sCount		= 0;
	unsigned long			rCount		= 0;
    
    [super list:inPath key:(NSString*)inKey];
    
    if (_enableSubNodes)
    {
        NSArray		*subnodeList = [self getSubnodeList];
        sCount = [subnodeList count];
        for (i = 0; i < sCount; i++)
        {
            printf("%s\n", [[subnodeList objectAtIndex:i] UTF8String]);
        }
    }
    
    rCount = [recordList count];
    if (sCount && rCount)
        printf ("\n");

    for (i = 0; i < rCount ; i++)
    {
        recType = [self stripDSPrefixOffValue:[recordList objectAtIndex:i]];
        printf("%s\n", [recType UTF8String]);
    }
        
    [pool release];
	
    return eDSNoErr;
}

- (PathItem*) cd:(NSString*)dest
{
    PathItem *nextItem = nil;

    // The following checks are in order of fastest check.
    
    // If the dest empty, abort.
    if (dest == nil || [dest length] == 0)
    {
        return nil;
    }
	
    // If the destination hase a fully qualified record type, then use it;
    // else look for existing standard, then native types.
    else if ([dest hasPrefix:kNSStdRecordTypePrefix] || [dest hasPrefix:kNSNativeRecordTypePrefix])
    {
        nextItem = [[PathRecordType alloc] initWithNode:_node recordType:dest];
    }
	
    // try using it as the name of a child of this node, but only if
    // configured to do so.
    else if (_enableSubNodes)
    {
        NSString *fullPathName = [NSString stringWithFormat:@"%@/%@",_pathName,dest];
        DSoNode *n;
        NS_DURING
            n = [_dir findNode:fullPathName matchType:eDSExact useFirst:NO]; // more efficient for non-existant names
            nextItem = [[PathNode alloc] initWithNode:n path:fullPathName];
        NS_HANDLER
            if (!DS_EXCEPTION_STATUS_IS(eDSUnknownNodeName) &&
                !DS_EXCEPTION_STATUS_IS(eDSNodeNotFound))
            {	// Some unexpected exception
                [localException raise];
            }
        NS_ENDHANDLER
    }
        
    // Try looking for a standard or native type by the name of the destination.
    if (nextItem == nil) // I would have used else here, but the Exception handlers were causing problems.
    {
        NSString *stdDest = [kNSStdRecordTypePrefix stringByAppendingString:dest];
        NSString *nativeDest = [kNSNativeRecordTypePrefix stringByAppendingString:dest];
		if ([_node hasRecordsOfType:[stdDest UTF8String]])
			dest = stdDest;
		else if ([_node hasRecordsOfType:[nativeDest UTF8String]])
			dest = nativeDest;
		else
			dest = nil;

        if (dest != nil)
        {
            // The destination is either a fully qualified record type, or an existing type,
            // the next item is a record type node.
            nextItem = [[PathRecordType alloc] initWithNode:_node recordType:dest];
        }
        else
        {
            return nil;
        }
    }
        
    return [nextItem autorelease];
}

- (tDirStatus) read:(NSArray*)inKeys
{
    NSAutoreleasePool      *pool		= [[NSAutoreleasePool alloc] init];
    NSString			   *key			= nil;
	NSString			   *value		= nil;
    NSEnumerator		   *keyEnum;
	NSEnumerator		   *valueEnum;
    NSDictionary		   *nodeAttribs = nil;
    
    nodeAttribs = [_node getAllAttributes];
    
    if (inKeys != nil && [inKeys count] > 0)
    {
        keyEnum = [inKeys objectEnumerator];
    }
    else
    {
        keyEnum = [nodeAttribs keyEnumerator];
    }
    
    while (key = [keyEnum nextObject])
    {
        valueEnum = [[nodeAttribs objectForKey:key] objectEnumerator];
        key = [self stripDSPrefixOffValue:key];
        printf("%s:", [key UTF8String]);
        while (value = [valueEnum nextObject])
        {
            printf(" %s", [[self stripDSPrefixOffValue:value] UTF8String]);
        }
        printf("\n");
    }
    [pool release];
	
    return eDSNoErr;
}

- (tDirStatus) searchForKey:(NSString*)inKey withValue:(NSString*)inValue matchType:(NSString*)inType
{
	NSAutoreleasePool      *pool			= [[NSAutoreleasePool alloc] init];
	NSArray				   *searchResults   = nil;
	NSArray				   *recordTypes		= [_node findRecordTypes];
	NSMutableArray		   *attribList		= [NSMutableArray arrayWithObjects:
												@kDSNAttrRecordName,
												kDSOAttrRecordType, nil];
	NSString			   *key				= nil;
	tDirPatternMatch		type			= eDSExact;

	NS_DURING
		if ([inKey hasPrefix:kNSStdAttrTypePrefix] || [inKey hasPrefix:kNSNativeAttrTypePrefix])
		{
			key = inKey;
		}
		else
		{
			key = [kNSStdAttrTypePrefix stringByAppendingString:inKey];
			if (![[[_node directory] standardAttributeTypes] containsObject:key])
				key = [kNSNativeAttrTypePrefix stringByAppendingString:inKey];
		}
		[attribList addObject:key];
		searchResults = [_node findRecordsOfTypes:recordTypes withAttribute:[key UTF8String]
							value:inValue matchType:type retrieveAttributes:attribList];
		[self printSearch:key Results:searchResults];
				
		NS_HANDLER
			[localException retain];
			[pool release];
			[[localException autorelease] raise];
		NS_ENDHANDLER

		[pool release];
		
		return eDSNoErr;
}

// ----------------------------------------------------------------------------
// Utility functions
#pragma mark ******** Utility functions ********

- (PathItem*)cdNode:(NSString*)dest
{
    DSoNode    *n   = [_dir findNode:dest];
    PathNode   *p   = nil;
    
    if (n == nil)
    {
        //We just have a node prefix.
        p = [[PathNode alloc] initWithDir:_dir path:dest];
    }
    else
    {
        p = [[PathNode alloc] initWithNode:n path:dest];
    }
    
    return [p autorelease];
}

- (PathItem*)cdRecordType:(NSString*)destType
{
    PathRecordType *p = [[PathRecordType alloc] initWithNode:_node recordType:destType];
	
    return [p autorelease];
}


- (NSArray*)getSubnodeList
{
    NSAutoreleasePool      *pool					= [[NSAutoreleasePool alloc] init];
    NSArray				   *findResults				= nil;
    NSArray				   *findResults2			= nil;
	NSArray				   *nameComponents			= nil;
    NSArray				   *list					= nil;
	NSMutableSet		   *set						= [NSMutableSet set];
    NSString			   *name					= nil;
    unsigned long			i						= 0;
	unsigned long			count					= 0;
    int						currentComponentCount   = 0;
	int						iCompCount				= 0;
    
    NS_DURING
        findResults = [_dir findNodeNames:_pathName matchType:eDSStartsWith];
        
    NS_HANDLER
        if (!DS_EXCEPTION_STATUS_IS(eDSUnknownNodeName)
			&& !DS_EXCEPTION_STATUS_IS(eDSNodeNotFound))
        {
            // Clean up memory from the autorelease pool & the pool itself
            // before sending the exception on up.
            // This also means we have to transfer the localException from our local
            // pool to the containing pool, else it is lost.
            [localException retain];
            [pool release];
            [[localException autorelease] raise];
        }
    NS_ENDHANDLER
	
	NS_DURING
		if (_node != nil)
		{
			findResults2 = [_node getAttribute:kDSNAttrSubNodes];
			if (findResults2 != nil)
				findResults = [findResults arrayByAddingObjectsFromArray:findResults2];
		}
        
    NS_HANDLER
        // ignore exceptions here
    NS_ENDHANDLER
    
	count = [findResults count];
	
	// examine results to find only immediate child nodes.
	// We do this by comparing the number of components in the node names
	// using "/" as the component divider.
	currentComponentCount = [[_pathName componentsSeparatedByString:@"/"] count];
	for (i = 0; i < count; i++) 
	{
		name = [findResults objectAtIndex:i];
		nameComponents = [name componentsSeparatedByString:@"/"];
		iCompCount = [nameComponents count];
		if (iCompCount == currentComponentCount + 1)
			[set addObject:[nameComponents lastObject]];
	}
	list = [[set allObjects] retain];
	
	[pool release];
	
    return [list autorelease];
}

- (NSArray*)getRecordList
{
    if (_node != nil)
        return [_node findRecordTypes];
    else
        return [NSArray array];
}

// ----------------------------------------------------------------------------
// Accessor functions
#pragma mark ******** Accessor functions ********

- (BOOL)enableSubNodes
{
    return _enableSubNodes;
}

- (void)setEnableSubNodes:(BOOL)value
{
    _enableSubNodes = value;
}

- (NSString*)nodeName
{
    return [_node getName];
}

- (tDirStatus) authenticateName:(NSString*)inUsername withPassword:(NSString*)inPassword authOnly:(BOOL)inAuthOnly
{
    return [_node authenticateName:inUsername withPassword:inPassword authOnly:inAuthOnly];
}

@end

// ----------------------------------------------------------------------------
// Private functions
#pragma mark ******** Private functions ********

@implementation PathNode (PathNodePrivate)
/* Used by searchForKey:withValue:matchType: */
- (void)printSearch:(NSString*)inKey Results:(NSArray*)inResults
{
	NSEnumerator	   *resultEnumerator	= [inResults objectEnumerator];
	NSString		   *kNSAttrRecordName   = @kDSNAttrRecordName;
	NSDictionary	   *d					= nil;
	
	while(d = [resultEnumerator nextObject])
	{
		printf("%s/%s\t\t%s = %s\n",[[self stripDSPrefixOffValue:[d objectForKey:kDSOAttrRecordType]] UTF8String],
		 [[[d objectForKey:kNSAttrRecordName] objectAtIndex:0] UTF8String],[[self stripDSPrefixOffValue:inKey] UTF8String],[[[d objectForKey:inKey] description] UTF8String]);
	}
}

@end