// Copyright 1997-1999 Omni Development, Inc.  All rights reserved.
//
// This software may only be used and reproduced according to the
// terms in the file OmniSourceLicense.html, which should be
// distributed with this project and can also be found at
// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.

#import "OFStringScanner.h"

#import <Foundation/Foundation.h>
#import <OmniBase/OmniBase.h>

#import "OFTrie.h"
#import "OFTrieBucket.h"
#import "OFTrieNode.h"

RCS_ID("$Header: /Network/Developer/Source/CVS/OmniGroup/OmniFoundation/OFStringScanner.m,v 1.21 1999/03/30 21:25:32 kc Exp $")

@implementation OFStringScanner

unichar OFStringScannerEndOfDataCharacter;
static NSCharacterSet *endOfLineSet;

+ (void)initialize;
{
    static BOOL initialized = NO;

    [super initialize];
    if (initialized)
	return;
    initialized = YES;

    OFStringScannerEndOfDataCharacter = '\0';
    endOfLineSet = [[NSCharacterSet characterSetWithCharactersInString:@"\r\n"] retain];
}

- init;
{
    if (!(self = [super init]))
	return nil;

    inputBuffer = NULL;
    scanEnd = inputBuffer;
    scanLocation = scanEnd;
    inputStringPosition = 0;

    return self;
}

- initWithString:(NSString *)aString;
{
    if (!(self = [self init]))
	return nil;

    targetString = [aString retain];
    [self fetchMoreDataFromString:aString];

    return self;
}

- (void)dealloc;
{
    [targetString release];
    if (inputBuffer && freeInputBuffer)
	NSZoneFree(NULL, inputBuffer);
    [super dealloc];
}


// Declared methods

- (NSString *)string;
{
    if (targetString)
        return targetString;

    while ([self fetchMoreData]) {
        // Load all data
    }

    targetString = [[NSString alloc] initWithCharactersNoCopy:inputBuffer
                                                       length:scanEnd - inputBuffer
                                                 freeWhenDone:NO];
    return targetString;
}

- (BOOL)fetchMoreData;
{
    return NO;
}

- (BOOL)fetchMoreDataFromString:(NSString *)inputString;
{
    unsigned int length;
    unichar *newBuffer = NULL;

    length = [inputString length];
    if (length)
        newBuffer = NSZoneMalloc(NULL, sizeof(unichar) * length);
    [inputString getCharacters:newBuffer];
    return [self fetchMoreDataFromCharacters:newBuffer length:length freeWhenDone:YES];
}

- (BOOL)fetchMoreDataFromCharacters:(unichar *)characters length:(unsigned int)length freeWhenDone:(BOOL)doFreeWhenDone;
{
    inputStringPosition += (scanEnd - inputBuffer);
    if (freeInputBuffer)
        NSZoneFree(NULL, inputBuffer);
    freeInputBuffer = doFreeWhenDone;
    if (!characters || !length) {
        inputBuffer = NULL;
        scanEnd = inputBuffer;
        scanLocation = scanEnd;
        return NO;
    }
    inputBuffer = characters;
    scanLocation = inputBuffer;
    scanEnd = inputBuffer + length;
    return YES;
}

- (unichar)peekCharacter;
{
    return scannerPeekCharacter(self);
}

- (void)skipPeekedCharacter;
{
    scannerSkipPeekedCharacter(self);
}

- (unichar)readCharacter;
{
    return scannerReadCharacter(self);
}

- (unsigned int)scanLocation;
{
    return scannerScanLocation(self);
}

- (void)setScanLocation:(unsigned int)aLocation;
{
    if (aLocation >= inputStringPosition) {
	unsigned int inputLocation = aLocation - inputStringPosition;

	if (inputLocation < (unsigned)(scanEnd - inputBuffer))
	   scanLocation = inputBuffer + inputLocation;
	else {
	    scanEnd = inputBuffer;
	    scanLocation = scanEnd;
	    inputStringPosition = aLocation;
	}
    } else {
        scanEnd = inputBuffer;
        scanLocation = scanEnd;
	inputStringPosition = aLocation;
    }
}

- (void)skipCharacters:(int)anOffset;
{
    if (scanLocation + anOffset < inputBuffer) {
	[self setScanLocation:(scanLocation - inputBuffer) + anOffset];
	return;
    }
    scanLocation += anOffset;
    if (scanLocation >= scanEnd) {
	inputStringPosition += (scanLocation - inputBuffer);
	scanEnd = inputBuffer;
	scanLocation = scanEnd;
    }
}

- (BOOL)scanUpToCharacter:(unichar)aCharacter;
{
    return scannerScanUpToCharacter(self, aCharacter);
}

- (BOOL)scanUpToCharacterInSet:(NSCharacterSet *)delimiterCharacterSet;
{
    return scannerScanUpToCharacterInSet(self, delimiterCharacterSet);
}

#define SAFE_ALLOCA_SIZE (8 * 8192)

- (BOOL)scanUpToString:(NSString *)delimiterString;
{
    unichar *buffer, *ptr;
    int length, left;
    BOOL stringFound;
    BOOL useMalloc;

    if (!(length = [delimiterString length]))
        return YES;

    stringFound = NO;
    useMalloc = length * sizeof(unichar) >= SAFE_ALLOCA_SIZE;
    if (useMalloc) {
        buffer = (unichar *)NSZoneMalloc(NULL, length * sizeof(unichar));
    } else {
        buffer = (unichar *)alloca(length * sizeof(unichar));
    }
    [delimiterString getCharacters:buffer];
    while (scannerScanUpToCharacter(self, *buffer)) {
        ptr = buffer;
        left = length;
        while(left--) {
            if (scannerPeekCharacter(self) != *ptr++) {
                break;
            }
            scannerSkipPeekedCharacter(self);
        }
        if (left == -1) {
            stringFound = YES;
            break;
        }
    }

    if (useMalloc)
        NSZoneFree(NULL, buffer);

    if (stringFound)
        [self setScanLocation:[self scanLocation] - [delimiterString length]];
    
    return stringFound;
}

#warning This can break if [str lowercaseString] or [string uppercaseString] can change string length
/* (which it can, in Unicode) */
- (BOOL)scanUpToStringCaseInsensitive:(NSString *)delimiterString;
{
    unichar *lowerBuffer, *upperBuffer, *lowerPtr, *upperPtr;
    int length, left;
    BOOL stringFound;
    BOOL useMalloc;
    CSBitmap bitmap;
    NSMutableCharacterSet *set;
    unichar c;

    if (!(length = [delimiterString length]))
        return YES;

    stringFound = NO;
    useMalloc = length * sizeof(unichar) >= SAFE_ALLOCA_SIZE;
    if (useMalloc) {
        lowerBuffer = (unichar *)NSZoneMalloc(NULL, length * sizeof(unichar));
        upperBuffer = (unichar *)NSZoneMalloc(NULL, length * sizeof(unichar));
    } else {
        lowerBuffer = (unichar *)alloca(length * sizeof(unichar));
        upperBuffer = (unichar *)alloca(length * sizeof(unichar));
    }
    [[delimiterString lowercaseString] getCharacters:lowerBuffer];
    [[delimiterString uppercaseString] getCharacters:upperBuffer];
    set = [[NSMutableCharacterSet alloc] init];
    [set addCharactersInRange:NSMakeRange(*lowerBuffer, 1)];
    [set addCharactersInRange:NSMakeRange(*upperBuffer, 1)];
    bitmap = bitmapForCharacterSetDoRetain(set, NO);

    while (scannerScanUpToCharacterInCSBitmap(self, bitmap)) {
        lowerPtr = lowerBuffer;
        upperPtr = upperBuffer;
        left = length;
        while(left--) {
            c = scannerPeekCharacter(self);
            if ((c != *lowerPtr) && (c != *upperPtr)) {
                break;
            }
            scannerSkipPeekedCharacter(self);
            lowerPtr++;
            upperPtr++;
        }
        if (left == -1) {
            stringFound = YES;
            break;
        }
    }
    [set release];
    if (useMalloc) {
        NSZoneFree(NULL, lowerBuffer);
        NSZoneFree(NULL, upperBuffer);
    }

    if (stringFound)
        [self setScanLocation:[self scanLocation] - [delimiterString length]];

    return stringFound;
}

static inline NSString *
readTokenFragmentWithDelimiterCSBitmap(
    OFStringScanner *self, 
    CSBitmap delimiterBitmapRep)
{
    unichar *startLocation;
    
    if (!scannerHasData(self))
	return nil;
    startLocation = self->scanLocation;
    while (self->scanLocation < self->scanEnd) {
	if (characterIsMemberOfCSBitmap(delimiterBitmapRep, *self->scanLocation))
	    break;
	self->scanLocation++;
    }
    return [NSString stringWithCharacters:startLocation length:self->scanLocation - startLocation];
}

- (NSString *)readTokenFragmentWithDelimiterCSBitmap:
    (CSBitmap)delimiterCSBitmap;
{
    return readTokenFragmentWithDelimiterCSBitmap(self, delimiterCSBitmap);
}

- (NSString *)readTokenFragmentWithDelimiters:(NSCharacterSet *)delimiterCharacterSet;
{
    CSBitmap delimiterBitmapRep;
    
    if (!scannerHasData(self))
	return nil;
    delimiterBitmapRep = [[delimiterCharacterSet bitmapRepresentation] bytes];
    return readTokenFragmentWithDelimiterCSBitmap(self, delimiterBitmapRep);
}

static inline NSString *
readFullTokenWithDelimiterCSBitmap(OFStringScanner *self, CSBitmap delimiterCSBitmap, BOOL forceLowercase)
{
    NSString *resultString = nil, *fragment;

    if (!scannerHasData(self))
	return nil;
    do {
	fragment = readTokenFragmentWithDelimiterCSBitmap(self, delimiterCSBitmap);
	if (!fragment)
	    break;
	if (resultString)
	    resultString = [resultString stringByAppendingString:fragment];
	else
	    resultString = fragment;
    } while (!characterIsMemberOfCSBitmap(delimiterCSBitmap, scannerPeekCharacter(self)));

    if (forceLowercase && resultString)
	resultString = [resultString lowercaseString];
    return resultString;
}

- (NSString *)readFullTokenWithDelimiterCSBitmap:(CSBitmap)delimiterCSBitmap forceLowercase:(BOOL)forceLowercase;
{
    return readFullTokenWithDelimiterCSBitmap(self, delimiterCSBitmap, forceLowercase);
}

- (NSString *)readFullTokenWithDelimiters:(NSCharacterSet *)delimiterCharacterSet forceLowercase:(BOOL)forceLowercase;
{
    CSBitmap delimiterBitmapRep;

    if (!scannerHasData(self))
	return nil;
    delimiterBitmapRep = [[delimiterCharacterSet bitmapRepresentation] bytes];
    return readFullTokenWithDelimiterCSBitmap(self, delimiterBitmapRep, forceLowercase);
}

- (NSString *)readFullTokenOfSet:(NSCharacterSet *)tokenSet;
{
    return [self readFullTokenWithDelimiters:[tokenSet invertedSet] forceLowercase:NO];
}

- (NSString *)readLine;
{
    NSString *line;

    line = [self readFullTokenWithDelimiters:endOfLineSet forceLowercase:NO];
    if (!line)
	return nil;
    if (scannerPeekCharacter(self) == '\r')
	scannerSkipPeekedCharacter(self);
    if (scannerPeekCharacter(self) == '\n')
	scannerSkipPeekedCharacter(self);
    return line;
}

- (NSString *)readCharacterCount:(unsigned int)count;
{
    unsigned int bufferedCharacterCount;

    bufferedCharacterCount = scanEnd - scanLocation;
    if (count <= bufferedCharacterCount) {
        NSString *result;

        result = [NSString stringWithCharacters:scanLocation length:count];
	scanLocation += count;
	return result;
    } else {
        NSMutableString *result;
        unsigned int charactersNeeded;

        result = [NSMutableString string];
        charactersNeeded = count;
        do {
            NSString *substring;

            substring = [[NSString alloc] initWithCharactersNoCopy:scanLocation length:bufferedCharacterCount freeWhenDone:NO];
            [result appendString:substring];
            [substring release];
            charactersNeeded -= bufferedCharacterCount;
            if (![self fetchMoreData])
                return nil;
            bufferedCharacterCount = scanEnd - scanLocation;
        } while (charactersNeeded > bufferedCharacterCount);
        if (charactersNeeded > 0) {
            NSString *substring;

            substring = [[NSString alloc] initWithCharactersNoCopy:scanLocation length:charactersNeeded freeWhenDone:NO];
            [result appendString:substring];
            [substring release];
            scanLocation += charactersNeeded;
        }
        OBASSERT([result length] == count);
        return result;
   }
}

- (unsigned int)scanUnsignedIntegerMaximumDigits:(unsigned int)maximumDigits;
{
    unsigned int resultInt = 0;

    while (maximumDigits-- > 0) {
        unichar nextCharacter;

        nextCharacter = scannerPeekCharacter(self);
        if (nextCharacter >= '0' && nextCharacter <= '9') {
            scannerSkipPeekedCharacter(self);
            resultInt = resultInt * 10 + (nextCharacter - '0');
        }
    }
    return resultInt;
}

- (int)scanIntegerMaximumDigits:(unsigned int)maximumDigits;
{
    int sign = 1;

    switch (scannerPeekCharacter(self)) {
        case '-':
            sign = -1;
            // no break
        case '+':
            scannerSkipPeekedCharacter(self);
            break;
        default:
            break;
    }
    return sign * (int)[self scanUnsignedIntegerMaximumDigits:maximumDigits];
}

- (BOOL)scanString:(NSString *)string peek:(BOOL)doPeek;
{
    unichar *buffer, *ptr;
    unsigned int length, start;
    BOOL stringFound;
    BOOL useMalloc;

    length = [string length];
    useMalloc = length * sizeof(unichar) >= SAFE_ALLOCA_SIZE;
    if (useMalloc) {
	buffer = (unichar *)NSZoneMalloc(NULL, length * sizeof(unichar));
    } else {
        buffer = (unichar *)alloca(length * sizeof(unichar));
    }
    [string getCharacters:buffer];
    start = scannerScanLocation(self);
    ptr = buffer;
    stringFound = YES;
    while (length--) {
        if (scannerReadCharacter(self) != *ptr++) {
	    stringFound = NO;
	    break;
	}
    }
    if (useMalloc) {
        NSZoneFree(NULL, buffer);
    }

    if (!stringFound || doPeek)
        [self setScanLocation:start];

    return stringFound;
}

- (OFTrieBucket *)readLongestTrieElement:(OFTrie *)trie;
{
    OFTrieNode *node;
    Class trieNodeClass;
    OFTrieBucket *bucket, *lastFoundBucket = nil;
    unsigned int lastFoundScanLocation = 0;
    unichar *lowerCheck, *upperCheck;
    unichar currentCharacter;

    node = [trie headNode];
    if (!node->count)
	return nil;

    trieNodeClass = ((OFStringScanner *)node)->isa;
    while ((currentCharacter = scannerPeekCharacter(self))) {
	if ((node = trieFindChild(node, currentCharacter))) {
	    if (((OFStringScanner *)node)->isa != trieNodeClass) {
		bucket = (OFTrieBucket *)node;
		lowerCheck = bucket->lowerCharacters;
		upperCheck = bucket->upperCharacters;
		
		scannerSkipPeekedCharacter(self);
		while (*lowerCheck && (currentCharacter = scannerPeekCharacter(self))) {
		    if (currentCharacter != *lowerCheck && currentCharacter != *upperCheck)
			break;
		    scannerSkipPeekedCharacter(self);
		    lowerCheck++, upperCheck++;
		}
		if (*lowerCheck) {
		    break;
		} else {
		    return bucket;
		}
	    } else if (!*node->characters) {
		lastFoundScanLocation = scannerScanLocation(self) + 1;
		lastFoundBucket = *node->children;
	    }
	} else {
	    break;
	}
	scannerSkipPeekedCharacter(self);
    }
    if (lastFoundBucket)
	[self setScanLocation:lastFoundScanLocation];
    return lastFoundBucket;
}

- (OFTrieBucket *)readShortestTrieElement:(OFTrie *)trie;
{
    OFTrieNode *node;
    Class trieNodeClass;
    OFTrieBucket *bucket;
    unichar *lowerCheck, *upperCheck;
    unichar currentCharacter;

    node = [trie headNode];
    if (!node->count)
	return nil;

    trieNodeClass = ((OFStringScanner *)node)->isa;
    while ((currentCharacter = scannerPeekCharacter(self))) {
	if ((node = trieFindChild(node, currentCharacter))) {
	    if (((OFStringScanner *)node)->isa != trieNodeClass) {
		bucket = (OFTrieBucket *)node;
		lowerCheck = bucket->lowerCharacters;
		upperCheck = bucket->upperCharacters;
		
		while (*lowerCheck && (currentCharacter = scannerPeekCharacter(self))) {
		    if (currentCharacter != *lowerCheck && currentCharacter != *upperCheck)
			break;
		    scannerSkipPeekedCharacter(self);
		    lowerCheck++, upperCheck++;
		}
		if (*lowerCheck) {
		    break;
		} else {
		    return bucket;
		}
	    } else if (!*node->characters) {
		return *node->children;
	    }
	} else {
	    break;
	}
	scannerSkipPeekedCharacter(self);
    }
    return nil;
}

// Debugging methods

- (NSMutableDictionary *)debugDictionary;
{
    NSMutableDictionary *debugDictionary;

    debugDictionary = [super debugDictionary];

    if (inputBuffer) {
        [debugDictionary setObject:[NSString stringWithCharacters:inputBuffer length:scanEnd - inputBuffer] forKey:@"inputString"];
        [debugDictionary setObject:[NSString stringWithFormat:@"%d", scanEnd - inputBuffer] forKey:@"inputStringLength"];
        [debugDictionary setObject:[NSString stringWithFormat:@"%d", scanLocation - inputBuffer] forKey:@"inputScanLocation"];
    }
    [debugDictionary setObject:[NSString stringWithFormat:@"%d", inputStringPosition] forKey:@"inputStringPosition"];

    return debugDictionary;
}

@end
