//
//  XTHtmlTag.m
//  TadsTerp
//
//  Created by Rune Berg on 29/03/14.
//  Copyright (c) 2014 Rune Berg. All rights reserved.
//

#import "XTHtmlTag.h"
#import "XTHtmlTag_private.h"
#import "XTLogger.h"
#import "XTConverter.h"
#import "XTOutputFormatterProtocol.h"
#import "XTStringUtils.h"
#import "XTOutputTextParserProtocol.h"
#import "XTOutputTextParserHtml.h"
#import "XTBaseTextHandler.h"
#import "XTAllocDeallocCounter.h"
#import "XTPair.h"


@interface XTHtmlTag ()

@end


@implementation XTHtmlTag

static XTLogger* logger;
static XTConverter *converter;

static NSUInteger nextUnqueId = 1;

@synthesize uniqueId = _uniqueId;

OVERRIDE_ALLOC_FOR_COUNTER
OVERRIDE_DEALLOC_FOR_COUNTER

+ (NSString *)name
{
	return @"OOPS! XTHtmlTag name not overridden";
}

+ (NSArray<NSString *>*)nameSynonyms
{
	// tags generally don't have synonyms:
	return [NSArray array];
}

+ (BOOL)standalone
{
	return YES;
}

- (BOOL)blockLevel
{
	return NO;
}

- (BOOL)blockLevelSpacingBefore
{
	return NO;
}

- (BOOL)blockLevelSpacingAfter
{
	return NO;
}

+ (BOOL)forT2
{
	return YES;
}

+ (BOOL)forT3
{
	return YES;
}

//TODO various methods should test for nil self.attributes

+ (void)initialize
{
	logger = [XTLogger loggerForClass:[XTHtmlTag class]];
		//TODO find concrete class?
	converter = [XTConverter converter];
}

- (id)init
{
    self = [super init];
    if (self) {
		_attributesDict = [NSMutableDictionary dictionary];
		_attributesArray = [NSMutableArray array];
		_hasFormatted = NO;
		_uniqueId = nextUnqueId++;
	}
	return self;
}

- (NSString *)name
{
	NSString *res = [[self class] name];
	return res;
}

- (BOOL)isBlockLevel
{
	BOOL res = [self blockLevel];
	return res;
}

- (XTHtmlTagContainer *)getContainer
{
	return self.container;
}

- (void)removeFromContainer
{
	//XT_DEF_SELNAME;
	//XT_WARN_1(@"%@", self.name);

	if (self.container == nil) {
		XT_DEF_SELNAME;
		XT_ERROR_1(@"%@ has no container", self.name);
	}

	[self.container removeFromContents:self];
	self.container = nil;
}

- (XTHtmlTagContainer *)findCorrectContainer:(XTHtmlTagContainer *)defaultContainer
{
	return defaultContainer;
}

- (void)onParsing:(NSObject<XTOutputTextParserProtocol> *)parser
{
	[parser appendTagToCurrentContainer:self];
}

- (void)onEndTag:(NSObject<XTOutputTextParserProtocol> *)parser
{
	/* process the normal end tag */
	[parser endNormalTag:self];
}

- (BOOL)isReadyToFormat
{
	//TODO !!! real impl, overrides
	return YES;
}

- (void)assertHasContainer
{
	if (self.container == nil) {
		XT_DEF_SELNAME;
		XT_ERROR_0(@"self.container == nil");
	}
}

// see htmltags.cpp, CHtmlTag::get_next_fmt_tag
- (XTHtmlTag *)getNextTagToFormat:(NSObject<XTOutputFormatterProtocol> *)formatter
					  textHandler:(XTBaseTextHandler *)textHandler
					 shouldFormat:(BOOL)shouldFormat
{
	[self assertHasContainer];
	
	/* if I have a sibling, it's the next tag */
	if (self.nextSibling != nil) {
		return self.nextSibling;
	}
	
	/*
	 *   That's the end of the contents list for my container - tell the
	 *   container we're leaving, and move on to its next sibling.  If we
	 *   don't have a container, there are no more tags left to format.
	 */
	if (self.container != nil) {
		XTHtmlTag *next;
		XTHtmlTagContainer *container;
		
		/*
		 *   Find the container's next sibling.  If the container itself
		 *   doesn't have a next sibling, find its container's next
		 *   sibling, and so on until we find a sibling or run out of
		 *   containers.
		 */
		for (container = self.container; container != nil; container = container.container) {
			[container assertHasContainer];
			
			/* get the container's next sibling, and stop if it has one */
			next = container.nextSibling;
			if (next != nil) {
				break;
			}
		}
		
		/*
		 *   If there is indeed another tag to format, or the tag is
		 *   marked as "closed", tell our container that we're done
		 *   formatting it.  If there isn't anything left, and the tag
		 *   hasn't been closed yet, do NOT leave the container yet --
		 *   more document parsing may occur that adds more items to the
		 *   current container, in which case we'll still want the current
		 *   container's settings in effect when we come back to do more
		 *   formatting.  Hence, only exit the container if we have more
		 *   work to do immediately, in which case we know that we'll
		 *   never add anything more to our container.  Note that we need
		 *   to inform as many containers as we're actually exiting, if
		 *   we're exiting more than one level, so iterate up the
		 *   containment tree until we find a container with a next
		 *   sibling.
		 */
		for (container = self.container; container != nil; container = container.container) {
			[container assertHasContainer];

			/*
			 *   if this one isn't closed yet, and there's not another
			 *   sibling, it's still open
			 */
			if (next == nil && ! container.closed) {
				//TODO !!! might cause a problem for tags we don't format until closed:
				
				// Remove some of container's current children - they've already been formatted and should not be formatted again:
				[container removeChildrenUntilFirstOpenContainer];
				
				break;
			}
			
			/* tell this one we're exiting it */
			if ([formatter willProcessTag:container]) {
				[container formatExit:formatter textHandler:textHandler];
			}
			
			[container assertHasContainer];

			/* stop if this one has a sibling */
			if (container.nextSibling != nil) {
				break;
			}
		}
		
		/* return the next tag after our container */
		return next;
	}
	
	/*
	 *   there's nothing after us, and we have no container, so this is
	 *   the end of the line
	 */
	return nil;
}

- (void)preFormatForBlockLevel:(NSObject<XTOutputFormatterProtocol> *)formatter
				   textHandler:(XTBaseTextHandler *)textHandler
{
	if (self.isBlockLevel) {
		NSArray *formattedElements = [formatter handleBlockLevelTagEntry:self];
		[textHandler receiveFormattedElements:formattedElements];
	}
}

- (void)checkNotHasFormatted
{
	if (self.hasFormatted) {
		XT_DEF_SELNAME;
		XT_ERROR_2(@"hasFormatted=YES <%@> (uid=%lu)", self.name, self.uniqueId);
	}
}

- (void)format:(NSObject<XTOutputFormatterProtocol> *)formatter
   textHandler:(XTBaseTextHandler *)textHandler
{
	XT_DEF_SELNAME;
	NSString *actualClassName = [self.class name];
	XT_WARN_1(@"not overridden for class %@", actualClassName);
	// ... and do nothing
}

- (void)noteHasFormatted
{
	self.hasFormatted = YES;
}

- (void)postFormatForBlockLevel:(NSObject<XTOutputFormatterProtocol> *)formatter
					textHandler:(XTBaseTextHandler *)textHandler
{
	if (self.isBlockLevel) {
		NSArray *formattedElements = [formatter handleBlockLevelTagExit:self];
		[textHandler receiveFormattedElements:formattedElements];
	}
}

- (XTFormattingSpecification *)makeFormattingSpecificationFrom:(XTFormattingSpecification *)formattingSpec
{
	XTFormattingSpecification *res = [XTFormattingSpecification specificationFrom:formattingSpec];
	return res;
}

- (XTTextAlignMode)getTextAlignModeFrom:(XTTextAlignMode)currentTextAlignMode
{
	return currentTextAlignMode;
}

//TODO rm if not needed - try to eliminate
- (XTTextAlignMode)getTextAlignMode:(XTTextAlignMode)currentTextAlignMode
			   defaultToLeftAligned:(BOOL)defaultToLeftAligned
{
	XTTextAlignMode res;
	
	if (defaultToLeftAligned) {
		res = [self getTextAlignModeWithDefault:XT_TEXT_ALIGN_LEFT];
	} else {
		res = [self getTextAlignModeWithDefault:currentTextAlignMode];
	}
	
	return res;
}

- (XTTextAlignMode)getTextAlignModeWithDefault:(XTTextAlignMode)defaultTextAlignMode
{
	XT_DEF_SELNAME;
	
	XTTextAlignMode res;
	
	NSString *attrNameAlign = @"align";
	
	if (! [self hasAttribute:attrNameAlign]) {
		res = defaultTextAlignMode;
	} else if ([self hasAttribute:attrNameAlign withCaseInsensitiveValue:@"right"]) {
		res = XT_TEXT_ALIGN_RIGHT;
	} else if ([self hasAttribute:attrNameAlign withCaseInsensitiveValue:@"center"]) {
		res = XT_TEXT_ALIGN_CENTER;
	} else if ([self hasAttribute:attrNameAlign withCaseInsensitiveValue:@"left"]) {
		res = XT_TEXT_ALIGN_LEFT;
	} else if ([self hasAttribute:attrNameAlign withCaseInsensitiveValue:@"justify"]) {
		res = XT_TEXT_ALIGN_JUSTIFY;
	} else {
		NSString *alignAttr = [self attributeAsString:@"align"];
		XT_ERROR_1(@"unknown align attr value \"%@\"", alignAttr)
		res = XT_TEXT_ALIGN_LEFT;
	}
	
	return res;
}

- (void)addAttribute:(NSString *)attributeName value:(NSString *)value
{
	[self.attributesDict setObject:value forKey:attributeName];
	
	XTPair *attrNameAndValue = [XTPair pairWithFirstObject:attributeName secondObject:value];
	[self.attributesArray addObject:attrNameAndValue];
}

- (void)replaceAttributes:(NSArray *)attributesArray
{
	self.attributesArray = [NSMutableArray arrayWithArray:attributesArray];
	
	self.attributesDict = [NSMutableDictionary dictionary];
	for (XTPair *attrNameAndValue in attributesArray) {
		NSString *attrName = (NSString *)attrNameAndValue.firstObject;
		NSObject *attrValue = attrNameAndValue.secondObject;
		if (attrValue == nil) {
			attrValue = [NSNull null];
		}
		self.attributesDict[attrName] = attrValue;
	}
}

- (BOOL)hasAttribute:(NSString *)attributeName
{
	BOOL res = ([self attributeAsString:attributeName] != nil);
	return res;
}

- (BOOL)attributeNameAndValue:(XTPair *)attrNameAndValue
					  hasName:(NSString *)name
{
	NSString *nameLowercase = [name lowercaseString];
	NSString *attrName = (NSString *)attrNameAndValue.firstObject;
	attrName = [attrName lowercaseString];
	BOOL res = [attrName isEqual:nameLowercase];
	return res;
}

- (BOOL)attributeNameAndValue:(XTPair *)attrNameAndValue
					  hasName:(NSString *)name
	 withCaseInsensitiveValue:(NSString *)value
{
	NSString *nameLowercase = [name lowercaseString];
	NSString *attrName = (NSString *)attrNameAndValue.firstObject;
	attrName = [attrName lowercaseString];
	
	BOOL res = NO;
	
	if ([attrName isEqual:nameLowercase]) {
		NSString *valueLowerCase = [value lowercaseString];
		NSString *attrValue = (NSString *)attrNameAndValue.secondObject;
		attrValue = [attrValue lowercaseString];

		res = [attrValue isEqual:valueLowerCase];
	}

	return res;
}

- (BOOL)attributeNameAndValue:(XTPair *)attrNameAndValue
					  hasName:(NSString *)name
	   withCaseSensitiveValue:(NSString *)value
{
	NSString *nameLowercase = [name lowercaseString];
	NSString *attrName = (NSString *)attrNameAndValue.firstObject;
	attrName = [attrName lowercaseString];
	
	BOOL res = NO;
	
	if ([attrName isEqual:nameLowercase]) {
		NSString *attrValue = (NSString *)attrNameAndValue.secondObject;

		res = [attrValue isEqual:value];
	}

	return res;
}

- (BOOL)hasAttribute:(NSString *)attributeName withCaseInsensitiveValue:(NSString *)value
{
	BOOL res = NO;
	
	NSString *actualValue = [self attributeAsString:attributeName];
	if (actualValue != nil) {
		actualValue = [actualValue lowercaseString];
		value = [value lowercaseString];
		if ([actualValue isEqualToString:value]) {
			res = YES;
		}
	}
	
	return res;
}

- (NSString *)attributeAsString:(NSString *)attributeName
{
	NSString *attributeValue = self.attributesDict[[attributeName lowercaseString]];
	return attributeValue;
}

- (BOOL)attribute:(NSString *)attributeName asOptionalSign:(NSInteger*)sign andUint:(NSUInteger*)uint
{
	id attributeValue = self.attributesDict[[attributeName lowercaseString]];
		//TODO trim
	BOOL res = NO;
	if (attributeValue != nil && attributeValue != [NSNull null]) {
		NSString *attributeValueStr = attributeValue;
		if (attributeValueStr.length >= 1) {
			NSInteger tempSign = 0;
			unichar ch = [attributeValueStr characterAtIndex:0];
			if (ch == '-') {
				tempSign = -1;
			} else if (ch == '+') {
				tempSign = 1;
			}
			NSUInteger uintStartIndex = (tempSign == 0 ? 0 : 1);
			NSString *uintStr = [attributeValueStr substringFromIndex:uintStartIndex];
			NSUInteger tempUint;
			if ([converter toUInteger:uintStr uinteger:&tempUint]) {
				*sign = tempSign;
				*uint = tempUint;
				res = YES;
			}
		}
	}
	return res;
}

//TODO rewrite like above, to communicate error
//TODO use
- (NSUInteger)attributeAsUInt:(NSString *)attributeName
{
	NSUInteger res = 0;
	
	id attributeValue = self.attributesDict[[attributeName lowercaseString]];
	//TODO trim

	BOOL ok = YES;
	if (attributeValue != nil && attributeValue != [NSNull null]) {
		NSString *attributeValueNumPrefix = [XTStringUtils numericPrefix:attributeValue];
		if (! [converter toUInteger:attributeValueNumPrefix uinteger:&res]) {
			ok = NO;
		}
	}
	if (! ok) {
		XT_DEF_SELNAME;
		XT_ERROR_1(@"failed for value \"%@\"", attributeValue);
		//TODO emit some kind of error to caller?
	}
	
	return res;
}

- (NSNumber *)attributeAsNumber:(NSString *)attributeName
{
	NSNumber *res = nil;
	NSString *attributeValue = [self trimmedAttributeValue:attributeName];

	if (attributeValue != nil) {
		NSInteger intVal;
		if ([converter toInteger:attributeValue integer:&intVal]) {
			res = [NSNumber numberWithInteger:intVal];
		} else {
			XT_DEF_SELNAME;
			XT_ERROR_1(@"failed for value \"%@\"", attributeValue);
		}
	}
	
	return res;
}

- (NSNumber *)attributeAsNumberWithOptionalSuffix:(NSString *)attributeName
{
	NSNumber *res = nil;
	NSString *attributeValue = [self trimmedAttributeValue:attributeName];

	if (attributeValue != nil) {
		NSInteger intVal;
		if ([converter toIntegerWithOptionalSuffix:attributeValue integer:&intVal]) {
			res = [NSNumber numberWithInteger:intVal];
		} else {
			XT_DEF_SELNAME;
			XT_ERROR_1(@"failed for value \"%@\"", attributeValue);
		}
	}
	
	return res;
}

- (NSNumber *)attributeAsPercentage:(NSString *)attributeName
{
	NSNumber *res = nil;
	NSString *attributeValue = [self trimmedAttributeValue:attributeName];
	
	if (attributeValue != nil) {
		if ([XTStringUtils string:attributeValue endsWithChar:'%']) {
			NSString *prefix = [XTStringUtils withoutLastChar:attributeValue];
			prefix = [XTStringUtils trimLeadingAndTrailingWhitespace:prefix];
			NSInteger intVal;
			if ([converter toInteger:prefix integer:&intVal]) {
				res = [NSNumber numberWithInteger:intVal];
			} else {
				XT_DEF_SELNAME;
				XT_ERROR_1(@"failed for value \"%@\"", attributeValue);
			}
		}
	}
	
	return res;
}

//TODO unit test
- (NSArray *)attributeAsCommaSeparatedStrings:(NSString *)attributeName
{
	NSMutableArray *res = [NSMutableArray array];
	
	NSString *attributeValue = self.attributesDict[[attributeName lowercaseString]];

	if (attributeValue != nil && attributeValue.length >= 1) {
		NSArray *rawStrings = [attributeValue componentsSeparatedByString:@","];
		for (NSString *rawS in rawStrings) {
			NSString *trimmedS = [XTStringUtils trimLeadingAndTrailingWhitespace:rawS];
			[res addObject:trimmedS];
		}
	}
	return res;
}

/*TODO keep?
- (NSInteger)attributeAsInt:(NSString *)attributeName
{
	NSInteger res = 0;
	
	id attributeValue = self.attributes[[attributeName lowercaseString]];
	//TODO trim
	
	BOOL ok = YES;
	if (attributeValue != nil && attributeValue != [NSNull null]) {
		if (! [converter toInteger:attributeValue integer:&res]) {
			ok = NO;
		}
	}
	if (! ok) {
		XT_DEF_SELNAME;
		XT_ERROR_1(@"attributeAsInt failed for value \"%@\"", attributeValue);
		//TODO emit some kind of error to caller
	}
	
	return res;
}*/

//TODO use!
- (NSString *)trimmedAttributeValue:(NSString *)attributeName
{
	NSString *res = nil;
	 
	id attributeValue = self.attributesDict[[attributeName lowercaseString]];

	if (attributeValue != nil && attributeValue != [NSNull null]) {
		res = [XTStringUtils trimLeadingAndTrailingWhitespace:(NSString *)attributeValue];
	}
	
	return res;
}

- (BOOL)isStandalone
{
	BOOL res = [[self class] standalone];
	return res;
}

- (BOOL)needsBlockLevelSpacingBefore
{
	BOOL res = [self blockLevelSpacingBefore];
	return res;
}

- (BOOL)needsBlockLevelSpacingAfter
{
	BOOL res = [self blockLevelSpacingAfter];
	return res;
}

- (NSUInteger)getListNestingLevel
{
	NSUInteger res;
	if (self.container == nil) {
		res = 0;
	} else {
		res = [self.container getListNestingLevel];
	}
	return res;
}

- (NSUInteger)getBlockquoteNestingLevel
{
	NSUInteger res;
	if (self.container == nil) {
		res = 0;
	} else {
		res = [self.container getBlockquoteNestingLevel];
	}
	return res;
}

- (BOOL)isForT2
{
	BOOL res = [[self class] forT2];
	return res;
}

- (BOOL)isForT3
{
	BOOL res = [[self class] forT3];
	return res;
}

- (BOOL)isSameClassAs:(XTHtmlTag *)other
{
	BOOL res = [[self class] isEqualTo:[other class]];
	return res;
}

- (NSString *)debugString
{
	NSString *closingSlash = (self.closing ? @"/" : @"");
	NSMutableString *res = [NSMutableString stringWithFormat:@"<%@%@", closingSlash, [[self class] name]];
	//TODO make work:
	for (NSString *name in self.attributesDict.allKeys) {
		id value = self.attributesDict[name];
		if ([value isKindOfClass:[NSNull class]]) {
			[res appendFormat:@" %@", name];
		} else {
			[res appendFormat:@" %@=\"%@\"", name, (NSString *)value];
		}
	}
	[res appendString:@">"];
	return res;
}

- (NSUInteger)recursivelyCountChildren
{
	return 1;
}

@end
