//
//  XTStringUtils.m
//  TadsTerp
//
//  Created by Rune Berg on 24/04/14.
//  Copyright (c) 2014 Rune Berg. All rights reserved.
//

#import "XTStringUtils.h"
#import "XTLogger.h"
#import "XTConstants.h"


@implementation XTStringUtils

static XTLogger* logger;

static NSCharacterSet *whitespaceCharSet;
static NSCharacterSet *whitespaceToDeleteToLastNewlineCharSet;
static NSCharacterSet *breakingWhitespaceCharSet;
static NSArray *internetLinkPrefixes;

static const NSString *zeroWidthSpace = ZERO_WIDTH_SPACE; // non-printing, 0-width space

+ (void)initialize
{
	logger = [XTLogger loggerForClass:[XTStringUtils class]];

	whitespaceCharSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
	
	NSMutableCharacterSet *tempWSTDTLNCharSet = [NSMutableCharacterSet whitespaceCharacterSet];
	[tempWSTDTLNCharSet addCharactersInString:zeroWidthSpace];

	whitespaceToDeleteToLastNewlineCharSet = tempWSTDTLNCharSet;
	
	NSMutableCharacterSet *tempBreakingWhitespaceCharSet = [NSMutableCharacterSet whitespaceAndNewlineCharacterSet];
		//TODO ? rm non-breaking ones?
	breakingWhitespaceCharSet = tempBreakingWhitespaceCharSet;

	internetLinkPrefixes = @[@"http:", @"https:", @"ftp:", @"news:", @"mailto:", @"telnet:"];
}

+ (BOOL)string:(NSString *)string endsWithChar:(unichar)ch
{
	//TODO unit test
	BOOL res = NO;
	NSUInteger stringLen = string.length;
	if (string != nil && stringLen >= 1) {
		unichar lastCh = [string characterAtIndex:(stringLen - 1)];
		res = (lastCh == ch);
	}
	return res;
}

+ (BOOL)string:(NSString *)string endsWith:(NSString *)end
{
	BOOL res = [XTStringUtils internalString:string endsWith:end];
	return res;
}

+ (BOOL)string:(NSString *)string endsWithCaseInsensitive:(NSString *)end
{
	if (string != nil && string.length >= 1) {
		string = [string lowercaseString];
	}
	if (end != nil && end.length >= 1) {
		end = [end lowercaseString];
	}
	BOOL res = [XTStringUtils internalString:string endsWith:end];
	return res;
}

//TODO unit test
+ (BOOL)string:(NSString *)string startsWith:(NSString *)start
{
	BOOL res = [string hasPrefix:start];
	return res;
}

//TODO use!
+ (BOOL)string:(NSString *)string1 isEqualToCaseInsensitive:(NSString *)string2
{
	BOOL res;
	if (string1 == nil || string2 == nil) {
		res = NO;
	} else {
		string1 = [string1 lowercaseString];
		string2 = [string2 lowercaseString];
		res = [string1 isEqualToString:string2];
	}
	return res;
}

+ (BOOL)internalString:(NSString *)string endsWith:(NSString *)end
{
	BOOL res = NO;
	if (string != nil && end != nil && string.length >= 1 && end.length >= 1) {
		NSRange rangeOfString = [string rangeOfString:end options:NSBackwardsSearch];
		if (rangeOfString.location != NSNotFound) {
			NSUInteger expectedLocation = (string.length - end.length);
			res = (rangeOfString.location == expectedLocation);
		}
	}
	return res;
}

+ (NSString *)filterRepeatedNewlines:(NSString *)string
{
	NSMutableString *res = nil;
	
	if (string != nil) {
		NSUInteger stringLength = string.length;
		res  = [NSMutableString stringWithCapacity:stringLength];
		unichar prevCh = 'z';
		for (NSUInteger i = 0; i < stringLength; i++) {
			unichar ch = [string characterAtIndex:i];
			if (ch != '\n' || prevCh != '\n') {
				NSString *newChStr = [NSString stringWithCharacters:&ch length:1];
				[res appendString:newChStr];
			}
			prevCh = ch;
		}
	}
	
	return res;
}

+ (NSRange)findRangeOfTrailingWhitespaceInLastParagraph:(NSString *)string
{
	NSUInteger resLoc = NSNotFound;
	NSUInteger resLen = 0;
	
	if (string != nil) {
		
		NSUInteger trailingTabCount = 0;
		NSInteger index = string.length - 1;
		
		while (index >= 0 && [whitespaceToDeleteToLastNewlineCharSet characterIsMember:[string characterAtIndex:index]]) {
			trailingTabCount += 1;
			index -= 1;
		}
		
		if (trailingTabCount >= 1) {
			resLoc = index + 1;
			resLen = trailingTabCount;
		}
	}
	
	NSRange res = NSMakeRange(resLoc, resLen);
	return res;
}

+ (NSRange)findRangeOfLastParagraph:(NSString *)string
{
	NSUInteger resLoc = NSNotFound;
	NSUInteger resLen = 0;
	
	if (string != nil) {
		NSInteger stringLen = string.length;
		NSInteger idxStartOfPara;
		for (idxStartOfPara = stringLen - 1; idxStartOfPara >= 0; idxStartOfPara -= 1) {
			unichar ch = [string characterAtIndex:idxStartOfPara];
			if (ch == '\n') {
				idxStartOfPara += 1;
				break;
			}
		}
		if (idxStartOfPara < 0) {
			resLoc = 0;
			resLen = stringLen;
		} else {
			resLoc = idxStartOfPara;
			resLen = stringLen - idxStartOfPara;
		}
	}
	
	NSRange res = NSMakeRange(resLoc, resLen);
	return res;
}

//TODO unit test
+ (NSString *)trimLeadingAndTrailingWhitespace:(NSString *)string
{
	NSString *res = nil;
	if (string != nil) {
		res = [string stringByTrimmingCharactersInSet:whitespaceCharSet];
	}
	return res;
}

//TODO unit test
+ (NSString *)emptyIfNull:(NSString *)string {
	
	NSString *res = string;
	if (res == nil) {
		res = @"";
	}
	return res;
}

//TODO unit test
+ (BOOL)isEmptyOrNull:(NSString *)string
{
	BOOL res = (string == nil || string.length == 0);
	return res;
}

//TODO unit test
+ (BOOL)isInternetLink:(NSString *)string
{
	// See http://www.tads.org/t3doc/doc/htmltads/deviate.htm#Achanges
	
	BOOL res = NO;
	if (string != nil) {
		string = [XTStringUtils trimLeadingAndTrailingWhitespace:string];
		string = [string lowercaseString];
		for (NSString* prefix in internetLinkPrefixes) {
			if ([string hasPrefix:prefix]) {
				res = YES;
				break;
			}
		}
	}
	return res;
}

+ (NSString *)stringOf:(NSUInteger)n string:(NSString *)s
{
	NSMutableString *res = [NSMutableString stringWithCapacity:50];
	
	for (NSUInteger i = 0; i < n; i++) {
		[res appendString:s];
	}
	
	return res;
}

+ (NSCharacterSet *)breakingWhitespaceCharSet
{
	return breakingWhitespaceCharSet;
}

+ (NSUInteger)indexInString:(NSString *)string ofCharAtRow:(NSUInteger)row column:(NSUInteger)column;
{
	//XT_DEF_SELNAME;
	
	NSUInteger sLen = string.length;
	NSUInteger currRow = 0;
	NSUInteger currColumn = 0;
	NSUInteger i;
	
	for (i = 0; i < sLen && currRow < row; i++) {
		unichar ch = [string characterAtIndex:i];
		if (ch != '\n') {
			currColumn += 1;
		} else {
			currRow += 1;
			currColumn = 0;
		}
	}
	if (currRow < row) {
		//XT_WARN_2(@"only has %lu rows, asked for %lu", currRow, row);
		// It's ok - we get a flush after banner_goto
	} else {
		for (; i < sLen && currColumn < column; i++) {
			unichar ch = [string characterAtIndex:i];
			if (ch != '\n') {
				currColumn += 1;
			} else {
				// we're on the right row, but it's not wide enough
				break;
			}
		}
		if (currColumn < column) {
			//XT_WARN_3(@"row %lu has only %lu cols, asked for %lu", currRow, currColumn, column);
			// It's ok - we get a flush after banner_goto
		}
	}
	
	return i;
}

+ (NSRange)rangeOfLongestLineIn:(NSString *)string
{
	NSUInteger lenString = string.length;
	NSUInteger startLongest = 0;
	NSUInteger startCurrent = 0;
	NSUInteger lenLongest = 0;
	NSUInteger lenCurrent = 0;
	NSUInteger i;
	
	for (i = 0; i < lenString; i++) {
		unichar ch = [string characterAtIndex:i];
		if (ch == '\n') {
			if (lenCurrent > lenLongest) {
				startLongest = startCurrent;
				lenLongest = i - startCurrent;
			}
			startCurrent = i + 1;
			lenCurrent = 0;
		} else {
			lenCurrent += 1;
		}
	}
	
	if (lenCurrent > lenLongest) {
		startLongest = startCurrent;
		lenLongest = i - startCurrent;
	}
	
	NSRange res = NSMakeRange(startLongest, lenLongest);
	return res;
}

+ (unichar)removeLastCharFrom:(NSMutableString *)string
{
	unichar lastChar = 0;
	
	NSUInteger len = string.length;
	if (string != nil && len >= 1) {
		lastChar = [string characterAtIndex:(len - 1)];
		NSRange range = NSMakeRange(len - 1, 1);
		[string deleteCharactersInRange:range];
	} else {
		XT_DEF_SELNAME;
		XT_ERROR_0(@"string was nil or empty");
	}
	
	return lastChar;
}

+ (NSString *)numericPrefix:(NSString *)string
{
	NSMutableString *res = nil;
	
	if (string != nil) {
		res = [NSMutableString string];
		NSUInteger len = string.length;
		for (NSUInteger i = 0; i < len; i++) {
			unichar ch = [string characterAtIndex:i];
			if (ch >= '0' && ch <= '9') {
				NSString *newChStr = [NSString stringWithCharacters:&ch length:1];
				[res appendString:newChStr];
			} else {
				break;
			}
		}
	}
	
	return res;
}

+ (unichar)lastChar:(NSString *)string
{
	unichar res = [string characterAtIndex:(string.length - 1)];
	return res;
}

+ (NSArray *)splitString:(NSString *)s
			 bySeparator:(NSString *)sep
 includeSeparatorEntries:(BOOL)includeSepEntries
{
	NSMutableArray *tempRes;
	
	if (sep == nil) {
		tempRes = [NSMutableArray arrayWithCapacity:1];
		if (s != nil) {
			[tempRes addObject:s];
		}
	} else {
		NSArray *comps = [s componentsSeparatedByString:sep];
		tempRes = [NSMutableArray arrayWithCapacity:comps.count * 2];
		BOOL useSep = NO;
		for (NSString *s in comps) {
			if (useSep) {
				[tempRes addObject:sep];
			}
			if (includeSepEntries) {
				useSep = YES;
			}
			[tempRes addObject:s];
		}
	}

	NSArray *res = [NSArray arrayWithArray:tempRes];
	return res;
}

+ (NSRange)rangeOfNextParagraphIn:(NSString *)string fromLoc:(NSUInteger)loc
{
	NSRange res;
	
	if (loc < string.length) {
		NSString *paraSep = @"\n";
		NSRange rangeSearch = NSMakeRange(loc, string.length - loc);
		NSRange rangeParaSep = [string rangeOfString:paraSep options:0 range:rangeSearch];
		
		NSUInteger paraLen;
		if (rangeParaSep.location == NSNotFound) {
			paraLen = rangeSearch.length; // i.e. the rest of string
		} else {
			paraLen = rangeParaSep.location - loc + paraSep.length;
		}
			
		res = NSMakeRange(loc, paraLen);

	} else {
		res = NSMakeRange(NSNotFound, 0);
	}
	
	return res;
}

+ (NSArray *)splitAttributedString:(NSAttributedString *)attrString
					   bySeparator:(NSString *)separator
{
	NSArray *plainStrings = [XTStringUtils splitString:attrString.string bySeparator:separator includeSeparatorEntries:YES];
	
	NSMutableArray *res = [NSMutableArray arrayWithCapacity:plainStrings.count];
	
	NSUInteger idx = 0;
	for (NSString *s in plainStrings) {
		if (s.length >= 1) {
			NSRange range = NSMakeRange(idx, s.length);
			NSAttributedString *as = [attrString attributedSubstringFromRange:range];
			[res addObject:as];
			idx += range.length;
		}
	}
	
	return res;
}

+ (NSString *)safeNameForEncoding:(NSStringEncoding)encoding
{
	NSString *encName = [NSString localizedNameOfStringEncoding:encoding];
	encName = [encName stringByReplacingOccurrencesOfString:@" " withString:@"-"];
	encName = [encName stringByReplacingOccurrencesOfString:@"(" withString:@"-"];
	encName = [encName stringByReplacingOccurrencesOfString:@")" withString:@"-"];
	return encName;
}

+ (BOOL)isTads2GameUrl:(NSURL *)url
{
	BOOL res = [XTStringUtils url:url endsWith:@"." XT_FILE_EXTENSION_TADS2_GAME];
	return res;
}
	
+ (BOOL)isTads3GameUrl:(NSURL *)url
{
	BOOL res = [XTStringUtils url:url  endsWith:@"." XT_FILE_EXTENSION_TADS3_GAME];
	return res;
}

+ (BOOL)url:(NSURL *)url endsWith:(NSString *)dotFileExtension
{
	BOOL res = NO;
	if (url != nil) {
		NSString *urlString = [url absoluteString];
		res = [XTStringUtils string:urlString endsWithCaseInsensitive:dotFileExtension];
	}
	return res;
}

@end
