//---------------------------------------------------------------------------------------
//	NSString+MessageUtils.m created by erik on Sun 23-Mar-1997
//	This code is part of the Alexandra Newsreader Project. For copyright details see
//	GNU public license version 2 or above. No warranties implied. Use at own risk.
//	More information can be found at <http://www.object-factory.com/Alexandra>.
//	@(#)$Id: NSString+MessageUtils.m,v 1.4 1998/09/28 12:28:41 erik Exp $
//---------------------------------------------------------------------------------------

#import "Utilities.h"
#import "NSString+MessageUtils.h"


//---------------------------------------------------------------------------------------
	@implementation NSString(MessageUtilities_Alexandra)
//---------------------------------------------------------------------------------------

//---------------------------------------------------------------------------------------
//	STANDARD STUFF
//---------------------------------------------------------------------------------------

- (NSString *)stringByRemovingSurroundingWhitespace
{
	static NSCharacterSet *iwsSet = nil;
	NSRange				  start, end, result;
	
	if(iwsSet == nil)
		iwsSet = [[[NSCharacterSet whitespaceCharacterSet] invertedSet] retain];
		
	start = [self rangeOfCharacterFromSet:iwsSet];
	if(start.length == 0)
		return @""; // string is empty or consists of whitespace only
	
	end = [self rangeOfCharacterFromSet:iwsSet options:NSBackwardsSearch];
	if((start.location == 0) && (end.location == [self length] - 1))
		return self;
	
	result = NSMakeRange(start.location, end.location + end.length - start.location);

	return [self substringWithRange:result];	
}



//---------------------------------------------------------------------------------------
//	EXTRACTING SUBSRUCTURES
//---------------------------------------------------------------------------------------

- (BOOL)isValidArticleID;
{ 	
	return [self hasPrefix:@"<"] && [self hasSuffix:@">"] && ([self rangeOfString:@"@"].length != 0);
}


- (NSString *)getURLFromArticleID;
{
    NSScanner *scanner;
    NSString  *articleId;

    scanner = [NSScanner scannerWithString:self];
    if([scanner scanString:@"<" intoString:NULL])
        if([scanner scanUpToString:@">" intoString:&articleId])
            return [NSString stringWithFormat:@"news:%@", articleId];
    return nil;
}


- (NSString *)realnameFromEMailAddress;
{
	static NSCharacterSet *skipChars = nil;	
	NSRange charPos, char2Pos, nameRange;
	
	if(skipChars == nil)
		skipChars = [[NSCharacterSet characterSetWithCharactersInString:@"\"' "] retain];

	if((charPos = [self rangeOfString:@"@"]).length == 0)
		{
		nameRange = NSMakeRange(0, [self length]);
		}
	else if((charPos = [self rangeOfString:@"<"]).length > 0) 
		{
		nameRange = NSMakeRange(0, charPos.location);
		}
	else if((charPos = [self rangeOfString:@"("]).length > 0)
		{
		char2Pos = [self rangeOfString:@")"];
		if((char2Pos.length > 0) && (char2Pos.location > charPos.location))
			nameRange = NSMakeRange(charPos.location + 1, char2Pos.location - charPos.location - 1);
		else
			nameRange = NSMakeRange(0, [self length]);
		}
	else
		{
		nameRange = NSMakeRange(0, [self length]);
		}
		
	while((nameRange.length > 0) && [skipChars characterIsMember:[self characterAtIndex:nameRange.location]])
		{
		nameRange.location += 1; nameRange.length -= 1;
		}
	while((nameRange.length > 0) && [skipChars characterIsMember:[self characterAtIndex:nameRange.location + nameRange.length - 1]])
		{
		nameRange.length -= 1;
		}
		
	return [self substringWithRange:nameRange];
}	


//---------------------------------------------------------------------------------------
//	TRIM ID LISTS
//---------------------------------------------------------------------------------------

- (NSArray *)messageIDsFromFieldBody;
{
	NSRange			gluedIDRange;
	NSMutableString	*temp;
	NSMutableArray	*list;
	int				i;
	NSString		*refs, *articleID;

	// 	this is not really efficient, but it works fairly well even with badly 
	//	mangled headers and we usually only work with small numbers of elements.

	//	first pass: some agents don't put blanks between the references.
	if((gluedIDRange = [self rangeOfString:@"><"]).length > 0)
		{
		temp = [[self mutableCopy] autorelease];
		while((gluedIDRange = [temp rangeOfString:@"><"]).length > 0)
			[temp insertString:@" " atIndex:gluedIDRange.location + 1];
		refs = temp;
		}
	else
		{
		refs = self;
		}

	//	second pass: we remove all invalid ids and all "empty" ids (resulting from
	//  more than one space inbetween.)
	list = [NSMutableArray arrayWithArray:[refs componentsSeparatedByString:@" "]];
	for(i = [list count] - 1; i >= 0; i--)
		{
		articleID = [list objectAtIndex:i];
		if([articleID isValidArticleID] == NO)
			[list removeObjectAtIndex:i];
		}
	
	return list;
}


/* we should be able to pass in a list of ids that must not be removed (because they appear in the body) */

+ (NSString *)fieldBodyFromMesageIDs:(NSArray *)idList maxLength:(unsigned int)length;
{
	NSMutableArray	*newList;
	unsigned int	newLength;
	int				i, n;
	
	if((n = [idList count]) == 0)
		return @"";
	
	newList = [NSMutableArray arrayWithArray:idList];
	newLength = -1; // first id doesn't have a leading space...
	for(i = 0; i < [idList count]; i++)
		newLength += [[idList objectAtIndex:i] length] + 1; // one for space
	
	// we remove ids until the resulting string is short enough. note that
	// the first and the last three ids are not deleted even if the string
	// remains too long. (cf. son-of-1036)
	while(newLength > length)
		{
		if([newList count] < 5)
			return nil;
		newLength -= [[newList objectAtIndex:1] length] + 1;	
		[newList removeObjectAtIndex:1];
		}
	
	return [newList componentsJoinedByString:@" "];
}


- (NSString *)stringByTrimmingReferencesToLength:(unsigned int)maxLength;
{
	return [NSString fieldBodyFromMesageIDs:[self messageIDsFromFieldBody] maxLength:maxLength];
}


//---------------------------------------------------------------------------------------
//	CONVERSIONS
//---------------------------------------------------------------------------------------

- (NSString *)stringByApplyingROT13;
{
	NSString	 	*result;
	unsigned int 	length, i;
	unichar			*buffer, *cp;

	length = [self length];
	buffer = NSZoneMalloc([self zone], length * sizeof(unichar));
	[self getCharacters:buffer range:NSMakeRange(0, length)];
	for(i = 0, cp = buffer; i < length; i++, cp++)
		if((*cp >= (unichar)'a') && (*cp <= (unichar)'z'))
			*cp = (((*cp - 'a') + 13) % 26) + 'a';
		else if((*cp >= (unichar)'A') && (*cp <= (unichar)'Z'))
			*cp = (((*cp - 'A') + 13) % 26) + 'A';
	result = [NSString stringWithCharacters:buffer length:length];
	NSZoneFree([self zone], buffer);
	
	return result;
}


//---------------------------------------------------------------------------------------
//	REFORMATTING
//---------------------------------------------------------------------------------------

- (NSString *)stringByUnwrappingParagraphs;
{
    NSCharacterSet	*forceBreakSet, *separatorSet;
    NSMutableString	*buffer;
    NSEnumerator	*lineEnum;
    NSString		*currentLine, *nextLine;

    separatorSet = [NSCharacterSet characterSetWithCharactersInString:@" -\t"];
    forceBreakSet = [NSCharacterSet characterSetWithCharactersInString:@" \t.?!:-·1234567890>}#%|"];
    buffer = [[[NSMutableString allocWithZone:[self zone]] init] autorelease];
    lineEnum = [[self componentsSeparatedByString:@"\n"] objectEnumerator];
    currentLine = [lineEnum nextObject];
    while((nextLine = [lineEnum nextObject]) != nil)
        {
        [buffer appendString:currentLine];
		// if any of these conditions isn't met we don't unwrap
        if(([currentLine isEqualToString:@""]) || ([nextLine isEqualToString:@""]) || ([currentLine length] < 55) || ([forceBreakSet characterIsMember:[nextLine characterAtIndex:0]]))
            {
            [buffer appendString:@"\n"];
            }
		// if the line didn't end with a whitespace or hyphen we insert a space
        else if([separatorSet characterIsMember:[currentLine characterAtIndex:[currentLine length] - 1]] == NO)
            {
            [buffer appendString:@" "];
            }
        currentLine = nextLine;
        }
    [buffer appendString:currentLine];

    return buffer;
}


- (NSString *)stringByWrappingToLineLength:(unsigned int)length;
{
    NSCharacterSet	*breakSet, *textSet;
    NSMutableString	*buffer;
    NSEnumerator	*lineEnum;
    NSString		*originalLine, *prefix, *spillOver, *lastPrefix;
	NSMutableString	*mcopy;
    NSRange			textStart, endOfLine;
    unsigned int	lineStart, nextLineStart, prefixLength;

    breakSet = [NSCharacterSet characterSetWithCharactersInString:@" \t-"];
    textSet = [[NSCharacterSet characterSetWithCharactersInString:@" \t>}#%|"] invertedSet];
    buffer = [[[NSMutableString allocWithZone:[self zone]] init] autorelease];
	spillOver = nil;
    lineEnum = [[self componentsSeparatedByString:@"\n"] objectEnumerator];
    while((originalLine = [lineEnum nextObject]) != nil)
        {
		if((textStart = [originalLine rangeOfCharacterFromSet:textSet]).length == 0)
			textStart.location = [originalLine length];
		prefix = [originalLine substringToIndex:textStart.location];
		prefixLength = textStart.location;
		lineStart = textStart.location;		
				
		if(spillOver != nil)
			if(([lastPrefix isEqualToString:prefix] == NO) || (textStart.length == 0))
				{
				[buffer appendAsLine:spillOver withPrefix:lastPrefix];
				}
			else
				{
				originalLine = mcopy = [[originalLine mutableCopy] autorelease];
				[mcopy insertString:@" " atIndex:lineStart];
				[mcopy insertString:spillOver atIndex:lineStart];
				}
	
		// note that this doesn't work properly if length(prefix) > length!, so...
		NSAssert(prefixLength <= length, @"line prefix too long.");
	
		if([originalLine length] - lineStart  > length - prefixLength)
			{
			do
            	{
				endOfLine = [originalLine rangeOfCharacterFromSet:breakSet options:NSBackwardsSearch range:NSMakeRange(lineStart, length - prefixLength)];
           		if(endOfLine.length > 0)
                	{
                	if([originalLine characterAtIndex:endOfLine.location] == (unichar)'-')
                    	endOfLine.location += 1;
                	nextLineStart = endOfLine.location;
                	while((nextLineStart < [originalLine length]) && ([originalLine characterAtIndex:nextLineStart] == (unichar)' '))
                    	nextLineStart += 1;
                	}
            	else
                	{
                	endOfLine = [originalLine rangeOfComposedCharacterSequenceAtIndex:lineStart + length - prefixLength];
                	nextLineStart = endOfLine.location;
                	}
				[buffer appendAsLine:[originalLine substringWithRange:NSMakeRange(lineStart, endOfLine.location - lineStart)] withPrefix:prefix];
            	lineStart = nextLineStart;
				}
			while([originalLine length] - lineStart  > length - prefixLength);
            spillOver = [originalLine substringFromIndex:lineStart];
			}
		else
			{
			[buffer appendString:originalLine];
           	[buffer appendString:@"\n"];
			spillOver = nil;
			}
		lastPrefix = prefix;
        }
	if(spillOver != nil)
		[buffer appendAsLine:spillOver withPrefix:lastPrefix];

    return buffer;
}


- (NSString *)stringByPrefixingLinesWithString:(NSString *)prefix;
{
    NSMutableString *buffer;
    NSEnumerator   	*lineEnum;
    NSString		*line;

    buffer = [[[NSMutableString allocWithZone:[self zone]] init] autorelease];
    lineEnum = [[self componentsSeparatedByString:@"\n"] objectEnumerator];
    while((line = [lineEnum nextObject]) != nil)
        {
        [buffer appendAsLine:line withPrefix:prefix];
        }
    
    return buffer;
}



//---------------------------------------------------------------------------------------
    @end
//---------------------------------------------------------------------------------------



//---------------------------------------------------------------------------------------
    @implementation NSMutableString(MessageUtilities_Alexandra)
//---------------------------------------------------------------------------------------

- (void)appendAsLine:(NSString *)line withPrefix:(NSString *)prefix
{
	[self appendString:prefix];
	[self appendString:line];
	[self appendString:@"\n"];
}


//---------------------------------------------------------------------------------------
    @end
//---------------------------------------------------------------------------------------
