/*
    Copyright (c) 1998, 1999, 2000, 2001, 2003, 2004 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+Events.m created by benhur on 12-nov-2002 (from A0bSimul) */

#include "PajeSimul.h"
#include "SimulContainer.h"
#include "../General/PajeType.h"
#include "UserEvent.h"
#include "../General/Macros.h"

@implementation PajeSimul (UserEvents)
//
// User defined entities
//

- (id)containerOfNumber:(NSString *)containerNumber
                   type:(id)containerType
                inEvent:(PajeEvent *)event
{
    if (containerType == nil) {
        // GAMBIARRA, deveria procurar em todos os tipos
        // precisa disso por causa de PajeDestroyContainer, que antes
        // nao tinha o tipo do container a destruir
        return [userNumberToContainer objectForKey:containerNumber];
    }

    if (![containerType isKindOfClass:[PajeContainerType class]]) {
        return nil;
    }

    return [containerType instanceWithId:containerNumber];
}


- (void)pajeStartTrace:(PajeEvent *)event
{
    Assign(startTime, [event objectForKey:@"StartTime"]);
    Assign(endTime, [event objectForKey:@"EndTime"]);
}

- (void)pajeDefineContainerType:(PajeEvent *)event
{
    NSString *newContainerTypeAlias;
    NSString *containerTypeId;
    NSString *newContainerTypeName;

    PajeContainerType *containerType;
    PajeContainerType *newContainerType;

    // get fields from event
    newContainerTypeName   = [event objectForKey:@"Name"];
    newContainerTypeAlias  = [event objectForKey:@"Alias"];
    containerTypeId        = [event objectForKey:@"ContainerType"];

    // get fields with old names
    if (newContainerTypeName == nil) {
        newContainerTypeName   = [event objectForKey:@"NewName"];
    }
    if (newContainerTypeAlias == nil) {
        newContainerTypeAlias  = [event objectForKey:@"NewType"];
    }

    // verify presence of obligatory fields
    if (newContainerTypeName == nil) {
        [self error:@"Missing \"Name\" field" inEvent:event];
    }
    if (containerTypeId == nil) {
        [self error:@"Missing \"ContainerType\" field" inEvent:event];
    }

    containerType = [userTypes objectForKey:containerTypeId];
    if (!containerType) {
        [self error:@"Unknown container type" inEvent:event];
    }

    // new type should not exist (but may, if replaying)
    if ([userTypes objectForKey:newContainerTypeName] != nil) {
        //NSWarnLog(@"Redefining container type %@ with event %@",
        //          newContainerTypeName, event);
        return;
    }
    if (newContainerTypeAlias != nil 
        && [userTypes objectForKey:newContainerTypeAlias] != nil) {
        //NSWarnLog(@"Redefining container type alias %@ with event %@",
        //          newContainerTypeAlias, event);
        return;
    }

    // create the new container type
    newContainerType = [PajeContainerType typeWithName:newContainerTypeName
                                         containerType:containerType
                                                 event:event];
    [userTypes setObject:newContainerType forKey:newContainerTypeName];
    if (newContainerTypeAlias != nil) {
        [userTypes setObject:newContainerType forKey:newContainerTypeAlias];
    }
}

- (void)pajeDefineLinkType:(PajeEvent *)event
{
    NSNumber *newEntityTypeAlias;
    NSNumber *containerTypeId;
    NSNumber *sourceContainerTypeId;
    NSNumber *destContainerTypeId;
    NSString *newEntityTypeName;

    PajeContainerType *containerType;
    PajeContainerType *sourceContainerType;
    PajeContainerType *destContainerType;
    PajeEntityType *newEntityType;

    // get fields from event
    newEntityTypeName         = [event objectForKey:@"Name"];
    newEntityTypeAlias        = [event objectForKey:@"Alias"];
    containerTypeId           = [event objectForKey:@"ContainerType"];
    sourceContainerTypeId     = [event objectForKey:@"SourceContainerType"];
    destContainerTypeId       = [event objectForKey:@"DestContainerType"];

    // old field names
    if (newEntityTypeName == nil) {
        newEntityTypeName         = [event objectForKey:@"NewName"];
    }
    if (newEntityTypeAlias == nil) {
        newEntityTypeAlias        = [event objectForKey:@"NewType"];
    }

    containerType = [userTypes objectForKey:containerTypeId];
    if (containerType == nil) {
        [self error:@"Unknown container type" inEvent:event];
    }

    sourceContainerType = [userTypes objectForKey:sourceContainerTypeId];
    if (sourceContainerType == nil) {
        [self error:@"Unknown source container type" inEvent:event];
    }

    if (destContainerTypeId == nil) {
        destContainerTypeId       = [event objectForKey:@"DestinContainerType"];
    }
    destContainerType = [userTypes objectForKey:destContainerTypeId];
    if (destContainerType == nil) {
        [self error:@"Unknown dest container type" inEvent:event];
    }

    // new type should not exist
    if ([userTypes objectForKey:newEntityTypeName] != nil) {
        //NSWarnLog(@"Redefining entity type %@ with event %@",
        //          newEntityTypeName, event);
        return;
    }
    if (newEntityTypeAlias != nil 
        && [userTypes objectForKey:newEntityTypeAlias] != nil) {
        //NSWarnLog(@"Redefining entity type alias %@ with event %@",
        //          newEntityTypeAlias, event);
        return;
    }

    newEntityType = [PajeLinkType typeWithName:newEntityTypeName
                                 containerType:containerType
                           sourceContainerType:sourceContainerType
                             destContainerType:destContainerType
                                         event:event];
    [userTypes setObject:newEntityType forKey:newEntityTypeName];
    if (newEntityTypeAlias != nil) {
        [userTypes setObject:newEntityType forKey:newEntityTypeAlias];
    }
}

- (void)_defineUserEntityType:(PajeEvent *)event
                  drawingType:(PajeDrawingType)drawingType
{
    NSNumber *newEntityTypeAlias;
    NSNumber *containerTypeId;
    NSString *newEntityTypeName;

    PajeContainerType *containerType;
    PajeEntityType *newEntityType;

    // get fields from event
    newEntityTypeName   = [event objectForKey:@"Name"];
    newEntityTypeAlias  = [event objectForKey:@"Alias"];
    containerTypeId     = [event objectForKey:@"ContainerType"];

    // old field names
    if (newEntityTypeName == nil) {
        newEntityTypeName   = [event objectForKey:@"NewName"];
    }
    if (newEntityTypeAlias == nil) {
        newEntityTypeAlias  = [event objectForKey:@"NewType"];
    }

    // new type should not exist
    if ([userTypes objectForKey:newEntityTypeName] != nil) {
        //NSWarnLog(@"Redefining entity type %@ with event %@",
        //          newEntityTypeName, event);
        return;
    }
    if (newEntityTypeAlias != nil 
        && [userTypes objectForKey:newEntityTypeAlias] != nil) {
        //NSWarnLog(@"Redefining entity type alias %@ with event %@",
        //          newEntityTypeAlias, event);
        return;
    }

    containerType = [userTypes objectForKey:containerTypeId];
    if (!containerType) {
        [self error:@"Unknown container type" inEvent:event];
    }

    switch (drawingType) {
    case PajeEventDrawingType:
        newEntityType = [PajeEventType typeWithName:newEntityTypeName
                                      containerType:containerType
                                              event:event];
        break;
    case PajeStateDrawingType:
        newEntityType = [PajeStateType typeWithName:newEntityTypeName
                                      containerType:containerType
                                              event:event];
        break;
    case PajeVariableDrawingType:
        newEntityType = [PajeVariableType typeWithName:newEntityTypeName
                                         containerType:containerType
                                                 event:event];
        break;
    default:
        [self error:@"Internal simulator error: unknown drawing type"
            inEvent:event];
    }

    [userTypes setObject:newEntityType forKey:newEntityTypeName];
    if (newEntityTypeAlias != nil) {
        [userTypes setObject:newEntityType forKey:newEntityTypeAlias];
    }
}

- (void)pajeDefineEventType:(PajeEvent *)event
{
    [self _defineUserEntityType:event drawingType:PajeEventDrawingType];
}

- (void)pajeDefineStateType:(PajeEvent *)event
{
    [self _defineUserEntityType:event drawingType:PajeStateDrawingType];
}

- (void)pajeDefineVariableType:(PajeEvent *)event
{
    [self _defineUserEntityType:event drawingType:PajeVariableDrawingType];
}


- (void)pajeDefineEntityValue:(PajeEvent *)event
{
    NSString *newEntityValueAlias;
    NSString *entityTypeId;
    NSString *newEntityValueName;
    NSColor *color;

    PajeEntityType *entityType;

    // get fields from event
    newEntityValueName   = [event objectForKey:@"Name"];
    newEntityValueAlias  = [event objectForKey:@"Alias"];
    entityTypeId         = [event objectForKey:@"EntityType"];
    color                = [event objectForKey:@"Color"];
    if (entityTypeId == nil) {
        entityTypeId  = [event objectForKey:@"Type"];
    }

    // old field names
    if (newEntityValueName == nil) {
        newEntityValueName   = [event objectForKey:@"NewName"];
    }
    if (newEntityValueAlias == nil) {
        newEntityValueAlias  = [event objectForKey:@"NewValue"];
    }

    entityType = [userTypes objectForKey:entityTypeId];
    if (!entityType) {
        [self error:@"Unknown entity type" inEvent:event];
    }

    if ([entityType drawingType] == PajeVariableDrawingType) {
        NSWarnLog(@"Values of variables cannot be named in event %@", event);
        return;
    }

    if (color != nil) {
        [(PajeEventType *)entityType setValue:newEntityValueName
                                        alias:newEntityValueAlias
                                        color:color];
    } else {
        [(PajeEventType *)entityType setValue:newEntityValueName
                                        alias:newEntityValueAlias];
    }
}



- (void)pajeCreateContainer:(PajeEvent *)event
{
    NSString *newContainerAlias;
    NSString *newContainerTypeId;
    NSString *containerId;
    NSString *newContainerName;

    PajeContainerType *typeOfNewContainer;
    PajeContainer *container;
    PajeContainer *newContainer;

    // get fields from event
    newContainerName       = [event objectForKey:@"Name"];
    newContainerAlias      = [event objectForKey:@"Alias"]; // optional
    newContainerTypeId     = [event objectForKey:@"Type"];
    containerId            = [event objectForKey:@"Container"];
    
    // get fields with old names
    if (newContainerName == nil) {
        newContainerName       = [event objectForKey:@"NewName"];
    }
    if (newContainerAlias == nil) {
        newContainerAlias      = [event objectForKey:@"NewContainer"];
    }
    if (newContainerTypeId == nil) {
        newContainerTypeId     = [event objectForKey:@"NewContainerType"];
    }

    if (newContainerName == nil) {
        [self error:@"Missing \"Name\" field" inEvent:event];
    }
    if (newContainerTypeId == nil) {
        [self error:@"Missing \"Type\" field" inEvent:event];
    }
    if (containerId == nil) {
        [self error:@"Missing \"Container\" field" inEvent:event];
    }

    typeOfNewContainer = [userTypes objectForKey:newContainerTypeId];
    if (!typeOfNewContainer) {
        [self error:@"Unknown container type" inEvent:event];
    }
    
    if ([self containerOfNumber:newContainerName 
                           type:typeOfNewContainer
                        inEvent:event] != nil) {
        //NSWarnLog(@"Redefining container %@ in event %@",
        //          newContainerName, event);
        return;
    }

    if (newContainerAlias == nil) {
        newContainerAlias      = newContainerName;
    }
    if (newContainerAlias != nil 
        && [self containerOfNumber:newContainerAlias 
                              type:typeOfNewContainer
                           inEvent:event] != nil) {
        //NSWarnLog(@"Redefining container alias %@ in event %@",
        //          newContainerAlias, event);
        return;
    }

    container = [self containerOfNumber:containerId
                                   type:[typeOfNewContainer containerType]
                                inEvent:event];
    if (container == nil) {
        [self error:@"Unknown container" inEvent:event];
    }
    
    newContainer = [SimulContainer containerWithType:typeOfNewContainer
                                                name:newContainerName
                                               alias:newContainerAlias
                                           container:container
                                        creationTime:[event time]
                                           simulator:self];
    [container addSubContainer:newContainer];
    [typeOfNewContainer addInstance:newContainer];

//    [userNumberToContainer setObject:newContainer forKey:newContainerName];
//    if (newContainerAlias != nil) {
        [userNumberToContainer setObject:newContainer forKey:newContainerAlias];
//    }
}

- (void)pajeDestroyContainer:(PajeEvent *)event
{
    NSString *containerId;
    NSString *containerTypeId;
    PajeContainerType *containerType;

    SimulContainer *container;

    // get fields from event
    containerId         = [event objectForKey:@"Name"];
    containerTypeId     = [event objectForKey:@"Type"];
    
    if (containerId == nil) {
        containerId     = [event objectForKey:@"Container"];
    }

    containerType = [userTypes objectForKey:containerTypeId];
    if (containerType == nil) {
        NSWarnLog(@"Unknown container type in event %@", event);
        //[self error:@"Unknown container type" inEvent:event];
    }
 
    container = [self containerOfNumber:containerId
                                   type:containerType
                                inEvent:event];
    if (container == nil) {
        [self error:@"Unknown container" inEvent:event];
        return;
    }

    [container stopWithEvent:event];
}



- (void)_getEntityType:(PajeEntityType **)entityType
                 value:(id *)entityValue
             container:(PajeContainer **)container
             fromEvent:(PajeEvent *)event
{
    NSString *entityTypeId;
    NSString *containerId;

    if (entityType != NULL) {
        entityTypeId  = [event objectForKey:@"EntityType"];
	if (entityTypeId == nil) {
            entityTypeId  = [event objectForKey:@"Type"];
	}
        *entityType = [userTypes objectForKey:entityTypeId];
        if (!*entityType) {
            [self error:@"Unknown entity type" inEvent:event];
        }
    }

    if (entityType != NULL && container != NULL) {
        containerId   = [event objectForKey:@"Container"];
        *container = [self containerOfNumber:containerId
                                        type:[*entityType containerType]
                                     inEvent:event];
        if (*container == nil) {
            [self error:@"Unknown container" inEvent:event];
        }
    }

    if (entityValue) {
        id value;
        value = [event objectForKey:@"Value"];
        if (entityType != NULL 
            && [*entityType isKindOfClass:[PajeCategorizedEntityType class]]) {
            PajeCategorizedEntityType *type = *entityType;
            value = [type unaliasedValue:value];
        }
        *entityValue = value;
    }
}

- (void)pajeNewEvent:(PajeEvent *)event
{
    PajeEventType *entityType;
    SimulContainer *container;
    id entityValue;

    [self _getEntityType:&entityType
                   value:&entityValue
               container:&container
               fromEvent:event];

    [container newEventWithType:entityType
                          value:entityValue
                      withEvent:event];
}

- (void)pajeSetState:(PajeEvent *)event
{
    PajeStateType *entityType;
    SimulContainer *container;
    id entityValue;

    [self _getEntityType:&entityType
                   value:&entityValue
               container:&container
               fromEvent:event];
    
    [container setUserStateOfType:entityType
                          toValue:entityValue
                        withEvent:event];
}

- (void)pajePushState:(PajeEvent *)event
{
    PajeStateType *entityType;
    SimulContainer *container;
    id entityValue;

    [self _getEntityType:&entityType
                   value:&entityValue
               container:&container
               fromEvent:event];

    [container pushUserStateOfType:entityType
                             value:entityValue
                         withEvent:event];
}

- (void)pajePopState:(PajeEvent *)event
{
    PajeStateType *entityType;
    SimulContainer *container;

    [self _getEntityType:&entityType
                   value:NULL
               container:&container
               fromEvent:event];

    [container popUserStateOfType:entityType
                        withEvent:event];
}

- (void)pajeSetVariable:(PajeEvent *)event
{
    PajeVariableType *entityType;
    SimulContainer *container;
    id entityValue;

    [self _getEntityType:&entityType
                   value:&entityValue
               container:&container
               fromEvent:event];

    [container setUserVariableOfType:entityType
                             toValue:entityValue
                           withEvent:event];
}
- (void)pajeAddVariable:(PajeEvent *)event
{
    PajeVariableType *entityType;
    SimulContainer *container;
    id entityValue;

    [self _getEntityType:&entityType
                   value:&entityValue
               container:&container
               fromEvent:event];

    [container addUserVariableOfType:entityType
                               value:entityValue
                           withEvent:event];
}
- (void)pajeSubVariable:(PajeEvent *)event
{
    PajeVariableType *entityType;
    SimulContainer *container;
    id entityValue;

    [self _getEntityType:&entityType
                   value:&entityValue
               container:&container
               fromEvent:event];

    [container subUserVariableOfType:entityType
                               value:entityValue
                           withEvent:event];
}


- (void)pajeStartLink:(PajeEvent *)event
{
    NSString *sourceContainerNumber;
    PajeLinkType *entityType;
    SimulContainer *container;
    PajeContainer *sourceContainer;
    id entityValue;

    [self _getEntityType:&entityType
                   value:&entityValue
               container:&container
               fromEvent:event];

    sourceContainerNumber = [event objectForKey:@"SourceContainer"];
    sourceContainer = [self containerOfNumber:sourceContainerNumber
                                         type:[entityType sourceContainerType]
                                      inEvent:event];
    if (sourceContainer == nil) {
        [self error:@"Unknown source container" inEvent:event];
        return;
    }

    [container startUserLinkOfType:entityType
                             value:entityValue
                   sourceContainer:sourceContainer
                               key:[event objectForKey:@"Key"]
                         withEvent:event];
}

- (void)pajeEndLink:(PajeEvent *)event
{
    NSString *destContainerNumber;
    PajeLinkType *entityType;
    SimulContainer *container;
    PajeContainer *destContainer;
    id entityValue;

    [self _getEntityType:&entityType
                   value:&entityValue
               container:&container
               fromEvent:event];

    destContainerNumber = [event objectForKey:@"DestContainer"];
    destContainer = [self containerOfNumber:destContainerNumber
                                       type:[entityType destContainerType]
                                    inEvent:event];
    if (destContainer == nil) {
        [self error:@"Unknown destination container" inEvent:event];
        return;
    }

    [container endUserLinkOfType:entityType
                           value:entityValue
                   destContainer:destContainer
                             key:[event objectForKey:@"Key"]
                       withEvent:event];
}

@end
