// Copyright 1997-2002 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 "ONSocketStream.h"

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

#import "ONSocket.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniNetworking/ONSocketStream.m,v 1.21 2002/03/09 01:54:06 kc Exp $")

@implementation ONSocketStream

+ streamWithSocket:(ONSocket *)aSocket;
{
    return [[[self alloc] initWithSocket:aSocket] autorelease];
}

- initWithSocket:(ONSocket *)aSocket;
{
    if (![super init])
	return nil;
    socket = [aSocket retain];
    [self clearReadBuffer];
    return self;
}

- (void)dealloc;
{
    [socket release];
    [readBuffer release];
    [super dealloc];
}


- (ONSocket *)socket;
{
    return socket;
}

- (BOOL)isReadable;
{
    if ([readBuffer length])
        return YES;
    return [socket isReadable];
}

- (void)setReadBuffer:(NSData *)aData;
{
    if ((NSData *)readBuffer == aData)
	return;
    [readBuffer release];
    readBuffer = [aData retain];
}

- (void)clearReadBuffer;
{
    [readBuffer release];
    readBuffer = [[NSMutableData alloc] init];
}

- (void)advanceReadBufferBy:(unsigned int)advanceAmount;
{
    NSData *oldReadBuffer;
    NSRange range;

    oldReadBuffer = readBuffer;
    range = NSMakeRange(advanceAmount, [oldReadBuffer length] - advanceAmount);
    readBuffer = [[oldReadBuffer subdataWithRange: range] mutableCopy];
    [oldReadBuffer release];
}

- (BOOL)readSocket;
{
    NSData *newData;
    
    newData = [socket readData];
    if (!newData)
	return NO; // End Of File
    [readBuffer appendData:newData];
    return YES;
}


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

    endOfLineRange.length = 0;
    do {
	readBufferString = [[[NSString alloc] initWithData:readBuffer encoding:[self stringEncoding]] autorelease];
	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 if (nlRange.location - crRange.location == 2 && [readBufferString characterAtIndex:crRange.location + 1] == '\r') {
                    // Nov 7, 2000:  A WebSitePro/2.4.9 server at www.alpa.org was returning \r\r\n in some of its headers, so let's go ahead and allow that (since obviously it works in other browsers)
		    endOfLineRange.length = NSMaxRange(nlRange) - crRange.location;
                }
	    } 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;
	}
    } while ([self readSocket]);
    
    if (endOfLineRange.length != 0) {
	line = [readBufferString substringToIndex:endOfLineRange.location];
	if (shouldAdvance)
	    [self advanceReadBufferBy:NSMaxRange(endOfLineRange)];
	return line;
    }
    return nil;
}

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

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

- (NSData *)readData;
{
    NSData *data;

    if ([readBuffer length] == 0) {
	if (![self readSocket])
	    return nil;
    }
    data = [[readBuffer retain] autorelease];
    [self clearReadBuffer];
    return data;
}

- (NSData *)readDataWithMaxLength:(unsigned int)length;
{
    NSData *result;

    if (![readBuffer length])
        if (![self readSocket])
            return nil;
    
    if ([readBuffer length] <= length) {
        result = [readBuffer retain];
        [self clearReadBuffer];

        return [result autorelease];
    } else  {
        result = [readBuffer subdataWithRange:NSMakeRange(0, length)];
        [self advanceReadBufferBy:length];

        return result;
    }
}

- (NSData *)readDataOfLength:(unsigned int)length;
{
    NSData *result;
    unsigned int readBufferLength;

    readBufferLength = [readBuffer length];
    if (readBufferLength == length) {
        result = [readBuffer retain];
        [self clearReadBuffer];
        return [result autorelease];
    } else if (readBufferLength > length) {
        result = [readBuffer subdataWithRange:NSMakeRange(0, length)];
        [self advanceReadBufferBy:length];
        return result;
    } else {
        NSMutableData *mutableBuffer;
        unsigned char *mutableBytes;
        unsigned int remainingByteCount;
        unsigned int lengthRead;

        mutableBuffer = [[NSMutableData alloc] initWithCapacity:length];
        [mutableBuffer appendData:readBuffer];
        [mutableBuffer setLength:length];

        [self clearReadBuffer];

        mutableBytes = [mutableBuffer mutableBytes] + readBufferLength;
        remainingByteCount = length - readBufferLength;
        while (remainingByteCount != 0) {
            lengthRead = [socket readBytes:remainingByteCount intoBuffer:mutableBytes];
            remainingByteCount -= lengthRead;
            mutableBytes += lengthRead;
        }
        return [mutableBuffer autorelease];
    }
}

- (unsigned int)readBytesWithMaxLength:(unsigned int)length intoBuffer:(void *)buffer;
{
    unsigned int readBufferLength;
    
    if ((readBufferLength = [readBuffer length])) {
        length = MIN(readBufferLength, length);
        [readBuffer getBytes:buffer length:length];
        if (readBufferLength == length)
            [self clearReadBuffer];
        else
            [self advanceReadBufferBy:length];
        return length;
    } else {
        return [socket readBytes:length intoBuffer:buffer];
    }
}

- (void)readBytesOfLength:(unsigned int)length intoBuffer:(void *)buffer;
{
    int read;
    
    while(length) {
        read = [self readBytesWithMaxLength:length intoBuffer:buffer];
        length -= read;
        buffer += read;
    }
}

- (NSString *)readString;
{
    NSData *data;
    
    data = [self readData];
    if (!data)
	return nil;
    // Note that this will only work with single byte encodings (which is probably why we don't use it anymore).
    // We should assert that the string encoding is reasonable for this sort of operation; it's perfectly reasonable to call -readString on an ASCII stream, but not on a Unicode stream (unless we make this more complex).
    return [[[NSString alloc] initWithData:data encoding:[self stringEncoding]] autorelease];
}

- (void)writeString:(NSString *)aString;
{
    [socket writeString:aString];
}

- (void)writeFormat:(NSString *)aFormat, ...;
{
    va_list argList;
    NSString *formattedString;

    va_start(argList, aFormat);
    formattedString = [[NSString alloc] initWithFormat:aFormat arguments:argList];
    va_end(argList);
    [self writeString:formattedString];
    [formattedString release];
}

- (void)writeData:(NSData *)theData;
{
    [socket writeData:theData];
}

- (NSStringEncoding)stringEncoding;
{
    return [socket stringEncoding];
}

- (void)setStringEncoding:(NSStringEncoding)aStringEncoding;
{
    [socket setStringEncoding:aStringEncoding];
}

// Debugging

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

    debugDictionary = [super debugDictionary];
    if (socket)
	[debugDictionary setObject:socket forKey:@"socket"];
    if (readBuffer)
	[debugDictionary setObject:readBuffer forKey:@"readBuffer"];

    return debugDictionary;
}

@end
