//
//  XTPendingWhitespaceQueue.m
//  XTads
//
//  Created by Rune Berg on 10/11/2016.
//  Copyright © 2016 Rune Berg. All rights reserved.
//

#import "XTPendingWhitespaceQueue.h"
#import "XTPendingTab.h"
#import "XTLogger.h"
#import "XTAllocDeallocCounter.h"


#define UNICHAR_QUOTED_SPACE 0x15
#define UNICHAR_EN_SPACE 8194
#define UNICHAR_EM_SPACE 8195
#define UNICHAR_FIG_SPACE 8199
#define EXPANDED_EN_SPACE @"  "
#define EXPANDED_EM_SPACE @"    "
#define EXPANDED_FIG_SPACE @"  "


@interface XTPendingWhitespaceQueue ()

@property NSMutableArray *queue;

@end


@implementation XTPendingWhitespaceQueue

static XTLogger* logger;
static NSRange rangeFirstChar;
static NSNumber *inlineTagMarker;

typedef NS_ENUM(NSInteger, XTCombinedWhitespaceState) {
	XT_COMB_WS_STATE_INITIAL = 1,
	XT_COMB_WS_STATE_AFTER_REGULAR = 2,
	XT_COMB_WS_STATE_AFTER_TYPOGRAPHICAL = 3,
	XT_COMB_WS_STATE_AFTER_INLINE_TAG = 4,
	XT_COMB_WS_STATE_AFTER_INLINE_TAG_AFTER_REGULAR = 5,
	XT_COMB_WS_STATE_AFTER_INLINE_TAG_AFTER_TYPOGRAPHICAL = 6
};

+ (void)initialize
{
	logger = [XTLogger loggerForClass:[XTPendingWhitespaceQueue class]];
	rangeFirstChar = NSMakeRange(0, 1);
	inlineTagMarker = [NSNumber numberWithUnsignedInteger:999];
}

OVERRIDE_ALLOC_FOR_COUNTER

OVERRIDE_DEALLOC_FOR_COUNTER

- (instancetype)init
{
	self = [super init];
	if (self) {
		_queue = [NSMutableArray arrayWithCapacity:50];
	}
	return self;
}

+ (NSString *)enSpaceString
{
	return EXPANDED_EN_SPACE;
}

- (NSUInteger)length {
	
	return self.queue.count;
}

- (void)append:(NSAttributedString *)attrString
{
	[self.queue addObject:attrString];
	//TODO check len == 1
	//TODO check that contains only ws chars
}

- (void)appendInlineTag;
{
	[self.queue addObject:inlineTagMarker];
}

- (void)appendTab:(NSAttributedString *)attrString
{
	XTPendingTab *pendingTab = [XTPendingTab tabWithAttributedString:attrString];
	[self.queue addObject:pendingTab];
}

- (BOOL)containsTabs
{
	BOOL res = NO;
	for (id obj in self.queue) {
		if ([obj isKindOfClass: [XTPendingTab class]]) {
			res = YES;
			break;
		}
	}
	return res;
}

- (void)reset
{
	[self.queue removeAllObjects];
}

- (NSMutableArray *)combinedWhitespace
{
	XT_DEF_SELNAME;
	//TODO tabs?
	
	NSMutableArray *res = [NSMutableArray arrayWithCapacity:self.queue.count];
	XTCombinedWhitespaceState state = XT_COMB_WS_STATE_INITIAL;
	
	for (id obj in self.queue) {
		
		BOOL haveCh;
			//TODO rename
		unichar ch;
		BOOL chIsRegularSpace;
		NSAttributedString *origAttrString = nil;
		
		if ([obj isKindOfClass:[NSAttributedString class]]) {
			origAttrString = (NSAttributedString *)obj;
			NSString *str = [origAttrString string];
			if (str.length != 1) {
				XT_ERROR_1(@"expected string of length 1 but got \"%@\"", str);
			}
			ch = [str characterAtIndex:0];
			haveCh = YES;
			chIsRegularSpace = (ch == ' ');
			
		} else if ([obj isKindOfClass: [XTPendingTab class]]) {
			//TODO wip!! behaviour wrt tags, surrounding ws and typographical spaces
			//TODO unit test!!
			origAttrString = ((XTPendingTab *)obj).attrString;
			//NSString *str = [origAttrString string];
			//if (str.length != 1) {
			//	XT_ERROR_1(@"expected string of length 1 but got \"%@\"", str);
			//}
			ch = '\t'; //[str characterAtIndex:0];
			haveCh = YES;
			chIsRegularSpace = NO;
			
		} else if (obj == inlineTagMarker) {
			haveCh = NO;
			
		} else {
			XT_ERROR_0("obj was neither NSAttributedString or inlineTagMarker or XTPendingTab");
			continue;
		}
		
		switch (state) {
			case XT_COMB_WS_STATE_INITIAL:
				if (haveCh) {
					if (chIsRegularSpace) {
						[res addObject:origAttrString];
						state = XT_COMB_WS_STATE_AFTER_REGULAR;
					} else {
						// Typographical space
						NSAttributedString *transAttrStr = [self attributedStringForChar:ch originalAttrString:origAttrString];
						[res addObject:transAttrStr];
						state = XT_COMB_WS_STATE_AFTER_TYPOGRAPHICAL;
					}
				} else {
					state = XT_COMB_WS_STATE_AFTER_INLINE_TAG;
				}
				break;
			case XT_COMB_WS_STATE_AFTER_REGULAR:
				if (haveCh) {
					if (chIsRegularSpace) {
						// Ignore repeated regular space
					} else {
						[res removeLastObject]; // *** i.e. preceding regular space ***
						NSAttributedString *transAttrStr = [self attributedStringForChar:ch originalAttrString:origAttrString];
						[res addObject:transAttrStr];
						state = XT_COMB_WS_STATE_AFTER_TYPOGRAPHICAL;
					}
				} else {
					state = XT_COMB_WS_STATE_AFTER_INLINE_TAG_AFTER_REGULAR;
				}
				break;
			case XT_COMB_WS_STATE_AFTER_TYPOGRAPHICAL:
				if (haveCh) {
					if (chIsRegularSpace) {
						// Ignore regular space after typographical space
					} else {
						NSAttributedString *transAttrStr = [self attributedStringForChar:ch originalAttrString:origAttrString];
						[res addObject:transAttrStr];
						//state = XT_COMB_WS_STATE_AFTER_TYPOGRAPHICAL;
					}
				} else {
					state = XT_COMB_WS_STATE_AFTER_INLINE_TAG_AFTER_TYPOGRAPHICAL;
				}
				break;
			case XT_COMB_WS_STATE_AFTER_INLINE_TAG:
				if (haveCh) {
					if (chIsRegularSpace) {
						[res addObject:origAttrString];
						state = XT_COMB_WS_STATE_AFTER_REGULAR;
					} else {
						// Typographical space
						NSAttributedString *transAttrStr = [self attributedStringForChar:ch originalAttrString:origAttrString];
						[res addObject:transAttrStr];
						state = XT_COMB_WS_STATE_AFTER_TYPOGRAPHICAL;
					}
				} else {
					//state = XT_COMB_WS_STATE_AFTER_INLINE_TAG;
				}
				break;
			case XT_COMB_WS_STATE_AFTER_INLINE_TAG_AFTER_REGULAR:
				//TODO? merge with XT_COMB_WS_STATE_AFTER_INLINE_TAG_AFTER_TYPOGRAPHICAL
				if (haveCh) {
					if (chIsRegularSpace) {
						// Ignore regular space after inline tag if regular space before that inline tag
					} else {
						// Typographical space
						NSAttributedString *transAttrStr = [self attributedStringForChar:ch originalAttrString:origAttrString];
						[res addObject:transAttrStr];
						state = XT_COMB_WS_STATE_AFTER_TYPOGRAPHICAL;
					}
				} else {
					//state = XT_COMB_WS_STATE_AFTER_INLINE_TAG_AFTER_REGULAR;
				}
				break;
			case XT_COMB_WS_STATE_AFTER_INLINE_TAG_AFTER_TYPOGRAPHICAL:
				if (haveCh) {
					if (chIsRegularSpace) {
						// Ignore regular space after inline tag if typographical space before that inline tag
					} else {
						// Typographical space
						NSAttributedString *transAttrStr = [self attributedStringForChar:ch originalAttrString:origAttrString];
						[res addObject:transAttrStr];
						state = XT_COMB_WS_STATE_AFTER_TYPOGRAPHICAL;
					}
				} else {
					//state = XT_COMB_WS_STATE_AFTER_INLINE_TAG_AFTER_TYPOGRAPHICAL;
				}
				break;
			default:
				XT_ERROR_1(@"Unexpected state %ld - ignoring entry", state);
				break;
		}
	}
	
	return res;
}

- (NSAttributedString *)attributedStringForChar:(unichar)ch
							 originalAttrString:(NSAttributedString *)origAttrString
{
	NSAttributedString *res;
	
	switch (ch) {
		case UNICHAR_QUOTED_SPACE:
			res = [self attributedString:origAttrString withFirstCharReplacedBy:@" "];
			break;
		case UNICHAR_EN_SPACE:
			/*
			NSTextView seems to treat this like a regular space: same width, no non-breaking-ness,
			so at least compensate for the width.
			TODO revisit if we can get NSTextView to behave better.
			 */
			res = [self attributedString:origAttrString withFirstCharReplacedBy:EXPANDED_EN_SPACE];
			break;
		case UNICHAR_EM_SPACE:
			/*
			 NSTextView seems to treat this like a regular space: same width, no non-breaking-ness,
			 so at least compensate for the width.
			 TODO revisit if we can get NSTextView to behave better.
			 */			
			res = [self attributedString:origAttrString withFirstCharReplacedBy:EXPANDED_EM_SPACE];
			break;
		case UNICHAR_FIG_SPACE:
			/*
			 NSTextView seems to treat this like a regular space: same width, no non-breaking-ness,
			 so at least compensate for the width.
			 TODO revisit if we can get NSTextView to behave better.
			 */
			res = [self attributedString:origAttrString withFirstCharReplacedBy:EXPANDED_FIG_SPACE];
			break;
		default:
			res = origAttrString;
			break;
	}
	
	return res;
}

- (NSAttributedString *)attributedString:(NSAttributedString *)origAttrString
				 withFirstCharReplacedBy:(NSString *)string
{
	NSMutableAttributedString *mutAttrStr = [[NSMutableAttributedString alloc] initWithAttributedString:origAttrString];
	[[mutAttrStr mutableString] replaceCharactersInRange:rangeFirstChar withString:string];
	NSAttributedString *res = mutAttrStr;
	return res;
}

@end
