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

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

#import "OWContentCache.h"
#import "OWContentType.h"
#import "OWDataStreamCursor.h"
#import "OWUnknownDataStreamProcessor.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OWF/Content.subproj/OWDataStream.m,v 1.11 1999/08/12 10:28:14 kc Exp $")

@interface OWDataStream (Private)
- (void)flushContentsToFile;
- (void)flushAndCloseSaveFile;
@end

@implementation OWDataStream

unsigned int OWDataStreamUnknownLength = NSNotFound;

static OWContentType *unknownContentType;
static OWContentType *unencodedContentEncoding;
static NSStringEncoding defaultStringEncoding;

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

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

    unknownContentType = [OWUnknownDataStreamProcessor unknownContentType];
    unencodedContentEncoding = [OWContentType contentTypeForString:@"encoding/none"];
    defaultStringEncoding = NSISOLatin1StringEncoding;
}

+ (void)setDefaultStringEncoding:(NSStringEncoding)aStringEncoding;
{
    defaultStringEncoding = aStringEncoding;
}

- initWithLength:(unsigned int)newLength;
{
    if (![super init])
	return nil;

    dataLength = newLength;
    
    dataAvailableCondition = [[OFCondition alloc] init];
    endOfDataCondition = [[OFCondition alloc] init];

    first = NSAllocateMemoryPages(sizeof(OWDataStreamBuffer));
    last = first;
    last->next = NULL;
    readLength = 0;
    dataInBuffer = 0;
    
    stringEncoding = defaultStringEncoding;

    flags.endOfData = NO;
    flags.hasThrownAwayData = NO;
    flags.hasIssuedCursor = NO;
    flags.hasResetContentTypeAndEncoding = NO;
    
    saveFilename = nil;
    saveFileHandle = nil;

    return self;
}

- init;
{
    return [self initWithLength:OWDataStreamUnknownLength];
}

- (void)dealloc;
{
    OBASSERT(saveFileHandle == nil);
    while (first) {
        last = first->next;
        NSDeallocateMemoryPages(first, sizeof(OWDataStreamBuffer));
        first = last;
    }
    [dataAvailableCondition release];
    [endOfDataCondition release];
    [saveFilename release];
    [super dealloc];
}

- (id)newCursor;
{
    if (flags.hasThrownAwayData)
	[NSException raise:OWDataStreamNoLongerValidException format:@"Data stream no longer contains valid data"];

    flags.hasIssuedCursor = YES;
    return [[[OWDataStreamCursor alloc] initForDataStream:self] autorelease];
}

- (NSData *)bufferedData;
{
    unsigned int length, totalLength;
    OWDataStreamBuffer *buffer;
    NSMutableData *result;

    if (flags.hasThrownAwayData)
	[NSException raise:OWDataStreamNoLongerValidException format:@"Data stream no longer contains valid data"];

    length = readLength;
    if (length <= OWDataStreamBuffer_BufferedDataLength)
        return [NSData dataWithBytes:first->data length:length];
    result = [[NSMutableData alloc] initWithCapacity:length];
    totalLength = readLength;
    for (buffer = first; totalLength > 0; buffer = buffer->next) {
        length = MIN(totalLength, OWDataStreamBuffer_BufferedDataLength);
        [result appendBytes:buffer->data length:length];
        totalLength -= length;
    }
    return [result autorelease];
}

- (unsigned int)bufferedDataLength;
{
    if (flags.hasThrownAwayData)
	[NSException raise:OWDataStreamNoLongerValidException format:@"Data stream no longer contains valid data"];

    return readLength;
}

- (unsigned int)dataLength;
{
    if (flags.hasThrownAwayData)
	[NSException raise:OWDataStreamNoLongerValidException format:@"Data stream no longer contains valid data"];

    if (![self knowsDataLength])
        [endOfDataCondition waitForCondition];
    return dataLength;
}

- (BOOL)knowsDataLength;
{
    return dataLength != OWDataStreamUnknownLength;
}

- (BOOL)getBytes:(void *)buffer range:(NSRange)range;
{
    OWDataStreamBuffer *dsBuffer;
    unsigned int length;

    if (flags.hasThrownAwayData)
	[NSException raise:OWDataStreamNoLongerValidException format:@"Data stream no longer contains valid data"];

    if (![self waitForBufferedDataLength:NSMaxRange(range)])
        return NO;

    dsBuffer = first;
    while (range.location >= OWDataStreamBuffer_BufferedDataLength) {
        dsBuffer = dsBuffer->next;
        range.location -= OWDataStreamBuffer_BufferedDataLength;
    }
    while ((length = MIN(range.length, OWDataStreamBuffer_BufferedDataLength - range.location))) {
        bcopy(dsBuffer->data + range.location, buffer, length);
        range.location = 0;
        range.length -= length;
        dsBuffer = dsBuffer->next;
        buffer += length;
    }
    return YES;
}

- (NSData *)dataWithRange:(NSRange)range;
{
    NSMutableData *subdata;
    OWDataStreamBuffer *dsBuffer;
    unsigned int length;

    if (flags.hasThrownAwayData)
	[NSException raise:OWDataStreamNoLongerValidException format:@"Data stream no longer contains valid data"];

    if (![self waitForBufferedDataLength:NSMaxRange(range)])
        return nil;

    dsBuffer = first;
    while (range.location >= OWDataStreamBuffer_BufferedDataLength) {
        dsBuffer = dsBuffer->next;
        range.location -= OWDataStreamBuffer_BufferedDataLength;
    }
    if (range.location + range.length <= OWDataStreamBuffer_BufferedDataLength)
        return [NSData dataWithBytes:dsBuffer->data + range.location length:range.length];
    
    subdata = [[NSMutableData alloc] initWithCapacity:range.length];
    while ((length = MIN(range.length, OWDataStreamBuffer_BufferedDataLength - range.location))) {
        [subdata appendBytes:dsBuffer->data + range.location length:length];
        range.location = 0;
        range.length -= length;
        dsBuffer = dsBuffer->next;
    }
    return [subdata autorelease];
}

- (BOOL)waitForMoreData;
{
    if (flags.hasThrownAwayData)
	[NSException raise:OWDataStreamNoLongerValidException format:@"Data stream no longer contains valid data"];

    if (flags.endOfData)
	return NO;
    [dataAvailableCondition waitForCondition];
    return YES;
}

- (BOOL)waitForBufferedDataLength:(unsigned int)length;
{
    if (flags.hasThrownAwayData)
	[NSException raise:OWDataStreamNoLongerValidException format:@"Data stream no longer contains valid data"];

    while (readLength < length)
        if (![self waitForMoreData])
            return NO;
    return YES;
}

- (void)writeData:(NSData *)newData;
{
    NSRange range;
    unsigned int length, lengthLeft;
    OWDataStreamBuffer *new;
    
    length = [newData length];
    if (length == 0)
        return;
    lengthLeft = length;
    range.location = 0;
    range.length = MIN(lengthLeft, OWDataStreamBuffer_BufferedDataLength - dataInBuffer);
    while (lengthLeft) {
        [newData getBytes:last->data + dataInBuffer range:range];
        lengthLeft -= range.length;
        dataInBuffer += range.length;
        if (dataInBuffer == OWDataStreamBuffer_BufferedDataLength) {
            new = NSAllocateMemoryPages(sizeof(OWDataStreamBuffer));
            new->next = NULL;
            last->next = new;
            last = new;
            dataInBuffer = 0;
            range.location += range.length;
            range.length = MIN(lengthLeft, OWDataStreamBuffer_BufferedDataLength - dataInBuffer);
        }
    }
    readLength += length;
    [dataAvailableCondition broadcastCondition];
    if (saveFilename)
        [self flushContentsToFile];
}

- (NSStringEncoding)stringEncoding;
{
    return stringEncoding;
}

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

- (OWContentType *)contentEncoding;
{
    return contentEncoding;
}

- (OWContentType *)encodedContentType;
{
    return [[nonretainedContentType retain] autorelease];
}

- (void)setContentEncoding:(OWContentType *)aContentEncoding;
{
    if (aContentEncoding == unencodedContentEncoding)
	aContentEncoding = nil;
    contentEncoding = aContentEncoding;
}

- (void)setContentTypeAndEncodingFromFilename:(NSString *)aFilename;
{
    OWContentType *type;

    type = [OWContentType contentTypeForFilename:aFilename];
    if ([type isEncoding]) {
	[self setContentEncoding:type];
	type = [OWContentType contentTypeForFilename:[aFilename stringByDeletingPathExtension]];
    }
    [self setContentType:type];
}

- (NSString *)pathExtensionForContentTypeAndEncoding;
{
    NSString *typeExtension;
    NSString *encodingExtension;
    
    typeExtension = [nonretainedContentType primaryExtension];
    encodingExtension = [contentEncoding primaryExtension];
    if (!encodingExtension)
	return typeExtension;
    else if (!typeExtension)
	return encodingExtension;
    else
	return [typeExtension stringByAppendingPathExtension:encodingExtension];
}

- (BOOL)resetContentTypeAndEncoding;
{
    if (flags.hasResetContentTypeAndEncoding)
        return NO;
    flags.hasResetContentTypeAndEncoding = YES;
    [self setContentType:unknownContentType];
    [self setContentEncoding:unencodedContentEncoding];
    return YES;
}

//

- (BOOL)pipeToFilename:(NSString *)aFilename contentCache:(OWContentCache *)cache;
{
    BOOL fileCreated;
    
    if (saveFilename)
	return NO;

    if (flags.hasThrownAwayData)
	[NSException raise:OWDataStreamNoLongerValidException format:@"Data stream no longer contains valid data"];

    fileCreated = [[NSFileManager defaultManager] createFileAtPath:aFilename contents:[NSData data] attributes:nil];
    if (!fileCreated)
        [NSException raise:@"Can't save" format:@"Can't create file at path %@: %s", aFilename, strerror(OMNI_ERRNO())];

    saveFileHandle = [[NSFileHandle fileHandleForWritingAtPath:aFilename] retain];
    if (!saveFileHandle)
	[NSException raise:@"Can't save" format:@"Can't open file %@ for writing: %s", aFilename, strerror(OMNI_ERRNO())];
    savedBuffer = first;
    savedInBuffer = 0;

    saveFilename = [aFilename retain];

    // If end of data happened before we set saveFilename, we need to flush out everything ourselves
    if (flags.endOfData)
        [self flushAndCloseSaveFile];

    if (!flags.hasIssuedCursor) {
        [cache flushContentOfType:[self contentType]];
        flags.hasThrownAwayData = YES;
    }

    return YES;
}

- (NSString *)filename;
{
    return saveFilename;
}

- (BOOL)hasThrownAwayData;
{
    return flags.hasThrownAwayData;
}

- (void)writeString:(NSString *)string;
{
    if (!string)
	return;
    [self writeData:[string dataUsingEncoding:stringEncoding allowLossyConversion:YES]];
}

- (void)writeFormat:(NSString *)formatString, ...;
{
    NSString *string;
    va_list argList;

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

- (void)_noMoreData;
{
    if (saveFilename)
        [self flushAndCloseSaveFile];
    
    flags.endOfData = YES;
    [dataAvailableCondition clearCondition];
    [endOfDataCondition clearCondition];
}

- (void)waitForDataEnd;
{
    if (!flags.endOfData)
        [endOfDataCondition waitForCondition];
}

- (BOOL)endOfData;
{
    return flags.endOfData;
}

- (void)dataEnd;
{
    dataLength = readLength;
    [self _noMoreData];
}

- (void)dataAbort;
{
    flags.hasThrownAwayData = YES;

    [self _noMoreData];

    if (saveFilename) {
        NSString *oldFilename;

        oldFilename = saveFilename;
        saveFilename = nil;
        [[NSFileManager defaultManager] removeFileAtPath:oldFilename handler:nil];
        [oldFilename release];
    }
}

// OWContent protocol

- (OWContentType *)contentType;
{
    return contentEncoding ? contentEncoding : nonretainedContentType;
}

- (unsigned long int)cacheSize;
{
    return readLength;
}

- (BOOL)contentIsValid;
{
    return ![self hasThrownAwayData];
}

@end


@implementation OWDataStream (Private)

// This always happens in writer's thread, or after writer is done.
- (void)flushContentsToFile;
{
    const byte *bytes;
    unsigned int bytesRemaining;

    do {
        NSData *data;
        
        if (savedBuffer == last)
            bytesRemaining = dataInBuffer - savedInBuffer;
        else
            bytesRemaining = OWDataStreamBuffer_BufferedDataLength - savedInBuffer;
        bytes = savedBuffer->data + savedInBuffer;

        data = [[NSData alloc] initWithBytes:bytes length:bytesRemaining];
        [saveFileHandle writeData:data];
        [data release];

        savedInBuffer = 0;
        savedBuffer = savedBuffer->next;
    } while (savedBuffer);
    savedBuffer = last;
    savedInBuffer = dataInBuffer;

    // throw away anything no longer needed
    if (flags.hasThrownAwayData && !flags.hasIssuedCursor) {
        while (first != savedBuffer) {
            last = first->next;
            NSDeallocateMemoryPages(first, sizeof(OWDataStreamBuffer));
            first = last;
        }
    }
}

- (void)flushAndCloseSaveFile;
{
    [self flushContentsToFile];
    [saveFileHandle release];
    saveFileHandle = nil;
}

// Debugging

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

    debugDictionary = [super debugDictionary];
    [debugDictionary setObject:flags.endOfData ? @"YES" : @"NO" forKey:@"flags.endOfData"];
    [debugDictionary setObject:flags.hasThrownAwayData ? @"YES" : @"NO" forKey:@"flags.hasThrownAwayData"];
    [debugDictionary setObject:flags.hasIssuedCursor ? @"YES" : @"NO" forKey:@"flags.hasIssuedCursor"];
    [debugDictionary setObject:flags.hasResetContentTypeAndEncoding ? @"YES" : @"NO" forKey:@"flags.hasResetContentTypeAndEncoding"];
    [debugDictionary setObject:[NSNumber numberWithInt:readLength] forKey:@"readLength"];
    return debugDictionary;
}

@end

NSString *OWDataStreamNoLongerValidException = @"Stream invalid";
