/*
        Copyright (c) 1996, NeXT Software, Inc.
        All rights reserved.

        You may freely copy, distribute and reuse the code in this example.
        NeXT disclaims any warranty of any kind, expressed or implied,
        as to its fitness for any particular use.
*/
#import "FlatFileChannel.h"
#import "FlatFileContext.h"
#import "FlatFileAdaptor.h"
#import "FlatFileColumn.h"
#import "FileScanning.h"

@interface NSObject (FlatFileAdaptorFormatting)

- (NSString *)flatFileFormattedDescription;

@end

@interface NSCalendarDate (FlatFileAdaptorFormatting)

- (NSString *)flatFileFormattedDescription;

@end

@interface FlatFileChannel (FlatFileChannelPrivate)

- (NSDictionary *)dictionaryFromRow:(char *)row length:(int)length entity:(EOEntity *)entity;
- (NSData *)dataFromDictionary:(NSDictionary *)dict entity:(EOEntity *)entity;
- (void)selectFromEntity:(EOEntity *)entity qualifier:(EOQualifier *)qualifier recordIndices:(BOOL)record;
- (void)cleanupFetch;
- (void)buildColumnsForAttributes:(NSArray *)attributes;
- (void)createTable:(NSString *)tableName withColumnList:(NSString *)columnList;
- (void)dropTable:(NSString *)tableName;

@end

@implementation FlatFileChannel

- initWithFlatFileContext:(FlatFileContext *)context
{
    [super initWithAdaptorContext:context];

    rowSeparator = [[context rowSeparator] retain];
    columnSeparator = [[context columnSeparator] retain];
    
    return self;
}

- (void)dealloc
{
    [rowSeparator release];
    [columnSeparator release];

    if (columns) {
        [columns release];
        [columnAttributes release];
    }

    if (resultSet) {
        [resultEnumerator release];
        [resultSet release];
    }

    [super dealloc];
}

- (void)openChannel
{
    _flags.isOpen = YES;
}

- (void)closeChannel
{
    if ([self isOpen]) {
        if ([self isFetchInProgress])
            [self cancelFetch];

        _flags.isOpen = NO;
    }
}

- (BOOL)isOpen
{
    return _flags.isOpen;
}

- (void)insertRow:(NSDictionary *)row forEntity:(EOEntity *)entity
{
    NSMutableData *table;

    [(FlatFileContext *)[self adaptorContext] autoBeginTransaction];

    table = [(FlatFileContext *)_context dataForTable:[entity externalName]];
    [table appendData:[self dataFromDictionary:row entity:entity]];
    [table appendBytes:[rowSeparator cString] length:[rowSeparator cStringLength]];
    [(FlatFileContext *)_context channel:self didDirtyTable:[entity externalName]];

    [(FlatFileContext *)[self adaptorContext] autoCommitTransaction];

}

- (unsigned)updateValues:(NSDictionary *)row inRowsDescribedByQualifier:(EOQualifier *)qualifier entity:(EOEntity *)entity
{
    NSMutableData *table;
    NSMutableArray *matches = [NSMutableArray array];
    NSDictionary *dict;
    NSMutableDictionary *dictToUpdate;
    NSEnumerator *enumerator;
    char *begin, *currentRow, *nextRow, *end, *delimiter;
    int length;
    unsigned updates;

    [(FlatFileContext *)[self adaptorContext] autoBeginTransaction];

    [self setAttributesToFetch:[entity attributes]];
    table = [(FlatFileContext *)_context dataForTable:[entity externalName]];
    begin = (char *)[table mutableBytes];
    currentRow = begin;
    end = begin + [table length] - 1;
    delimiter = (char *)[rowSeparator cString];

    while (currentRow) {
        nextRow = FFNextTokenIn(currentRow, &length, end, delimiter);
        dict = [self dictionaryFromRow:currentRow length:length entity:entity];
        if (dict && (!qualifier || [(id <EOQualifierEvaluation>)qualifier evaluateWithObject:dict])) {
            [matches addObject:[[[NSMutableDictionary alloc] initWithDictionary:dict] autorelease]];
            if (nextRow) {
                memmove((void *)currentRow, (const void *)nextRow, 1 + end - nextRow);
                [table setLength:(1 + end - begin) - (nextRow - currentRow)];
                end -= (1 + nextRow - currentRow);
                nextRow = currentRow;
            } else
                [table setLength:(1 + end - begin) - (1 + end - currentRow)];
        }
        currentRow = nextRow;
    }

    enumerator = [matches objectEnumerator];
    while (dictToUpdate = [enumerator nextObject]) {
        [dictToUpdate addEntriesFromDictionary:row];
        [self insertRow:dictToUpdate forEntity:entity];
    }

    updates = [matches count];
    if (updates)
        [(FlatFileContext *)_context channel:self didDirtyTable:[entity externalName]];

    [(FlatFileContext *)[self adaptorContext] autoCommitTransaction];

    return updates;
}

- (unsigned)deleteRowsDescribedByQualifier:(EOQualifier *)qualifier entity:(EOEntity *)entity
{
    NSMutableData *table;
    NSDictionary *dict;
    char *begin, *currentRow, *nextRow, *end, *delimiter;
    int length;
    unsigned deleted = 0;

    [(FlatFileContext *)[self adaptorContext] autoBeginTransaction];

    [self setAttributesToFetch:[entity attributes]];
    table = [(FlatFileContext *)_context dataForTable:[entity externalName]];
    begin = (char *)[table mutableBytes];
    currentRow = begin;
    end = begin + [table length] - 1;
    delimiter = (char *)[rowSeparator cString];

    while (currentRow) {
        nextRow = FFNextTokenIn(currentRow, &length, end, delimiter);
        dict = [self dictionaryFromRow:currentRow length:length entity:entity];
        if (dict && (!qualifier || [(id <EOQualifierEvaluation>)qualifier evaluateWithObject:dict])) {
            if (nextRow) {
                memmove((void *)currentRow, (const void *)nextRow, 1 + end - nextRow);
                [table setLength:(1 + end - begin) - (nextRow - currentRow)];
                end -= (1 + nextRow - currentRow);
                nextRow = currentRow;
                deleted++;
            } else
                [table setLength:(1 + end - begin) - (1 + end - currentRow)];
        }
        currentRow = nextRow;
    }

    [(FlatFileContext *)_context channel:self didDirtyTable:[entity externalName]];

    [(FlatFileContext *)[self adaptorContext] autoCommitTransaction];

    return deleted;
}

- (void)selectAttributes:(NSArray *)attributes fetchSpecification:(EOFetchSpecification *)spec lock:(BOOL)yn entity:(EOEntity *)entity
{
    NSData *table;
    NSDictionary *dict;
    NSArray *sortOrderings = [spec sortOrderings];
    EOQualifier *qualifier = [spec qualifier];
    char *currentRow, *nextRow, *end, *delimiter;
    int length;

    [(FlatFileContext *)[self adaptorContext] autoBeginTransaction];

    if (_debug) NSLog(@"%s: fetching with spec: %@", sel_getName(_cmd), spec);
    
    if (_delegateRespondsTo.shouldSelectAttributes) {
        attributes = [[NSMutableArray alloc] initWithArray:attributes];
        if (![_delegate adaptorChannel:self shouldSelectAttributes:(NSMutableArray *)attributes fetchSpecification:spec lock:yn entity:entity])
            return;
    }

    resultSet = [[NSMutableArray array] retain];
    [self setAttributesToFetch:attributes];

    table = [(FlatFileContext *)_context dataForTable:[entity externalName]];
    currentRow = (char *)[table bytes];
    end = currentRow + [table length] - 1;
    delimiter = (char *)[rowSeparator cString];

    while (currentRow) {
        nextRow = FFNextTokenIn(currentRow, &length, end, delimiter);
        if (length) {
            dict = [self dictionaryFromRow:currentRow length:length entity:entity];
            if (dict && (!qualifier || [(id <EOQualifierEvaluation>)qualifier evaluateWithObject:dict])) {
                [resultSet addObject:dict];
            }
        }
        currentRow = nextRow;
    }

    if (_debug) NSLog(@"%s: match %d rows", sel_getName(_cmd), [resultSet count]);

    if ([resultSet count]) {
        if (sortOrderings)
            [resultSet sortUsingKeyOrderArray:sortOrderings];

        _flags.isFetchInProgress = YES;
        resultEnumerator = [[resultSet objectEnumerator] retain];
    } else
        [self cleanupFetch];

    if (_delegateRespondsTo.didSelectAttributes)
        [_delegate adaptorChannel:self didSelectAttributes:attributes fetchSpecification:spec lock:yn entity:entity];
}

- (void)evaluateExpression:(EOSQLExpression *)expression
{
    NSScanner *scanner = [NSScanner scannerWithString:[expression statement]];
    NSString *operation, *tableName, *columnList;
    NSCharacterSet *whitespace, *alpha;
    BOOL createTable = NO, isValidExpression = NO;

    whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
    alpha = [NSCharacterSet alphanumericCharacterSet];

    [scanner scanUpToCharactersFromSet:alpha intoString:NULL];

    if ([scanner scanUpToCharactersFromSet:whitespace intoString:&operation]) {
        createTable = [operation isEqualToString:@"create"];
        if (createTable || [operation isEqualToString:@"drop"]) {
            [scanner scanUpToCharactersFromSet:alpha intoString:NULL];
            if ([scanner scanUpToCharactersFromSet:whitespace intoString:&tableName]) {
                if (createTable) {
                    columnList = [expression listString];
                    if ([columnList length]) {
                        isValidExpression = YES;
                        [self createTable:tableName withColumnList:columnList];
                    }
                } else {
                    isValidExpression = YES;
                    [self dropTable:tableName];
                }
            }
        }
    }

    if (!isValidExpression)
        [NSException raise:EOGeneralAdaptorException format:@"can't evaluate %@", expression];
}

- (void)createTable:(NSString *)tableName withColumnList:(NSString *)columnList
{
    NSString *fullPath;
    NSArray *components;
    NSData *headerData;
    NSFileManager *fileManager = [NSFileManager defaultManager];

    fullPath = [[(FlatFileContext *)_context path] stringByAppendingPathComponent:tableName];
    if ([fileManager fileExistsAtPath:fullPath])
        [NSException raise:EOGeneralAdaptorException format:@"%@ already exists", fullPath];

    if ([columnList length]) {
        components = [columnList componentsSeparatedByString:@", "];
        columnList = [[components componentsJoinedByString:columnSeparator] stringByAppendingString:rowSeparator];
    }
    headerData = [NSData dataWithBytes:[columnList cString] length:[columnList cStringLength]];

    if (![headerData writeToFile:fullPath atomically:YES])
        [NSException raise:EOGeneralAdaptorException format:@"can't create %@", fullPath];
}

- (void)dropTable:(NSString *)tableName
{
    NSString *fullPath;
    NSFileManager *fileManager = [NSFileManager defaultManager];

    fullPath = [[(FlatFileContext *)_context path] stringByAppendingPathComponent:tableName];

    if ([fileManager fileExistsAtPath:fullPath])
        if (![fileManager removeFileAtPath:fullPath handler:nil])
            [NSException raise:EOGeneralAdaptorException format:@"can't remove %@", fullPath];
}

- (void)setAttributesToFetch:(NSArray *)attributes {
    [selectedAttributes autorelease];
    selectedAttributes = [attributes retain];
    [self buildColumnsForAttributes:attributes];
}

- (NSArray *)attributesToFetch
{
    return selectedAttributes;
}

- (BOOL)isFetchInProgress
{
    return _flags.isFetchInProgress;
}

- (NSArray *)describeResults
{
    if (![self isFetchInProgress])
        [NSException raise:NSInternalInconsistencyException format:@"%@ -- %@ 0x%x: attempt to describe results with no fetch in progress", NSStringFromSelector(_cmd), NSStringFromClass([self class]), self];

    return [self attributesToFetch];
}

- (NSMutableDictionary *)fetchRowWithZone:(NSZone *)zone
{
    NSMutableDictionary *result;

    if (_delegateRespondsTo.willFetchRow) {
        [_delegate adaptorChannelWillFetchRow:self];
    }

    if (!_flags.isFetchInProgress)
        return nil;

    result = [resultEnumerator nextObject];
    if (result) {
        if (_delegateRespondsTo.didFetchRow)
            [_delegate adaptorChannel:self didFetchRow:result];
    }
    else
        [self cleanupFetch];

    return result;
}

- (void)cancelFetch
{
    [self cleanupFetch];
}

- (NSDictionary *)primaryKeyForNewRowWithEntity:(EOEntity *)entity
{
    return nil;
}

- (void)cleanupFetch
{
    _flags.isFetchInProgress = NO;
    [resultEnumerator autorelease];
    [resultSet autorelease];
    resultEnumerator = nil;
    resultSet = nil;
    [(FlatFileContext *)[self adaptorContext] autoCommitTransaction];
}

- (NSDictionary *)dictionaryFromRow:(char *)row length:(int)rowLength entity:(EOEntity *)entity
{
    FlatFileColumn *column;
    NSDictionary *dict;
    NSZone *zone = [self zone];
    EONull *nullValue = [EONull null];
    int i, columnCount, columnLength;
    id valueBuffer [100], *values;
    char *current, *next, *end, *delimiter;

    if (!rowLength)
        return nil;

    columnCount = [columns count];

    if (columnCount > 100)
        values = (id *)malloc(columnCount * sizeof(id));
    else
        values = valueBuffer;

    current = row;
    delimiter = (char *)[columnSeparator cString];
    end = current + rowLength - 1;
    next = NULL;
    
    for (i = 0; i < columnCount; i++) {
        column = [columns objectAtIndex:i];
        if (current)
            next = FFNextTokenIn(current, &columnLength, end, delimiter);

        if (current && columnLength)
            values[[column bindingIndex]] = [column newValueForBytes:current length:columnLength];
        else
            values[[column bindingIndex]] = nullValue;

        current = next;
    }
    dict = [self dictionaryWithObjects:values forAttributes:columnAttributes zone:zone];

    if (values != valueBuffer)
        free(values);

    return dict;
}

- (NSData *)dataFromDictionary:(NSDictionary *)dict entity:(EOEntity *)entity
{
    NSArray *attributes = [entity attributes];
    NSString *string, *columnName;
    NSDictionary *columnIndexMap;
    NSMutableData *row = [NSMutableData data];
    NSNumber *columnIndex;
    EOAttribute *attribute;
    EONull *nullValue = [EONull null];
    id *strings, stringBuffer[100], value;
    int i, count = [attributes count], delimiterLength, columnCount = 0;
    char *delimiter;
    
    columnIndexMap = [(FlatFileContext *)_context columnIndexMapForTable:[entity externalName]];
    delimiter = (char *)[columnSeparator cString];
    delimiterLength = [columnSeparator cStringLength];
    strings = (count > 100) ? (id *)malloc(count * sizeof(id)) : stringBuffer;

    for (i = 0; i < count; i++) {
        attribute = [attributes objectAtIndex:i];
        columnName = [attribute columnName];
        if (columnName) {
            columnIndex = [columnIndexMap objectForKey:columnName];
            value = [dict objectForKey:[attribute name]];

            if (value && (value != nullValue)) {
                value = [attribute adaptorValueByConvertingAttributeValue:value];
                strings[[columnIndex intValue]] = [value flatFileFormattedDescription];
            } else
                strings[[columnIndex intValue]] = nil;

            columnCount++;
        }
    }

    for (i = 0; i < columnCount; i++) {
        if (i)
            [row appendBytes:delimiter length:delimiterLength];

        string = strings [i];
        if (string)
            [row appendBytes:[string cString] length:[string cStringLength]];
    }

    if (strings != stringBuffer)
        free(strings);

    return row;
}


- (void)buildColumnsForAttributes:(NSArray *)attributes
{
    NSString *tableName;
    EOEntity *entity = [[attributes objectAtIndex:0] entity];
    NSEnumerator *enumerator;
    EOAttribute *attribute;
    NSString *columnName;
    NSDictionary *columnIndexMap;
    FlatFileColumn *column;
    NSArray *allAttributes = [entity attributes];
    int bindingIndex = 0, count = [allAttributes count];

    if (columns) {
        [columns release];
        [columnAttributes release];
    }

    if (count) {
        columnAttributes = [[NSMutableArray alloc] initWithCapacity:count];
        columns = [[NSMutableArray alloc] initWithCapacity:count];
        enumerator = [allAttributes objectEnumerator];
        tableName = [entity externalName];
        columnIndexMap = [(FlatFileContext *)_context columnIndexMapForTable:tableName];

        while (attribute = [enumerator nextObject]) {
            columnName = [attribute columnName];
            if (columnName) {
                column = [FlatFileColumn columnForAttribute:attribute];
                [columnAttributes addObject:attribute];
                [columns addObject:column];
                [column bindAtIndex:bindingIndex++];
            }
        }
    }

    if (!bindingIndex) {
        [columnAttributes release];
        [columns release];
        columnAttributes = nil;
        columns = nil;
    }

    selectedEntity = entity;
}

@end

@implementation NSObject (FlatFileAdaptorFormatting)

- (NSString *)flatFileFormattedDescription
{
    return [self description];
}

@end

@implementation NSCalendarDate (FlatFileAdaptorFormatting)

- (NSString *)flatFileFormattedDescription
{
    return [self descriptionWithCalendarFormat:FlatFileCalendarFormat];
}

@end







