/*
    Copyright (c) 1998-2005 Benhur Stein
    
    This file is part of Pajé.

    Pajé is free software; you can redistribute it and/or modify it under
    the terms of the GNU Lesser General Public License as published by the
    Free Software Foundation; either version 2 of the License, or (at your
    option) any later version.

    Pajé is distributed in the hope that it will be useful, but WITHOUT ANY
    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
    for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Pajé; if not, write to the Free Software Foundation, Inc.,
    59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
#include "PajeEventDecoder.h"
#include "../General/FoundationAdditions.h"
#include "../General/UniqueString.h"
#include "../General/NSUserDefaults+Additions.h"
#include "../General/NSColor+Additions.h"
#include "../General/Macros.h"

@implementation PajeEventDecoder

static NSString *INT_VALUE = @"Int";
static NSString *HEX_VALUE = @"Hex";
static NSString *TIME_VALUE = @"Date";
static NSString *DOUBLE_VALUE = @"Double";
static NSString *STRING_VALUE = @"String";
static NSString *COLOR_VALUE = @"Color";

- (id)initWithController:(PajeTraceController *)c
{
    [super initWithController:c];

    status = OUT_DEF;
    eventBeingDefined.fieldTypes = [[NSMutableArray alloc] init];
    eventBeingDefined.fieldNames = [[NSMutableArray alloc] init];

    eventNames = [[NSMutableDictionary dictionary] retain];
    eventFieldTypes = [[NSMutableDictionary dictionary] retain];
    eventFieldNames = [[NSMutableDictionary dictionary] retain];
    scanner = nil;
    percent = [[NSCharacterSet characterSetWithCharactersInString:@"%"] retain];
    alphanum = [[NSMutableCharacterSet characterSetWithCharactersInString:
        @"%0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"] retain];
//alphanumericCharacterSet];
//    [alphanum addCharactersInString:@"_"];
    delimiter = [[NSCharacterSet whitespaceAndNewlineCharacterSet] retain];

    return self;
}

- (void)encodeCheckPointWithCoder:(NSCoder *)coder
{
    [coder encodeValuesOfObjCTypes:"ii", &eventCount, &lineCount];
    NSDebugMLLog(@"tim", @"encoded %d %d", eventCount, lineCount);
}

- (void)decodeCheckPointWithCoder:(NSCoder *)coder
{
    [coder decodeValuesOfObjCTypes:"ii", &eventCount, &lineCount];
    NSDebugMLLog(@"tim", @"decoded %d %d", eventCount, lineCount);
}

- (void)dealloc
{
    [eventBeingDefined.fieldTypes release];
    [eventBeingDefined.fieldNames release];
    [eventBeingDefined.eventId release];
    [eventBeingDefined.eventName release];

    [valArray release];
    [eventNames release];
    [eventFieldTypes release];
    [eventFieldNames release];
    [percent release];
    [alphanum release];
    [delimiter release];
    [scanner release];
    [super dealloc];
}

- (void)reset
{
    [eventNames removeAllObjects];
    [eventFieldTypes removeAllObjects];
    [eventFieldNames removeAllObjects];
    Assign(scanner, nil);
}

- (BOOL)readNextLine
{
    int c;
    if (scanner && ![scanner isAtEnd]) {
        do {
            c = [scanner readChar];
	} while (c != '\n' && ![scanner isAtEnd]);
        lineCount++;
        return ![scanner isAtEnd];
    }
    return NO;
}

- (void)raise:(NSString *)reason
{
    [self reset];
    [[NSException exceptionWithName:@"DecodeFileException"
                             reason:reason
                           userInfo:
        [NSDictionary dictionaryWithObjectsAndKeys:
            [NSNumber numberWithInt:eventCount], @"Event Number",
            nil]
        ] raise];
}

- (void)inputEntity:(id)data
{    
    if (![data isKindOfClass:[NSData class]]) {
        [self raise:@"Internal error: incorrect data type"];
    }
    
    Assign(scanner, [DataScanner scannerWithData:(NSData *)data]);
    
    do {
        if (status != EVENTS) {
            [self scanDefinitionLine];
        }
	if (status == EVENTS) {
            PajeEvent *event;
            event = [self readEvent];
            if (event != nil) {
                [self outputEntity:event];
            }
        }
    } while ([self readNextLine]);
    
    Assign(scanner, nil);
}

- (BOOL)readPercent
{
    int c = [scanner readChar];
    if (c == '%') {
        return YES;
    }
    [scanner setPosition:[scanner position] - 1];
    return NO;
}

- (void)scanDefinitionLine
{
    NSString *fieldName, *fieldType, *str;

    switch (status) {
    case OUT_DEF:
        if (![self readPercent]) {
            status = EVENTS;
            break;
        }

        str = [scanner readString];
        Assign(eventBeingDefined.eventName, [scanner readString]);
        Assign(eventBeingDefined.eventId, [scanner readIntNumber]);
        if (![str isEqualToString:@"EventDef"]
            || eventBeingDefined.eventName == nil
            || eventBeingDefined.eventId == nil) {
            [self raise:@"'EventDef name number' expected"];
        }

        [eventBeingDefined.fieldTypes removeAllObjects];
        [eventBeingDefined.fieldNames removeAllObjects];
        [eventBeingDefined.fieldNames addObject: @"PajeEventId"];
        [eventBeingDefined.fieldNames addObject: @"PajeEventName"];
        status = IN_DEF;
        break;
    case IN_DEF:
        if (![self readPercent]) {
            [self raise:@"Line should start with a '%'"];
        }

        fieldName = [scanner readString];
        if (fieldName == nil) {
            [self raise:@"Incomplete line, missing field name"];
        }
        if ([fieldName isEqualToString:@"EndEventDef"]) {
            [eventNames setObject:U(eventBeingDefined.eventName)
                           forKey:eventBeingDefined.eventId];
            [eventFieldTypes setObject:[[eventBeingDefined.fieldTypes copy] autorelease]
                                forKey:eventBeingDefined.eventId];
            [eventFieldNames setObject:[[eventBeingDefined.fieldNames copy] autorelease]
                                forKey:eventBeingDefined.eventId];
            status = OUT_DEF;
            break;
        }

        fieldType = [scanner readString];
        if (fieldType == nil) {
            [self raise:@"Incomplete line, missing field type"];
        }

        if ([fieldType isEqualToString:@"int"])
            [eventBeingDefined.fieldTypes addObject:INT_VALUE];
        else if ([fieldType isEqualToString:@"hex"])
            [eventBeingDefined.fieldTypes addObject:HEX_VALUE];
        else if ([fieldType isEqualToString:@"double"])
            [eventBeingDefined.fieldTypes addObject:DOUBLE_VALUE];
        else if ([fieldType isEqualToString:@"date"])
            [eventBeingDefined.fieldTypes addObject:TIME_VALUE];
        else if ([fieldType isEqualToString:@"string"])
            [eventBeingDefined.fieldTypes addObject:STRING_VALUE];
        else if ([fieldType isEqualToString:@"color"])
            [eventBeingDefined.fieldTypes addObject:COLOR_VALUE];
        else
            [self raise:[NSString stringWithFormat:@"Unrecognised field type '%@'", fieldType]];

        [eventBeingDefined.fieldNames addObject:fieldName];
        break;
    default:
        [self raise:@"Internal error, invalid status"];
    }
}

- (PajeEvent *)readEvent
{
    NSNumber *eventId;
    NSString *eventName;
    static PajeEvent *event;
    NSEnumerator *fieldTypeEnum;
    NSString *type;
    NSNumber *aNumber;
    double aDouble;
    NSString *aString;
//static double tt;
//static NSDate *last = nil;
//static int n = 0;
//if (last == nil) last = [NSDate new];
//if ((++n %1000)==0) { double t = [[NSDate date] timeIntervalSinceDate:last]; Assign(last, [NSDate date]); NSLog(@"%d %f/s t=%f e=%@", n, 1000/t, tt, event);}

    if (valArray == nil) {
        valArray = [NSMutableArray new];
    }

    eventId = [scanner readIntNumber];
    if (eventId == nil) {
        return nil;
    }
    
    eventName = [eventNames objectForKey:eventId];
    if (eventName == nil) {
        [self raise: @"Unrecognized event id"];
    }
    [valArray addObject:eventId];
    [valArray addObject:eventName];

    fieldTypeEnum = [[eventFieldTypes objectForKey:eventId] objectEnumerator];
    while ((type = [fieldTypeEnum nextObject]) != nil) {
        if (type == INT_VALUE) {
            aNumber = [scanner readIntNumber];
            [valArray addObject:aNumber];
        } else if (type == DOUBLE_VALUE) {
            aNumber = [scanner readDoubleNumber];
            [valArray addObject:aNumber];
        } else if (type == TIME_VALUE) {
            aDouble = [scanner readDouble];
//tt=aDouble;
            [valArray addObject:[NSDate/*PTime*/ dateWithTimeIntervalSinceReferenceDate:aDouble]];
        } else if (type == HEX_VALUE || type == STRING_VALUE) {
            // string can be delimited by ""
            aString = [scanner readString];
            [valArray addObject:aString];
        } else if (type == COLOR_VALUE) {
            // string can be delimited by ""
            aString = [scanner readString];
            [valArray addObject:[NSColor colorFromString:aString]];
        } else
            [self raise: @"Unknown data type"];
    }

    event = [PajeEvent eventWithObjects:valArray
                                forKeys:[eventFieldNames objectForKey:eventId]];
    [valArray removeAllObjects];

    eventCount++;
    return event;
}

- (int)eventCount
{
    return eventCount;
}
@end
