/* Implementation for NSXMLNode for GNUStep
   Copyright (C) 2008 Free Software Foundation, Inc.

   Written by:  Richard Frith-Macdonald <rfm@gnu.org>
   Created: September 2008

   This file is part of the GNUstep Base Library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 3 of the License, or (at your option) any later version.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02111 USA.
*/

#import "common.h"

#define GSInternal              NSXMLNodeInternal
#import "NSXMLPrivate.h"
#import "GSInternal.h"
GS_PRIVATE_INTERNAL(NSXMLNode)


@implementation NSXMLNode

+ (id) attributeWithName: (NSString*)name
	     stringValue: (NSString*)stringValue
{
  NSXMLNode	*n;

  n = [[[self alloc] initWithKind: NSXMLAttributeKind] autorelease];
  [n setStringValue: stringValue];
  [n setName: name];
  return n;
}

+ (id) attributeWithName: (NSString*)name
		     URI: (NSString*)URI
	     stringValue: (NSString*)stringValue
{
  NSXMLNode	*n;

  n = [[[self alloc] initWithKind: NSXMLAttributeKind] autorelease];
  [n setURI: URI];
  [n setStringValue: stringValue];
  [n setName: name];
  return n;
}

+ (id) commentWithStringValue: (NSString*)stringValue
{
  NSXMLNode	*n;

  n = [[[self alloc] initWithKind: NSXMLCommentKind] autorelease];
  [n setStringValue: stringValue];
  return n;
}

+ (id) DTDNodeWithXMLString: (NSString*)string
{
  NSXMLNode	*n;

  n = [[[self alloc] initWithKind: NSXMLDTDKind] autorelease];
  [n setStringValue: string];
  return n;
}

+ (id) document
{
  NSXMLNode	*n;

  n = [[[NSXMLDocument alloc] initWithKind:NSXMLDocumentKind] autorelease];
  return n;
}

+ (id) documentWithRootElement: (NSXMLElement*)element
{
  NSXMLDocument	*d;

  d = [NSXMLDocument alloc];
  d = [[d initWithRootElement: element] autorelease];
  return d;
}

+ (id) elementWithName: (NSString*)name
{
  NSXMLNode	*n;

  n = [[[NSXMLElement alloc] initWithName: name] autorelease];
  return n;
}

+ (id) elementWithName: (NSString*)name
	      children: (NSArray*)children
	    attributes: (NSArray*)attributes
{
  NSXMLElement *e = [self elementWithName: name];

  [e insertChildren: children atIndex: 0];
  [e setAttributes: attributes];
  return e;
}

+ (id) elementWithName: (NSString*)name
		   URI: (NSString*)URI
{
  NSXMLNode	*n;

  n = [[[NSXMLElement alloc] initWithName: name URI: URI] autorelease];
  return n;
}

+ (id) elementWithName: (NSString*)name
	   stringValue: (NSString*)string
{
  NSXMLElement	*e;
  NSXMLNode	*t;

  e = [self elementWithName: name];
  t = [[self alloc] initWithKind: NSXMLTextKind];
  [t setStringValue: string];
  [e addChild: t];
  [t release];
  return e;
}

+ (NSString*) localNameForName: (NSString*)name
{
  return [self notImplemented: _cmd];
}

+ (id) namespaceWithName: (NSString*)name
	     stringValue: (NSString*)stringValue
{
  NSXMLNode	*n;

  n = [[[self alloc] initWithKind: NSXMLNamespaceKind] autorelease];
  [n setStringValue: stringValue];
  return n;
}

+ (NSXMLNode*) predefinedNamespaceForPrefix: (NSString*)name
{
  return [self notImplemented: _cmd];
}

+ (NSString*) prefixForName: (NSString*)name
{
  return [self notImplemented: _cmd];
}

+ (id) processingInstructionWithName: (NSString*)name
			 stringValue: (NSString*)stringValue
{
  NSXMLNode	*n;

  n = [[[self alloc] initWithKind: NSXMLProcessingInstructionKind] autorelease];
  [n setStringValue: stringValue];
  return n;
}

+ (id) textWithStringValue: (NSString*)stringValue
{
  NSXMLNode	*n;

  n = [[[self alloc] initWithKind: NSXMLTextKind] autorelease];
  [n setStringValue: stringValue];
  return n;
}

- (NSString*) canonicalXMLStringPreservingComments: (BOOL)comments
{
  return [self notImplemented: _cmd];	// FIXME ... generate from libxml
}

- (NSXMLNode*) childAtIndex: (NSUInteger)index
{
  return [internal->children objectAtIndex: index];
}

- (NSUInteger) childCount
{
  return internal->childCount;
}

- (NSArray*) children
{
  return internal->children;
}

- (id) copyWithZone: (NSZone*)zone
{
  id c = [[self class] allocWithZone: zone];

  c = [c initWithKind: internal->kind options: internal->options];
  [c setName: [self name]];
  [c setURI: [self URI]];
  [c setObjectValue: [self objectValue]];
  [c setStringValue: [self stringValue]];

  return c;
}

- (void) dealloc
{
  if (GS_EXISTS_INTERNAL)
    {
      [self detach];
      [internal->name release];
      [internal->URI release];
      [internal->children release];
      [internal->objectValue release];
      [internal->stringValue release];
      GS_DESTROY_INTERNAL(NSXMLNode);
    }
  [super dealloc];
}

- (void) detach
{
  [(NSXMLElement*)internal->parent removeChildAtIndex: [self index]];
}

- (NSUInteger) hash
{
  return [internal->name hash];
}

- (NSUInteger) index
{
  if (nil == internal->parent)
    {
      return 0;
    }
  return [GSIVar(internal->parent, children) indexOfObjectIdenticalTo: self];
}

- (id) init
{
  return [self initWithKind: NSXMLInvalidKind];
}

- (id) initWithKind:(NSXMLNodeKind) kind
{
  self = [self initWithKind: kind options: 0];
  return self;
}

- (id) initWithKind: (NSXMLNodeKind)kind options: (NSUInteger)theOptions
{
  Class theSubclass = [NSXMLNode class];

GSOnceMLog(@"WARNING  the XML DOM classes are not currently implemented to a usable level, but we expect to have an implementation based on libxml2 in place for the next release.");
  if (nil == (self = [super init]))
    {
      return nil;
    }

  /*
   * We find the correct subclass for specific node kinds:
   */
  switch (kind)
    {
      case NSXMLDocumentKind:
	theSubclass = [NSXMLDocument class];
	break;

      case NSXMLElementKind:
	theSubclass = [NSXMLElement class];
	break;

      case NSXMLDTDKind:
	theSubclass = [NSXMLDTD class];
	break;

      case NSXMLEntityDeclarationKind:
      case NSXMLElementDeclarationKind:
      case NSXMLNotationDeclarationKind:
	theSubclass = [NSXMLDTDNode class];
	break;

      default:
	break;
    }

  /*
   * Check whether we are already initializing an instance of the given
   * subclass. If we are not, release ourselves and allocate a subclass instance
   * instead.
   */
  if (NO == [self isKindOfClass: theSubclass])
    {
      [self release];
      return [[theSubclass alloc] initWithKind: kind
				       options: theOptions];
    }

  /* Create holder for internal instance variables if needed.
   */
  GS_CREATE_INTERNAL(NSXMLNode)

  /* If we are initializing for the correct class, we can actually perform
   * initializations:
   */
  internal->kind = kind;
  internal->options = theOptions;
  internal->stringValue = @"";
  return self;
}

- (BOOL) isEqual: (id)other
{
  NSString	*s;
  NSArray	*c;

  if (other == (id)self)
    {
      return YES;
    }

  if (NO == [other isKindOfClass: [self class]])
    {
      return NO;
    }

  if ([(NSXMLNode*)other kind] != internal->kind)
    {
      return NO;
    }

  s = [other name];
  if (s != internal->name && NO == [s isEqual: internal->name])
    {
      return NO;
    }

  s = [other URI];
  if (s != internal->URI && NO == [s isEqual: internal->URI])
    {
      return NO;
    }

  c = [other children];
  if (c != internal->children && NO == [c isEqual: internal->children])
    {
      return NO;
    }

  return YES;
}

- (NSXMLNodeKind) kind
{
  return internal->kind;
}

- (NSUInteger) level
{
  NSUInteger	level = 0;
  NSXMLNode	*tmp = internal->parent;

  while (tmp != nil)
    {
      level++;
      tmp = GSIVar(tmp, parent);
    }
  return level;
}

- (NSString*) localName
{
  return [self notImplemented: _cmd];	// FIXME ... fetch from libxml
}

- (NSString*) name
{
  return internal->name; 
}

- (NSXMLNode*) _nodeFollowingInNaturalDirection: (BOOL)forward
{
  NSXMLNode *ancestor = internal->parent;
  NSXMLNode *candidate = nil;

  /* Node walking is a depth-first thingy. Hence, we consider children first: */
  if (0 != internal->childCount)
    {
      NSUInteger theIndex = 0;
      if (NO == forward)
	{
	  theIndex = (internal->childCount) - 1;
	}
      candidate = [internal->children objectAtIndex: theIndex];
    }

  /* If there are no children, we move on to siblings: */
  if (nil == candidate)
    {
      if (forward)
	{
	  candidate = internal->nextSibling;
	}
      else
	{
	  candidate = internal->previousSibling;
	}
    }

  /* If there are no siblings left for the receiver, we recurse down to the root
   * of the tree until we find an ancestor with further siblings: */
  while ((nil == candidate) && (nil != ancestor))
    {
      if (forward)
	{
	  candidate = [ancestor nextSibling];
	}
      else
	{
	  candidate = [ancestor previousSibling];
	}
      ancestor = GSIVar(ancestor, parent);
    }

  /* No children, no next siblings, no next siblings for any ancestor: We are
   * the last node */
  if (nil == candidate)
    {
      return nil;
    }

  /* Sanity check: Namespace and attribute nodes are skipped: */
  if ((NSXMLAttributeKind == GSIVar(candidate, kind))
    || (NSXMLNamespaceKind == GSIVar(candidate, kind)))
    {
      return [candidate _nodeFollowingInNaturalDirection: forward];
    }
  return candidate;
}

- (NSXMLNode*) nextNode
{
  return [self _nodeFollowingInNaturalDirection: YES];
}

- (NSXMLNode*) nextSibling
{
  return internal->nextSibling;
}

- (id) objectValue
{
  return internal->objectValue;
}

- (NSXMLNode*) parent
{
  return internal->parent;
}

- (NSString*) prefix
{
  return [self notImplemented: _cmd];	// FIXME ... fetch from libxml
}

- (NSXMLNode*) previousNode
{
  return [self _nodeFollowingInNaturalDirection: NO];
}

- (NSXMLNode*) previousSibling
{
  return internal->previousSibling;

}

- (NSXMLDocument*) rootDocument
{
  NSXMLNode *ancestor = internal->parent;
  /*
   * Short-circuit evaluation gurantees that the nil-pointer is not
   * dereferenced:
   */
  while ((ancestor != nil)
    && (NSXMLDocumentKind != GSIVar(ancestor, kind)))
    {
      ancestor = GSIVar(ancestor, parent);
    }
  return (NSXMLDocument*)ancestor;
}

- (NSString*) stringValue
{
  return internal->stringValue;
}

- (NSString*) URI
{
  return internal->URI;	// FIXME ... fetch from libxml
}

- (NSString*) XMLString
{
  return [self XMLStringWithOptions: 0];
}

- (NSString*) XMLStringWithOptions: (NSUInteger)options
{
  NSMutableString *returnValue = [NSMutableString string];
  NSXMLNodeKind kind = [self kind];

  if (kind == NSXMLAttributeKind)
    {
      [returnValue appendString: [self name]];
      [returnValue appendString: @"=\""];
      [returnValue appendString: [self stringValue]];
      [returnValue appendString: @"\""];
    }
  else
    {
      // for all other types, do nothing for now...
    }

  return returnValue;
}

- (void) setObjectValue: (id)value
{
  ASSIGN(internal->objectValue, value);
}

- (void) setName: (NSString *)name
{
  if (NSXMLInvalidKind != internal->kind)
    {
      ASSIGNCOPY(internal->name, name);
    }
}

- (void) setStringValue: (NSString*)string
{
  [self setStringValue: string resolvingEntities: NO];
}

- (void) setURI: (NSString*)URI
{
  if (NSXMLInvalidKind != internal->kind)
    {
      ASSIGNCOPY(internal->URI, URI);
    }
}

- (void) setStringValue: (NSString*)string resolvingEntities: (BOOL)resolve
{
  if (resolve == NO)
    {
      ASSIGNCOPY(internal->stringValue, string);
    }
  else
    {
      // need to actually resolve entities...
      ASSIGNCOPY(internal->stringValue, string);
    }
  if (nil == internal->stringValue)
    {
      internal->stringValue = @"";	// string value may not be nil
    }
}

- (NSString*) XPath
{
  return [self notImplemented: _cmd];
}

 - (NSArray*) nodesForXPath: (NSString*)xpath error: (NSError**)error
{
  return [self notImplemented: _cmd];
}

 - (NSArray*) objectsForXQuery: (NSString*)xquery
		     constants: (NSDictionary*)constants
		         error: (NSError**)error
{
  return [self notImplemented: _cmd];
}

- (NSArray*) objectsForXQuery: (NSString*)xquery error: (NSError**)error
{
  return [self notImplemented: _cmd];
}

@end

