//
//  MGMXMLNode.m
//  MGMXML
//
//  Created by Mr. Gecko on 9/22/10.
//  Copyright (c) 2011 Mr. Gecko's Media (James Coleman). https://mrgeckosmedia.com/
//
//  Permission to use, copy, modify, and/or distribute this software for any purpose
//  with or without fee is hereby granted, provided that the above copyright notice
//  and this permission notice appear in all copies.
//
//  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
//  REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
//  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
//  OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
//  DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
//  ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//

#import "MGMXMLNode.h"
#import "MGMXMLAddons.h"
#import "MGMXMLDocument.h"
#import "MGMXMLElement.h"
#import "MGMXMLNodeOptions.h"

NSString * const MGMXMLErrorDomain = @"MGMXMLErrorDomain";
NSString * const MGMXMLLastError = @"MGMXMLLastError";

static NSMutableDictionary *MGMXMLInfo = nil;

static void MGMXMLErrorHandler(void *userData, xmlErrorPtr error) {
	NSAutoreleasePool *pool = [NSAutoreleasePool new];;
	if (error==NULL) {
		[MGMXMLInfo removeObjectForKey:MGMXMLLastError];
	} else {
		[MGMXMLInfo setObject:[NSValue valueWithXMLError:error] forKey:MGMXMLLastError];
	}
	[pool drain];
}

@implementation MGMXMLNode
+ (void)initialize {
	if (MGMXMLInfo==nil) {
		MGMXMLInfo = [NSMutableDictionary new];
		
		initGenericErrorDefaultFunc(NULL);
		xmlSetStructuredErrorFunc(NULL, MGMXMLErrorHandler);
		xmlKeepBlanksDefault(0);
	}
}

- (id)init {
	if ((self = [super init])) {
		commonXML = NULL;
		namespaceXML = NULL;
		parentNode = NULL;
		type = MGMXMLInvalidKind;
	}
	return self;
}
+ (id)nodeWithTypeXMLPtr:(xmlTypPtr)theXMLPtr {
	return [[[self alloc] initWithTypeXMLPtr:theXMLPtr] autorelease];
}
- (id)initWithTypeXMLPtr:(xmlTypPtr)theXMLPtr {
	if ((self = [self init])) {
		if (theXMLPtr==NULL) {
			//NSLog(@"NULL XMLPtr");
			[self release];
			self = nil;
		} else {
			if (theXMLPtr->type==MGMXMLNamespaceKind) {
				xmlNsPtr xmlPtr = (xmlNsPtr)theXMLPtr;
				if (xmlPtr->_private!=NULL) {
					[self release];
					self = nil;
					return [(MGMXMLNode *)xmlPtr->_private retain];
				}
			} else {
				xmlComPtr comXML = (xmlComPtr)theXMLPtr;
				if (comXML->_private!=NULL) {
					[self release];
					self = nil;
					return [(MGMXMLNode *)comXML->_private retain];
				}
			}
			[self setTypeXMLPtr:theXMLPtr];
		}
	}
	return self;
}
- (void)clearParent {
	parentNode = NULL;
}
- (void)freeXML {
	if (namespaceXML!=NULL) {
		namespaceXML->_private = NULL;
		
		if (parentNode==NULL)
			xmlFreeNs(namespaceXML);
		namespaceXML = NULL;
		parentNode = NULL;
	}
	if (commonXML!=NULL) {
		commonXML->_private = NULL;
		if (type!=MGMXMLDocumentKind)
			[self releaseDocument];
		
		if (commonXML->parent==NULL) {
			if (type==MGMXMLDocumentKind) {
				xmlNodePtr child = commonXML->children;
				while (child!=NULL) {
					xmlNodePtr nextChild = child->next;
					if (child->type==MGMXMLElementKind) {
						if (child->prev!=NULL)
							child->prev->next = child->next;
						if (child->next!=NULL)
							child->next->prev = child->prev;
						if (commonXML->children==child)
							commonXML->children = child->next;
						if (commonXML->last==child)
							commonXML->last = child->prev;
						[[self class] freeNode:child];
					}
					child = nextChild;
				}
				xmlFreeDoc(MGMXMLDocPtr);
			} else if (type==MGMXMLAttributeKind) {
				xmlFreeProp(MGMXMLAttrPtr);
			} else if (type==MGMXMLDTDKind) {
				xmlFreeDtd(MGMXMLDTDPtr);
			} else {
				[[self class] freeNode:MGMXMLNodePtr];
			}
		}
		commonXML = NULL;
	}
	type = MGMXMLInvalidKind;
}
- (void)dealloc {
	[self freeXML];
	[super dealloc];
}
+ (void)stripDocumentFromAttribute:(xmlAttrPtr)theAttribute {
	xmlNodePtr child = theAttribute->children;
	while (child!=NULL) {
		child->doc = NULL;
		child = child->next;
	}
	theAttribute->doc = NULL;
}
+ (void)stripDocumentFromNode:(xmlNodePtr)theNode {
	xmlAttrPtr attribute = theNode->properties;
	while (attribute!=NULL) {
		[self stripDocumentFromAttribute:attribute];
		attribute = attribute->next;
	}
	
	xmlNodePtr child = theNode->children;
	while (child!=NULL) {
		[self stripDocumentFromNode:child];
		child = child->next;
	}
	theNode->doc = NULL;
}
+ (void)removeAttributesFromNode:(xmlNodePtr)theNode {
	xmlAttrPtr attribute = theNode->properties;
	while (attribute!=NULL) {
		xmlAttrPtr nextAttribute = attribute->next;
		
		if (attribute->_private==NULL) {
			xmlFreeProp(attribute);
		} else {
			attribute->parent = NULL;
			attribute->prev = NULL;
			attribute->next = NULL;
			if (attribute->doc!=NULL) [self stripDocumentFromAttribute:attribute];
		}
		attribute = nextAttribute;
	}
	theNode->properties = NULL;
}
+ (void)removeNamespacesFromNode:(xmlNodePtr)theNode {
	xmlNsPtr namespace = theNode->nsDef;
	while (namespace!=NULL){
		xmlNsPtr nextNamespace = namespace->next;
		if (namespace->_private!=NULL) {
			[(MGMXMLNode *)namespace->_private clearParent];
			namespace->next = NULL;
		} else {
			xmlFreeNs(namespace);
		}
		namespace = nextNamespace;
	}
	theNode->nsDef = NULL;
	theNode->ns = NULL;
}
+ (void)removeChildrenFromNode:(xmlNodePtr)theNode {
	xmlNodePtr child = theNode->children;
	while (child!=NULL) {
		xmlNodePtr nextChild = child->next;
		[self freeNode:child];
		child = nextChild;
	}
	theNode->children = NULL;
	theNode->last = NULL;
}
+ (void)freeNode:(xmlNodePtr)theNode {
	if (![[self class] isNode:theNode->type]) {
		NSLog(@"Cannot free node as it is the wrong type %d.", theNode->type);
	} else {
		if (theNode->_private==NULL) {
			[self removeAttributesFromNode:theNode];
			[self removeNamespacesFromNode:theNode];
			[self removeChildrenFromNode:theNode];
			
			xmlFreeNode(theNode);
		} else {
			theNode->parent = NULL;
			theNode->prev = NULL;
			theNode->next = NULL;
			if (theNode->doc!=NULL) {
				[(MGMXMLNode *)theNode->_private releaseDocument];
				[self stripDocumentFromNode:theNode];
			}
		}
	}
}

- (void)setTypeXMLPtr:(xmlTypPtr)theXMLPtr {
	[self freeXML];
	type = theXMLPtr->type;
	if (type==XML_HTML_DOCUMENT_NODE)
		type = MGMXMLDocumentKind;
	if (type==MGMXMLNamespaceKind) {
		namespaceXML = (xmlNsPtr)theXMLPtr;
		namespaceXML->_private = self;
	} else {
		commonXML = (xmlComPtr)theXMLPtr;
		commonXML->_private = self;
		if (type==MGMXMLDocumentKind && [self isMemberOfClass:[MGMXMLNode class]])
			self->isa = [MGMXMLDocument class];
		else if (type==MGMXMLElementKind && [self isMemberOfClass:[MGMXMLNode class]])
			self->isa = [MGMXMLElement class];
		if (type!=MGMXMLDocumentKind)
			documentNode = [[MGMXMLDocument alloc] initWithTypeXMLPtr:(xmlTypPtr)commonXML->doc];
	}
}
- (void)setDocument:(MGMXMLDocument *)theDocument {
	[documentNode release];
	documentNode = [theDocument retain];
}
- (void)releaseDocument {
	[documentNode release];
	documentNode = nil;
}
+ (BOOL)isNode:(MGMXMLNodeKind)theType {
	switch (theType) {
		case MGMXMLElementKind:
		case MGMXMLProcessingInstructionKind:
		case MGMXMLCommentKind:
		case MGMXMLTextKind:
		case XML_CDATA_SECTION_NODE:
			return YES;
			break;
		default:
			break;
	}
	return NO;
}
- (BOOL)isNode {
	return [[self class] isNode:type];
}
+ (NSError *)lastError {
	if ([MGMXMLInfo objectForKey:MGMXMLLastError]!=nil) {
		xmlErrorPtr lastError = [[MGMXMLInfo objectForKey:MGMXMLLastError] xmlErrorValue];
		NSString *description = [[NSString stringWithUTF8String:lastError->message] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
		return [NSError errorWithDomain:MGMXMLErrorDomain code:lastError->code userInfo:[NSDictionary dictionaryWithObject:description forKey:NSLocalizedDescriptionKey]];
	}
	return nil;
}
- (NSError *)lastError {
	return [[self class] lastError];
}

- (id)initWithKind:(MGMXMLNodeKind)kind {
	if (kind==MGMXMLDocumentKind) {
		[self release];
		return [[MGMXMLDocument alloc] initWithRootElement:nil];
	} else if (kind==MGMXMLElementKind) {
		return [self initWithTypeXMLPtr:(xmlTypPtr)xmlNewNode(NULL, NULL)];
	} else if (kind==MGMXMLAttributeKind) {
		return [self initWithTypeXMLPtr:(xmlTypPtr)xmlNewProp(NULL, (const xmlChar *)"", NULL)];
	} else if (kind==MGMXMLNamespaceKind) {
		return [self initWithTypeXMLPtr:(xmlTypPtr)xmlNewNs(NULL, NULL, NULL)];
	} else if (kind==MGMXMLProcessingInstructionKind) {
		return [self initWithTypeXMLPtr:(xmlTypPtr)xmlNewPI(NULL, NULL)];
	} else if (kind==MGMXMLCommentKind) {
		return [self initWithTypeXMLPtr:(xmlTypPtr)xmlNewComment(NULL)];
	} else if (kind==MGMXMLTextKind) {
		return [self initWithTypeXMLPtr:(xmlTypPtr)xmlNewText(NULL)];
	}
	[self release];
	return nil;
}

- (MGMXMLNodeKind)kind {
	return type;
}
- (xmlComPtr)commonXML {
	return commonXML;
}
- (xmlNsPtr)nameSpaceXML {
	return namespaceXML;
}
- (void)setName:(NSString *)name {
	if (type==MGMXMLNamespaceKind) {
		if (namespaceXML->prefix!=NULL)
			xmlFree((xmlChar *)namespaceXML->prefix);
		namespaceXML->prefix = xmlStrdup([name xmlString]);
	} else {
		xmlNodeSetName(MGMXMLNodePtr, [name xmlString]);
	}
}
- (NSString *)name {
	if (type==MGMXMLNamespaceKind) {
		if (namespaceXML->prefix!=NULL)
			return [NSString stringWithXMLString:namespaceXML->prefix];
	} else {
		if (MGMXMLNodePtr->name!=NULL)
			return [NSString stringWithXMLString:(xmlChar *)MGMXMLNodePtr->name];
	}
	return nil;
}
- (void)setStringValue:(NSString *)string {
	if (type==MGMXMLNamespaceKind) {
		namespaceXML->href = [string xmlString];
	} else if ([self isNode] || type==MGMXMLAttributeKind) {
		xmlChar *contentString = xmlEncodeEntitiesReentrant(commonXML->doc, [string xmlString]);
		xmlNodeSetContent(MGMXMLNodePtr, contentString);
		xmlFree(contentString);
	}
}
- (NSString *)stringValue {
	if (type==MGMXMLNamespaceKind) {
		return [NSString stringWithXMLString:namespaceXML->href];
	} else if ([self isNode] || type==MGMXMLAttributeKind) {
		xmlChar *contentString = xmlNodeGetContent(MGMXMLNodePtr);
		NSString *stringValue = [NSString stringWithXMLString:contentString];
		xmlFree(contentString);
		return stringValue;
	}
	return nil;
}

- (MGMXMLDocument *)rootDocument {
	if (MGMXMLNodePtr->doc!=NULL)
		return [MGMXMLDocument nodeWithTypeXMLPtr:(xmlTypPtr)MGMXMLNodePtr->doc];
	return nil;
}
- (MGMXMLNode *)parent {
	return [MGMXMLNode nodeWithTypeXMLPtr:(xmlTypPtr)MGMXMLNodePtr->parent];
}
- (MGMXMLNode *)childAtIndex:(NSUInteger)index {
	if (type==MGMXMLNamespaceKind)
		return nil;
	
	NSUInteger i = 0;
	xmlNodePtr child = commonXML->children;
	while (child!=NULL) {
		if (i==index)
			return [MGMXMLNode nodeWithTypeXMLPtr:(xmlTypPtr)child];
		i++;
		child = child->next;
	}
	return nil;
}
+ (void)detatchAttribute:(xmlAttrPtr)theAttribute fromNode:(xmlNodePtr)theNode {
	if (theAttribute->prev==NULL && theAttribute->next==NULL) {
		theNode->properties = NULL;
	} else if (theAttribute->prev==NULL) {
		theNode->properties = theAttribute->next;
		theAttribute->next->prev = NULL;
	} else if (theAttribute->next==NULL) {
		theAttribute->prev->next = NULL;
	} else {
		theAttribute->prev->next = theAttribute->next;
		theAttribute->next->prev = theAttribute->prev;
	}
	theAttribute->parent = NULL;
	theAttribute->prev = NULL;
	theAttribute->next = NULL;
	if (theAttribute->doc!=NULL) [[self class] stripDocumentFromAttribute:theAttribute];
}
- (void)detach {
	if (type==MGMXMLNamespaceKind) {
		if (parentNode!=NULL) {
			xmlNsPtr previousNamespace = NULL;
			xmlNsPtr currentNamespace = parentNode->nsDef;
			while (currentNamespace!=NULL) {
				if (currentNamespace==namespaceXML) {
					if (previousNamespace!=NULL)
						previousNamespace->next = currentNamespace->next;
					else
						parentNode->nsDef = currentNamespace->next;
					break;
				}
				previousNamespace = currentNamespace;
				currentNamespace = currentNamespace->next;
			}
			namespaceXML->next = NULL;
			if (parentNode->ns==namespaceXML)
				parentNode->ns = NULL;
			parentNode = NULL;
		}
		return;
	}
	
	if (commonXML->parent==NULL) return;
	
	if (type==MGMXMLAttributeKind) {
		[[self class] detatchAttribute:MGMXMLAttrPtr fromNode:MGMXMLAttrPtr->parent];
	} else if ([self isNode]) {
		if (commonXML->prev==NULL && commonXML->next==NULL) {
			commonXML->parent->children = NULL;
			commonXML->parent->last = NULL;
		} else if (commonXML->prev==NULL) {
			commonXML->parent->children = commonXML->next;
			commonXML->next->prev = NULL;
		} else if (commonXML->next==NULL) {
			commonXML->parent->last = commonXML->prev;
			commonXML->prev->next = NULL;
		} else {
			commonXML->prev->next = commonXML->next;
			commonXML->next->prev = commonXML->prev;
		}
		commonXML->parent = NULL;
		commonXML->prev = NULL;
		commonXML->next = NULL;
		if (commonXML->doc!=NULL) [[self class] stripDocumentFromNode:MGMXMLNodePtr];
	}
}

+ (NSString *)localNameForName:(NSString *)name {
	if (name!=nil && [name length]>0) {
		NSRange range = [name rangeOfString:@":"];
		if (range.location!=NSNotFound)
			return [name substringFromIndex:range.location+range.length];
		else
			return name;
	}
	return nil;
}
+ (NSString *)prefixForName:(NSString *)name {
	if (name!=nil && [name length]>0) {
		NSRange range = [name rangeOfString:@":"];
		if (range.location!=NSNotFound)
			return [name substringToIndex:range.location];
	}
	return nil;
}

- (NSString *)description {
	return [self XMLString];
}
- (NSString *)XMLString {
	return [self XMLStringWithOptions:0];
}
- (NSString *)XMLStringWithOptions:(NSUInteger)options {
	if(options & MGMXMLNodeCompactEmptyElement)
		xmlSaveNoEmptyTags = 0;
	else
		xmlSaveNoEmptyTags = 1;
	
	int format = 0;
	if (options & MGMXMLNodePrettyPrint) {
		format = 1;
		xmlIndentTreeOutput = 1;
	}
	
	xmlBufferPtr bufferPtr = xmlBufferCreate();
	if (type==MGMXMLNamespaceKind)
		xmlNodeDump(bufferPtr, NULL, MGMXMLNodePtr, 0, format);
	else
		xmlNodeDump(bufferPtr, commonXML->doc, MGMXMLNodePtr, 0, format);
	
	NSString *result = [NSString stringWithXMLString:bufferPtr->content];
	if (type!=MGMXMLTextKind)
		result = [result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
	xmlBufferFree(bufferPtr);
	return result;
}

- (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error {
	BOOL shouldRemoveDocument = NO;
	xmlDocPtr document;
	if ([self isNode]) {
		document = MGMXMLNodePtr->doc;
		if (document==NULL) {
			shouldRemoveDocument = YES;
			document = xmlNewDoc(NULL);
			xmlDocSetRootElement(document, MGMXMLNodePtr);
		}
	} else if (type==MGMXMLDocumentKind) {
		document = MGMXMLDocPtr;
	} else {
		return nil;
	}
	
	xmlXPathContextPtr xPathContext = xmlXPathNewContext(document);
	xPathContext->node = MGMXMLNodePtr;
	
	xmlNodePtr rootNode = document->children;
	if (rootNode!=NULL) {
		xmlNsPtr namespace = rootNode->nsDef;
		while (namespace!=NULL) {
			xmlXPathRegisterNs(xPathContext, namespace->prefix, namespace->href);
			namespace = namespace->next;
		}
	}
	
	xmlXPathObjectPtr xPathObject = xmlXPathEvalExpression([xpath xmlString], xPathContext);
	
	NSMutableArray *nodes = [NSMutableArray array];
	
	if (xPathObject==NULL) {
		if (error!=nil) *error = [self lastError];
		nodes = nil;
	} else {
		int count = xmlXPathNodeSetGetLength(xPathObject->nodesetval);
		if (count!=0) {
			for (int i=0; i<count; i++)
				[nodes addObject:[MGMXMLNode nodeWithTypeXMLPtr:(xmlTypPtr)xPathObject->nodesetval->nodeTab[i]]];
		}
	}
	
	if (xPathObject) xmlXPathFreeObject(xPathObject);
	if (xPathContext) xmlXPathFreeContext(xPathContext);
	
	if (shouldRemoveDocument) {
		xmlUnlinkNode(MGMXMLNodePtr);
		xmlFreeDoc(document);
		[[self class] stripDocumentFromNode:MGMXMLNodePtr];
	}
	
	return nodes;
}
@end