// 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 "OWDataStreamCursor.h"

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

#import "OWDataStream.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OWF/Content.subproj/OWDataStreamCursor.m,v 1.9 1999/07/17 02:47:25 kc Exp $")

@implementation OWDataStreamCursor

static NSCharacterSet *tokenDelimiters;
static NSCharacterSet *newlineSet;
NSException *OWDataStreamCursor_UnderflowException;
NSException *OWDataStreamCursor_EndOfDataException;

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

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

    tokenDelimiters = [[NSCharacterSet whitespaceAndNewlineCharacterSet] retain];
    newlineSet = [[NSCharacterSet characterSetWithCharactersInString:@"\n\r"] retain];
    OWDataStreamCursor_UnderflowException = [[NSException alloc] initWithName:@"Underflow" reason:@"Attempted read off end of buffer" userInfo:nil];
    OWDataStreamCursor_EndOfDataException = [[NSException alloc] initWithName:@"Out of data" reason:@"More data required, but no more data available" userInfo:nil];
}

// Init and dealloc

- initForDataStream:(OWDataStream *)aStream;
{
    if (![super init])
	return nil;

    dataStream = [aStream retain];
    byteOrder = NS_UnknownByteOrder;
    dataOffset = 0;
    stringEncoding = [aStream stringEncoding];
    stringDecoder = nil;

    return self;
}

- initFromCursor:(id)aCursor;
{
    return [self initForDataStream:[aCursor dataStream]];
}

- (void)dealloc;
{
    [dataStream release];
    [stringDecoder release];
    [super dealloc];
}

//

- (OWDataStream *)dataStream;
{
    return dataStream;
}

//

- (void)setByteOrder:(OFByteOrder)newByteOrder;
{
    byteOrder = newByteOrder;
}

// These macros make the data access functions use the same code base without being deadly slow

static inline void _getMoreData(OWDataStreamCursor *self)
{
    if (self->abortException)
        [self->abortException raise];
    if (![self->dataStream waitForMoreData])
        [OWDataStreamCursor_EndOfDataException raise];
}

static inline void _getBytes(OWDataStreamCursor *self, void *buffer, unsigned int count)
{
    if (self->abortException)
        [self->abortException raise];
    if (![self->dataStream getBytes:buffer range:(NSRange){self->dataOffset, count}])
        [OWDataStreamCursor_UnderflowException raise];
    self->bitsLeft = 0;
}

static inline void _ensureBytesAvailable(OWDataStreamCursor *self, unsigned int count)
{
    if (self->abortException)
        [self->abortException raise];
    if (![self->dataStream waitForBufferedDataLength:self->dataOffset + count])
        [OWDataStreamCursor_UnderflowException raise];
}

static inline NSData *_getData(OWDataStreamCursor *self, unsigned int count)
{
    NSData *result;
    
    if (self->abortException)
        [self->abortException raise];
    if (!(result = [self->dataStream dataWithRange:(NSRange){self->dataOffset, count}]))
        [OWDataStreamCursor_UnderflowException raise];
    return result;
}

static inline NSData *_getBufferedData(OWDataStreamCursor *self, BOOL incrementOffset)
{
    unsigned int count;
    NSData *result;

    if (![self->dataStream waitForBufferedDataLength:(self->dataOffset + 1)])
        return nil;
    count = [self->dataStream bufferedDataLength] - self->dataOffset;
    result = [self->dataStream dataWithRange:(NSRange){self->dataOffset, count}];
    if (incrementOffset)
        self->dataOffset += count;
    return result;
}

#define SWAP_BYTES(inputValue, returnValue, readType, swapType)		\
{									\
    switch (byteOrder) {						\
        case NS_UnknownByteOrder:	     				\
            memcpy(&returnValue, &inputValue, sizeof(returnValue));	\
            break;	   						\
        case NS_LittleEndian:						\
            returnValue = NSSwapLittle ## swapType ## ToHost(inputValue); \
            break;     							\
        case NS_BigEndian:     						\
            returnValue = NSSwapBig ## swapType ## ToHost(inputValue);	\
            break;	   						\
    }									\
}

#define READ_DATA_OF_TYPE(readType, swapType)				\
- (readType)read ## swapType;						\
{									\
    OWSwapped ## swapType inputValue;					\
    readType returnValue;						\
									\
    _getBytes(self, &inputValue, sizeof(readType));			\
    SWAP_BYTES(inputValue, returnValue, readType, swapType);		\
    dataOffset += sizeof(readType);					\
    return returnValue;							\
}

#define PEEK_DATA_OF_TYPE(readType, swapType)				\
- (readType)peek ## swapType;						\
{									\
    OWSwapped ## swapType inputValue;					\
    readType returnValue;						\
									\
    _getBytes(self, &inputValue, sizeof(readType));			\
    SWAP_BYTES(inputValue, returnValue, readType, swapType);		\
    return returnValue;							\
}

- (unsigned int)currentOffset;
{
    return dataOffset;
}

- (void)skipBytes:(unsigned int)count;
{
    _ensureBytesAvailable(self, count);
    dataOffset += count;
}

- (void)readBytes:(unsigned int)count intoBuffer:(void *)buffer;
{
    _getBytes(self, buffer, count);
    dataOffset += count;
}

- (void)peekBytes:(unsigned int)count intoBuffer:(void *)buffer;
{
    _getBytes(self, buffer, count);
}

- (void)bufferBytes:(unsigned int)count;
{
    _ensureBytesAvailable(self, count);
}

- (unsigned int)readMaximumBytes:(unsigned int)maximum intoBuffer:(void *)buffer;
{
    unsigned int count;

    if (![dataStream waitForBufferedDataLength:dataOffset + 1])
        return 0;
    count = MIN([dataStream bufferedDataLength] - dataOffset, maximum);
    _getBytes(self, buffer, count);
    dataOffset += count;
    return count;
}

- (NSData *)readData;
{
    return _getBufferedData(self, YES);
}

- (NSData *)peekData;
{
    return _getBufferedData(self, NO);
}

- (NSData *)readAllData;
{
    [dataStream waitForDataEnd];
    return _getBufferedData(self, YES);
}

- (NSData *)readBytes:(unsigned int)count;
{
    NSData *returnData;

    returnData = _getData(self, count);
    dataOffset += count;
    return returnData;
}

- (NSData *)peekBytes:(unsigned int)count;
{
    return _getData(self, count);
}

- (byte)readByte;
{									
    byte returnValue;
    
    _getBytes(self, &returnValue, 1);
    dataOffset += 1;
    return returnValue;							
}

- (byte)peekByte;
{									
    byte returnValue;

    _getBytes(self, &returnValue, 1);
    return returnValue;							
}

typedef int OWSwappedInt;
typedef short OWSwappedShort;
typedef long OWSwappedLong;
typedef long long OWSwappedLongLong;
typedef NSSwappedFloat OWSwappedFloat;
typedef NSSwappedDouble OWSwappedDouble;

READ_DATA_OF_TYPE(int, Int);
PEEK_DATA_OF_TYPE(int, Int);
READ_DATA_OF_TYPE(short, Short);
PEEK_DATA_OF_TYPE(short, Short);
READ_DATA_OF_TYPE(long, Long);
PEEK_DATA_OF_TYPE(long, Long);
READ_DATA_OF_TYPE(long long, LongLong);
PEEK_DATA_OF_TYPE(long long, LongLong);
READ_DATA_OF_TYPE(float, Float);
PEEK_DATA_OF_TYPE(float, Float);
READ_DATA_OF_TYPE(double, Double);
PEEK_DATA_OF_TYPE(double, Double);

- (unsigned int)readBits:(unsigned int)number;
{
    unsigned int result = 0;

    if (bitsLeft) {
        partialByte &= (1 << bitsLeft) - 1;
        if (number > bitsLeft) {
            number -= bitsLeft;
            result = partialByte << number;
        } else {
            bitsLeft -= number;
            result = partialByte >> bitsLeft;
            number = 0;
        }
    }
    while (number) {
        _getBytes(self, &partialByte, 1);
        dataOffset += 1;
        if (number <= 8) {
            bitsLeft = 8 - number;
            result |= (partialByte >> bitsLeft);
            number = 0;
        } else {
            number -= 8;
            result |= (partialByte << number);
        }
    }
    return result;
}

- (int)readSignedBits:(unsigned int)number;
{
    int result = (int)[self readBits:number];

    if (result & (1 << (number-1)))
        result |= (-1 << number);
    return result;
}

- (void)skipToNextFullByte;
{
    bitsLeft = 0;
}

- (void)setStringDecoder:(NSObject <OWStringDecoder> *)decoder;
{
    [decoder retain];
    [stringDecoder release];
    stringDecoder = decoder;
}

- (NSObject <OWStringDecoder> *)stringDecoder;
{
    return stringDecoder;
}

- (NSStringEncoding)stringEncoding;
{
    return stringEncoding;
}

- (void)setStringEncoding:(NSStringEncoding)aStringEncoding;
{
    if (aStringEncoding)
        stringEncoding = aStringEncoding;
}

static inline NSString *_peekDataAsString(OWDataStreamCursor *self)
{
    if (self->stringDecoder) {
        // Hopefully, this path won't be taken very often at all.
        [self->dataStream waitForDataEnd];
        return [self->stringDecoder stringWithData:_getBufferedData(self, NO)];
    }

    return [NSString stringWithData:_getBufferedData(self, NO) encoding:self->stringEncoding];
}

static inline unsigned _indexOfCharacterFromSet(OWDataStreamCursor *self,
	NSCharacterSet *set)
{
    NSString *streamBufferString;
    NSRange testRange;

    do {
	streamBufferString = _peekDataAsString(self);
	testRange = [streamBufferString rangeOfCharacterFromSet:set];
	if (testRange.length == 0)
            _getMoreData(self);
    } while (testRange.length == 0);

    return testRange.location;
}

- (NSString *)readLineAndAdvance:(BOOL)shouldAdvance;
{
    NSString *line;
    NSString *readBufferString = nil;
    NSRange crRange, nlRange, endOfLineRange;

    endOfLineRange.length = 0;
    while (1) {
	readBufferString = _peekDataAsString(self);
	nlRange = [readBufferString rangeOfString:@"\n"];
	if (nlRange.length != 0)
	    crRange = [readBufferString rangeOfString:@"\r" options:0 range:NSMakeRange(0, nlRange.location)];
	else
	    crRange = [readBufferString rangeOfString:@"\r"];
	if (crRange.length != 0 && nlRange.length != 0) {
	    if (crRange.location < nlRange.location) {
		endOfLineRange = crRange;
		// If the nl immediately follows cr, we found a crnl.
		if (nlRange.location - crRange.location == 1)
		    endOfLineRange.length++;
	    } else
		endOfLineRange = nlRange;
	    break;
	} else if (crRange.length != 0) {
	    endOfLineRange = crRange;
	    // if the cr is the last char in the buffer, it MIGHT have a nl as the next char, so keep reading
	    if (NSMaxRange(crRange) < [readBufferString length])
		break;
	} else if (nlRange.length != 0) {
	    endOfLineRange = nlRange;
	    break;
	}

        if ([dataStream endOfData] && dataOffset == [dataStream bufferedDataLength])
	    break;
	_getMoreData(self);
    }
    
    if (endOfLineRange.length != 0) {
	line = [readBufferString substringToIndex:endOfLineRange.location];
	if (shouldAdvance)
	    dataOffset += NSMaxRange(endOfLineRange);
	return line;
    }
    return nil;
}

- (NSString *)readString;
{
    NSData *data;

    if (stringDecoder) {
        // TODO: Should we have a -sensitiveToByteBoundaries method or something, and do incremental read if the stringDecoder allows it?
        data = [self readAllData];
        if (!data)
            return nil;
	if ([data length] == 0)
            return @"";
	return [stringDecoder stringWithData:data];
    }

    data = _getBufferedData(self, YES);
    if (!data)
        return nil;

    return [[[NSString alloc] initWithData:data encoding:stringEncoding] autorelease];
}

- (NSString *)readLine;
{
    return [self readLineAndAdvance:YES];
}

- (NSString *)peekLine;
{
    return [self readLineAndAdvance:NO];
}

- (void)skipLine;
{
    [self readLine];
}

- (NSString *)readToken;
{
    NSString *returnString;
    unsigned int tokenEnd;

    dataOffset += _indexOfCharacterFromSet(self, [tokenDelimiters
		invertedSet]);
    tokenEnd = _indexOfCharacterFromSet(self, tokenDelimiters);
    returnString = [_peekDataAsString(self) substringWithRange:
			(NSRange){0, tokenEnd}];
    dataOffset += tokenEnd;
    return returnString;
}

- (NSString *)peekToken;
{
    unsigned int tokenStart;
    unsigned int tokenEnd;

    tokenStart = _indexOfCharacterFromSet(self, [tokenDelimiters
		invertedSet]);
    tokenEnd = _indexOfCharacterFromSet(self, tokenDelimiters);
    return [_peekDataAsString(self) substringWithRange:(NSRange){tokenStart,
	tokenEnd - tokenStart}];
}

- (int)readTextInt;
{
    return [[self readToken] intValue];
}

- (int)peekTextInt;
{
    return [_peekDataAsString(self) intValue];
}

- (unsigned)scanUntilByte:(byte)byteMatch;
{
    NSCharacterSet *byteCharacterSet;
    unsigned int readCount;

    byteCharacterSet = [NSCharacterSet characterSetWithCharactersInString:[NSString stringWithCString:&byteMatch length:1]];
    readCount = _indexOfCharacterFromSet(self, byteCharacterSet);
    dataOffset += readCount;
    return readCount;
}

- (unsigned)scanUntilByteSet:(NSCharacterSet *)byteSetMatch;
{
    unsigned int readCount;

    readCount = _indexOfCharacterFromSet(self, byteSetMatch);
    dataOffset += readCount;
    return readCount;
}

- (unsigned)scanUntilStringRead:(NSString *)stringMatch;
{
    NSString *streamBufferString;
    NSRange testRange;

    streamBufferString = _peekDataAsString(self);
    do {
	testRange = [streamBufferString rangeOfString:stringMatch];
	if (testRange.length == 0) {
	    _getMoreData(self);
	    streamBufferString = _peekDataAsString(self);
	}
    } while (testRange.length == 0);

    dataOffset += testRange.location + testRange.length;
    return testRange.location + testRange.length;
}

// OWCursor subclass

- (unsigned int)seekToOffset:(int)offset fromPosition:(OWCursorSeekPosition)position;
{
    switch (position) {
        case OWCursorSeekFromEnd:
            offset = [dataStream dataLength] + offset - dataOffset;
            break;
        case OWCursorSeekFromCurrent:
            break;
        case OWCursorSeekFromStart:
            offset = offset - dataOffset;
            break;
    }
    if (offset > 0)
        [self skipBytes:offset];
    else
        dataOffset += offset;
    return dataOffset;    
}

// OWContent protocol

- (OWContentType *)contentType;
{
    return [dataStream contentType];
}

- (unsigned long int)cacheSize;
{
    return [(id <OWOptionalContent>)dataStream cacheSize];
}

- (BOOL)contentIsValid;
{
    return [(id <OWOptionalContent>)dataStream contentIsValid];
}

// Debugging

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

    debugDictionary = [super debugDictionary];
    if (dataStream)
	[debugDictionary setObject:dataStream forKey:@"dataStream"];
    [debugDictionary setObject:[NSNumber numberWithInt:dataOffset] forKey:@"dataOffset"];

    return debugDictionary;
}

@end
