/*
    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.
*/
/*
 * PajeSimul.m
 *
 * Simulator for Paje traces.
 *
 * 20021107 BS  creation (from A0bSimul)
 */


#include "PajeSimul.h"
#include "SimulContainer.h"
#include "../General/UniqueString.h"
#include "../General/SourceCodeReference.h"
#include "../General/NSDictionary+Additions.h"
#include "../General/Macros.h"

#define DEFINE_STRINGS
#include "EventNames.h"
#undef DEFINE_STRINGS

@implementation PajeSimul

+ (void)initialize
{
    if (self == [PajeSimul class]) {
#define INIT_STRINGS
#include "EventNames.h"
#undef INIT_STRINGS
    }
}

- (void)error:(NSString *)format, ...
{
    va_list args;

    va_start(args, format);
    //NSLogv(format, args);
    [NSException raise:@"PajeSimulException" format:format arguments:args];
    va_end(args);
}

- (void)error:(NSString *)str inEvent:(PajeEvent *)event
{
    PajeEvent *eventCopy;
    NSDate *evTime;
    evTime = [event valueOfFieldNamed:@"Time"];
    if (evTime != nil) {
        id n;
        eventCopy = [event mutableCopy];
        n = [NSNumber numberWithDouble:[evTime timeIntervalSinceReferenceDate]];
        [eventCopy setValue:n ofFieldNamed:@"Time"];
    } else {
        eventCopy = [event retain];
    }
    [self error:@"%@ in event %@", str,
                [eventCopy descriptionWithLocale:[NSDictionary dictionary]
                                          indent:0]];
    [eventCopy release];
}

- (PajeContainer *)rootInstance
{
    return [self rootContainer];
}

- (PajeContainer *)rootContainer
{
    if (rootContainer == nil) {
        PajeContainerType *rootContainerType;
        NSString *rootContainerName;
    
        rootContainerName = [inputComponent traceDescription];
        if (rootContainerName == nil) {
            return nil;
        }

        rootContainerType = [PajeContainerType typeWithName:@"File"
                                              containerType:nil
                                                      event:nil];
        rootContainer = [[SimulContainer alloc] initWithType:rootContainerType
                                                        name:rootContainerName
                                                       alias:@"0"
                                                   container:nil
                                                creationTime:[NSDate dateWithTimeIntervalSinceReferenceDate:0]
                                                   simulator:self];
        [rootContainerType addInstance:rootContainer];

        [userTypes setObject:rootContainerType forKey:@"0"];
        [userTypes setObject:rootContainerType forKey:@"/"];
        [userTypes setObject:rootContainerType forKey:@"File"];
                            
        [userNumberToContainer setObject:rootContainer forKey:@"0"];
        [userNumberToContainer setObject:rootContainer forKey:@"/"];
        [userNumberToContainer setObject:rootContainer
                                  forKey:rootContainerName];
    }
    return rootContainer;
}

- (PajeContainerType *)rootContainerType
{
    return (PajeContainerType *)[[self rootContainer] entityType];
}

- (NSString *)delete_name {
    return NSStringFromClass([self class]);
}

#ifndef MAP
#undef NS_MESSAGE
#ifndef NS_MESSAGE
#ifndef GNUSTEP
NSInvocation *invocation;
#define NS_MESSAGE(obj, meth)                                   \
    ((invocation = [NSInvocation invocationWithMethodSignature: \
    [obj methodSignatureForSelector: @selector(meth)]]),        \
    [invocation setTarget: obj],                                \
    [invocation setSelector: @selector(meth)],                  \
    invocation)
#else
#define NS_MESSAGE(target, message)                             \
    ([[[NSInvocation alloc] initWithTarget:target                \
    selector:@selector(message), nil] autorelease])
#endif                          // GNUSTEP
#define ADD_INVOCATION(name) [invocationTable  \
                                    setObject:NS_MESSAGE(self, paje##name:) \
                                       forKey:Paje##name##EventName]
#else                           // NS_MESSAGE
#define ADD_INVOCATION(name) [invocationTable  \
                                    setObject:NS_MESSAGE(self, paje##name:nil) \
                                       forKey:Paje##name##EventName]
#endif                          // NS_MESSAGE
#else /* MAP */
#define ADD_INVOCATION(name) NSMapInsert(invocationTable, \
                                         Paje##name##EventName, \
                                         [self methodForSelector:@selector(paje##name:)])
#endif /* MAP */

- (void)_initInvocationTable
{
#ifdef MAP
    invocationTable = NSCreateMapTable(NSIntMapKeyCallBacks/*NSObjectMapKeyCallBacks*/,
                                       NSIntMapValueCallBacks, 50);
#else
    Assign(invocationTable, [NSMutableDictionary dictionary]);
#endif
    ADD_INVOCATION(StartTrace);
    ADD_INVOCATION(DefineContainerType);
    ADD_INVOCATION(DefineEventType);
    ADD_INVOCATION(DefineStateType);
    ADD_INVOCATION(DefineVariableType);
    ADD_INVOCATION(DefineLinkType);
    ADD_INVOCATION(DefineEntityValue);
    ADD_INVOCATION(CreateContainer);
    ADD_INVOCATION(DestroyContainer);
    ADD_INVOCATION(NewEvent);
    ADD_INVOCATION(SetState);
    ADD_INVOCATION(PushState);
    ADD_INVOCATION(PopState);
    ADD_INVOCATION(SetVariable);
    ADD_INVOCATION(AddVariable);
    ADD_INVOCATION(SubVariable);
    ADD_INVOCATION(StartLink);
    ADD_INVOCATION(EndLink);
}

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

    if (self != nil) {
        userTypes = [[NSMutableDictionary alloc] init];
        userNumberToContainer = [[NSMutableDictionary alloc] init];
        relatedEntities = [[NSMutableDictionary alloc] init];

        [self _initInvocationTable];
    }

    return self;
}

- (void)dealloc
{
    [rootContainer release];
#ifdef MAP
    NSFreeMapTable(invocationTable);
#else
    [invocationTable release];
#endif
    [userTypes release];
    [userNumberToContainer release];
    [relatedEntities release];
    [startTime release];
    [endTime release];
    [currentTime release];
    [super dealloc];
}

- (NSDate *)startTime
{
    return startTime;
}

- (NSDate *)endTime
{
    return endTime;
}

- (void)setCurrentTime:(NSDate *)time
{
    Assign(currentTime, time);
    if (endTime == nil || [time isLaterThanDate:endTime]) {
        Assign(endTime, time);
        if (startTime == nil) {
            Assign(startTime, time);
        }
    } else if ([time isEarlierThanDate:startTime]) {
        Assign(startTime, time);
    }
//    NSLog(@"simul times: (%f-%f) %f", [startTime timeIntervalSinceReferenceDate], [endTime timeIntervalSinceReferenceDate], [currentTime timeIntervalSinceReferenceDate]);
}

- (NSDate *)currentTime
{
    return currentTime;
}

- (int)eventCount
{
    return eventCount;
}

- (PajeEntityType *)entityTypeWithName:(NSString *)name
{
    return [userTypes objectForKey:name];
}

- (void)inputEntity:(PajeEvent *)event
{
    id time;

    [self rootContainer];

{
    NSString *filename;
        // set event's filename
//    file = [event objectForKey:@"File"];
//    if (file)
//        filename = [fileToFilename objectForKey:file];
//    else
        filename = [event objectForKey:@"FileName"];
    if (filename) {
        id line;
        int lineNumber;
        SourceCodeReference *sourceRef;
//        NSMutableDictionary *refToEvents;

        line = [event objectForKey:@"Line"];
        if (line == nil)
            line = [event objectForKey:@"LineNumber"];
        lineNumber = [line intValue];
        sourceRef = [SourceCodeReference referenceToFilename:filename
                                                  lineNumber:lineNumber];
//        refToEvents = [filenameToReferences objectForKey:filename];
//        [refToEvents addObject:event forKey:sourceRef];
        [event removeObjectForKey:@"File"];
        [event removeObjectForKey:@"FileName"];
        [event removeObjectForKey:@"Line"];
        [event removeObjectForKey:@"LineNumber"];
        [event setObject:sourceRef forKey:@"FileReference"];
    }

}

    // set the current time, if event has one
    time = [event time];
    if (time != nil) {
        [self setCurrentTime:time];
    }
    // invoke the method that simulates this event
#ifndef MAP
    NSInvocation *invocation;
    invocation = [invocationTable objectForKey:[event pajeEventName]];
    if (invocation) {
        [invocation setArgument:&event atIndex:2];
        [invocation invoke];
    }
#else
    void (*f)(id, SEL, id);
    f = NSMapGet(invocationTable, [event pajeEventName]);
    if (f != NULL) {
        f(self, 0, event);
    } else {
        NSLog(@"Unknown event \"%@\"", [event pajeEventName]);
    }
#endif
    
    eventCount++;
}

- (void)_resetContainers
{
    [[userNumberToContainer allValues]
        makeObjectsPerformSelector:@selector(reset)];
}

- (void)addRelatedEntity:(id)entity toKey:(id)key
{
    NSMutableArray *entities;
    entities = [relatedEntities objectForKey:key];
    if (entities == nil) {
        entities = [NSMutableArray arrayWithObject:entity];
        [relatedEntities setObject:entities forKey:key];
    } else {
        [entities addObject:entity];
    }
}

- (NSArray *)relatedEntitiesForEntity:(id<PajeEntity>)entity
{
    id key;

    key = [entity valueOfFieldNamed:@"RelationKey"];
    if (key != nil) {
        return [relatedEntities objectForKey:key];
    } else {
        return [entity relatedEntities];
    }
}

- (void)outputEntity:(id)entity
{
    id key;
    key = [(id<PajeEntity>)entity valueOfFieldNamed:@"RelationKey"];
    if (key != nil) {
        [self addRelatedEntity:entity toKey:key];
    }
    [super outputEntity:entity];
}

- (void)outputChunk:(id)entity
{
    [super outputEntity:entity];
}


- (void)endOfChunkInContainer:(SimulContainer *)container
{
    NSEnumerator *subContainerEnumerator;
    SimulContainer *subContainer;

    [container endOfChunk];

    subContainerEnumerator = [[container subContainers] objectEnumerator];
    while ((subContainer = [subContainerEnumerator nextObject]) != nil) {
        [self endOfChunkInContainer:subContainer];
    }
}

- (void)endOfChunk
{
    [self endOfChunkInContainer:rootContainer];
}


- (void)encodeCheckPointWithCoder:(NSCoder *)coder
{
    NSEnumerator *containerEnum;
    SimulContainer *container;
    NSMutableSet *c;

    c = [[NSMutableSet alloc] init];

//    [coder encodeObject:startTime];
//    [coder encodeObject:endTime];
    [coder encodeObject:currentTime];
    [coder encodeValueOfObjCType:@encode(int) at:&eventCount];
    // FIXME: should be saved by type
    containerEnum = [[userNumberToContainer allValues] objectEnumerator];
    while ((container = [containerEnum nextObject]) != nil) {
        if (![c containsObject:[container alias]]) {
            [c addObject:[container alias]];
            [coder encodeObject:[container alias]];
            [container encodeCheckPointWithCoder:coder];
        }
    }
    [coder encodeObject:nil];
    [c release];
}

- (void)decodeCheckPointWithCoder:(NSCoder *)coder
{
    NSString *containerName;
    
//    Assign(startTime, [coder decodeObject]);
//    Assign(endTime, [coder decodeObject]);
    Assign(currentTime, [coder decodeObject]);
    [coder decodeValueOfObjCType:@encode(int) at:&eventCount];

    [self _resetContainers];

    while ((containerName = [coder decodeObject]) != nil) {
        SimulContainer *container;
        container = [userNumberToContainer objectForKey:containerName];
        if (container == nil) {
        NSLog(@"known: %@", userNumberToContainer);
            [self error:@"Decoding unknown container '%@'", containerName];
        }
        NSDebugMLLog(@"enc", @"found container %@", containerName);
        [container decodeCheckPointWithCoder:coder];
    }
}

- (id)copyWithZone:(NSZone *)zone
{
    return [self retain];
}
@end
