//
//  FiniteStateMachine.m
//  ArDroneGameLib
//
//  Created by Clément Choquereau on 5/4/11.
//  Copyright 2011 Parrot. All rights reserved.
//

#import "FiniteStateMachine.h"

// FSMXMLParsing needed to know the state of the XML Parser
#define FSM_XML_ROOT					@"fsm"
#define FSM_XML_STATES					@"states"
#define FSM_XML_STATE					@"state"
#define FSM_XML_STATE_NAME				@"name"
#define FSM_XML_STATE_ENTER_CALLBACK	@"enter-callback"
#define FSM_XML_STATE_QUIT_CALLBACK		@"quit-callback"
#define FSM_XML_STATE_OBJECT			@"object"
#define FSM_XML_ACTIONS					@"actions"
#define FSM_XML_ACTION					@"action"
#define FSM_XML_ACTION_NAME				@"name"
#define FSM_XML_ASSOCIATIONS			@"associations"
#define FSM_XML_ASSOCIATION				@"association"
#define FSM_XML_ASSOCIATION_FROM_STATE	@"from-state"
#define FSM_XML_ASSOCIATION_ACTION		@"action"
#define FSM_XML_ASSOCIATION_TO_STATE	@"to-state"

typedef enum
{
	FSM_XML_PARSING_ROOT,
	FSM_XML_PARSING_STATES,
	FSM_XML_PARSING_ACTIONS,
	FSM_XML_PARSING_ASSOCIATIONS,
	FSM_XML_PARSING_ASSOCIATION,
	FSM_XML_PARSING_ERROR
} FSM_XML_PARSING;

@interface FSMXMLParsing : NSObject
{
@public
	FSM_XML_PARSING state;
	
	NSMutableDictionary *states;
	NSMutableDictionary *actions;
}

@end

@implementation FSMXMLParsing

- (id)init
{
    self = [super init];
	if (self)
	{
		state = FSM_XML_PARSING_ROOT;
		
		states = [[NSMutableDictionary alloc] init];
		actions = [[NSMutableDictionary alloc] init];
	}
	
	return self;
}

- (void)dealloc
{
	[states removeAllObjects];
	[states release];
	
	[actions removeAllObjects];
	[actions release];
	
	[super dealloc];
}

@end

// FSM implementation:
@implementation FiniteStateMachine

@synthesize delegate;
@synthesize statesCount;
@synthesize actionsCount;

+ (id) fsm
{
    FiniteStateMachine *fsm = [[FiniteStateMachine alloc] init];
    
    return [fsm autorelease];
}

+ (id) fsmWithXML:(NSString *)fileName
{
    FiniteStateMachine *fsm = [[FiniteStateMachine alloc] init];
	
	fsm->xml = [[FSMXMLParsing alloc] init];
	
	NSData *data = [NSData dataWithContentsOfFile:fileName];
	NSXMLParser *xml = [[NSXMLParser alloc] initWithData:data];

	[xml setDelegate:fsm];
	if (![xml parse])
		NSLog(@"XML error: %@ (file: %@)", [xml parserError], fileName);
	else
		NSLog(@"XML parsing OK!");
	
	[xml release];
	
	[fsm->xml release];
	fsm->xml = nil;
    
    return [fsm autorelease];
}

- (id) init
{
    self = [super init];
    
    if (self)
    {
        statesCount = 0;
        actionsCount = 0;
        
        currentState = NO_STATE;
        
        delegate = nil;

        objects = [[NSMutableDictionary alloc] init];
        enterCallbacks = [[NSMutableDictionary alloc] init];
        quitCallbacks = [[NSMutableDictionary alloc] init];
        stateActions = [[NSMutableDictionary alloc] init];
		
		xml = nil;
    }
    
    return self;
}

- (void) dealloc
{
    [objects removeAllObjects];
    [objects release];
    objects = nil;
    
    [enterCallbacks removeAllObjects];
    [enterCallbacks release];
    enterCallbacks = nil;
    
    [quitCallbacks removeAllObjects];
    [quitCallbacks release];
    quitCallbacks = nil;
    
    [stateActions removeAllObjects];
    [stateActions release];
    stateActions = nil;
    
    [super dealloc];
}

// Current State getter
- (unsigned int)currentState
{
    return currentState;
}

// Current State setter
- (void) setCurrentState:(unsigned int)state
{
    if (state >= statesCount)
        return;
    
    NSString *key = [NSString stringWithFormat:@"%u", currentState];
    
    if ( (delegate) && (currentState != NO_STATE) && ([[quitCallbacks allKeys] containsObject:key]) )
    {
        SEL callback = NSSelectorFromString((NSString *)[quitCallbacks objectForKey:key]);
        [delegate performSelector:callback withObject:self];
    }
    
    currentState = state;
    
    key = [NSString stringWithFormat:@"%u", currentState];
	
    if ( (delegate) && ([[enterCallbacks allKeys] containsObject:key]) )
    {
        SEL callback = NSSelectorFromString((NSString *)[enterCallbacks objectForKey:key]);
        [delegate performSelector:callback withObject:self];
    }
}

// Current Object getter
- (id) currentObject
{
    NSString *key = [NSString stringWithFormat:@"%u", currentState];
    
    if ( ![[objects allKeys] containsObject:key] )
        return nil;
    
    return [objects objectForKey:key];
}

// Managing states:
- (unsigned int) createState
{
    statesCount++;
    
    return (statesCount - 1);
}

- (void) createStates:(unsigned int)count inArray:(unsigned int *)states
{
    for (unsigned int i = 0 ; i < count ; i++)
    {
        unsigned int state = [self createState];
        
        if (states)
            states[i] = state;
    }
}

- (void) setObject:(id)object forState:(unsigned int)state
{
	if ( (state >= statesCount) || (!object) )
        return;
    
    NSString *key = [NSString stringWithFormat:@"%u", state];
    [objects setObject:object forKey:key];
}

- (void) setEnterStateCallback:(SEL)enter forState:(unsigned int)state
{
    if ( (state >= statesCount) || (!enter) )
        return;
    
    NSString *key = [NSString stringWithFormat:@"%u", state];
    id object = NSStringFromSelector(enter);
    
    [enterCallbacks setObject:object forKey:key];
}

- (void) setQuitStateCallback:(SEL)quit forState:(unsigned int)state
{
	if ( (state >= statesCount) || (!quit) )
        return;
    
    NSString *key = [NSString stringWithFormat:@"%u", state];
    id object = NSStringFromSelector(quit);
    
    [quitCallbacks setObject:object forKey:key];
}

- (unsigned int) createStateWithObject:(id)object andEnterStateCallback:(SEL)enter andQuitStateCallback:(SEL)quit
{
	unsigned int state = [self createState];
	
	[self setObject:object forState:state];
	[self setEnterStateCallback:enter forState:state];
	[self setQuitStateCallback:quit forState:state];
	
	return state;
}

- (void) createStates:(unsigned int)count withObjects:(id *)items andEnterStateCallbacks:(SEL *)enters andQuitStateCallbacks:(SEL *)quits inArray:(unsigned int *)states
{
    for (unsigned int i = 0 ; i < count ; i++)
    {
		unsigned int state = [self createState];
		
		if (items)
			[self setObject:items[i] forState:state];
		
		if (enters)
			[self setEnterStateCallback:enters[i] forState:state];
		
		if (quits)
			[self setQuitStateCallback:quits[i] forState:state];
        
        if (states)
            states[i] = state;
    }
}

// Managing actions:
- (unsigned int) createAction
{
    actionsCount++;
    
    return (actionsCount - 1);
}

- (void)createActions:(unsigned int)count inArray:(unsigned int *)actions
{
    for (unsigned int i = 0 ; i < count ; i++)
    {
        unsigned int action = [self createAction];
        
        if (actions)
            actions[i] = action;
    }
}

// Associations:
- (void) createAssociationFromState:(unsigned int)start withAction:(unsigned int)action toState:(unsigned int)end
{
    if ( (action >= actionsCount) || (start >= statesCount) || (end >= statesCount) )
        return;
    
    NSString *key = NSStringFromRange(NSMakeRange(start, action));
    id object = [NSString stringWithFormat:@"%u", end];
    
    [stateActions setObject:object forKey:key];
}

- (void) createAssociationFromState:(unsigned int)start withActions:(unsigned int *)actions toStates:(unsigned int *)ends andNumAssociations:(unsigned int)count
{
	if ( (start >= statesCount) || (!actions) || (!ends) || (!count) )
		return;
	
	for (unsigned int i = 0 ; i < count ; i++)
		[self createAssociationFromState:start withAction:actions[i] toState:ends[i]];
}

- (void) doAction:(unsigned int)action
{
    if  ( (action >= actionsCount) || (currentState == NO_STATE) )
        return;
    
    NSString *key = NSStringFromRange(NSMakeRange(currentState, action));
    id object = [stateActions objectForKey:key];
    
    self.currentState = ((unsigned int)[(NSString *)object intValue]);
}

// NSXMLParserDelegate functions:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
	switch (xml->state)
	{
		case FSM_XML_PARSING_ROOT:
			if ([elementName compare:FSM_XML_ROOT] == NSOrderedSame)
			{
				xml->state = FSM_XML_PARSING_STATES;
				break;
			}
			
			NSLog(@"Missing root");
			xml->state = FSM_XML_PARSING_ERROR;
			
			break;
		case FSM_XML_PARSING_STATES:
			
			if ([elementName compare:FSM_XML_STATES] == NSOrderedSame)
			{
				break;
			}
			else if ([elementName compare:FSM_XML_STATE] == NSOrderedSame)
			{
				// Create state:
				NSString *key = nil;
				
				if ([[attributeDict allKeys] containsObject:FSM_XML_STATE_NAME])
					key = [attributeDict objectForKey:FSM_XML_STATE_NAME];
				
				if (!key)
				{
					NSLog(@"Error while parsing state: attribute '%@' missing", FSM_XML_STATE_NAME);
					xml->state = FSM_XML_PARSING_ERROR;
					break;
				}
				
				id object = nil;
				SEL enter = nil;
				SEL quit = nil;
				
				if ([[attributeDict allKeys] containsObject:FSM_XML_STATE_OBJECT])
					object = [attributeDict objectForKey:FSM_XML_STATE_OBJECT];
				
				if ([[attributeDict allKeys] containsObject:FSM_XML_STATE_ENTER_CALLBACK])
					enter = NSSelectorFromString([attributeDict objectForKey:FSM_XML_STATE_ENTER_CALLBACK]);
				
				if ([[attributeDict allKeys] containsObject:FSM_XML_STATE_QUIT_CALLBACK])
					quit = NSSelectorFromString([attributeDict objectForKey:FSM_XML_STATE_QUIT_CALLBACK]);
				
				unsigned int state = [self createStateWithObject:object andEnterStateCallback:enter andQuitStateCallback:quit];
				
				[xml->states setObject:[NSNumber numberWithUnsignedInt:state] forKey:key];
				
				break;
			}
			else if ([elementName compare:FSM_XML_ACTIONS] == NSOrderedSame)
			{
				xml->state = FSM_XML_PARSING_ACTIONS;
				break;
			}
			else if ([elementName compare:FSM_XML_ASSOCIATIONS] == NSOrderedSame)
			{
				xml->state = FSM_XML_PARSING_ASSOCIATIONS;
				break;
			}
			
			NSLog(@"Error while parsing states (found %@)", elementName);
			xml->state = FSM_XML_PARSING_ERROR;
			
			break;
		case FSM_XML_PARSING_ACTIONS:
			
			if ([elementName compare:FSM_XML_ACTIONS] == NSOrderedSame)
			{
				break;
			}
			else if ([elementName compare:FSM_XML_ACTION] == NSOrderedSame)
			{
				// Create action:
				NSString *key = nil;
				
				if ([[attributeDict allKeys] containsObject:FSM_XML_ACTION_NAME])
					key = [attributeDict objectForKey:FSM_XML_ACTION_NAME];
				
				if (!key)
				{
					NSLog(@"Error while parsing action: attribute '%@' missing", FSM_XML_ACTION_NAME);
					xml->state = FSM_XML_PARSING_ERROR;
					break;
				}
				
				unsigned int action = [self createAction];
				
				[xml->actions setObject:[NSNumber numberWithUnsignedInt:action] forKey:key];
				
				break;
			}
			else if ([elementName compare:FSM_XML_ASSOCIATIONS] == NSOrderedSame)
			{
				xml->state = FSM_XML_PARSING_ASSOCIATIONS;
				break;
			}
			
			NSLog(@"Error while parsing actions (found %@)", elementName);
			xml->state = FSM_XML_PARSING_ERROR;
			
			break;
		case FSM_XML_PARSING_ASSOCIATIONS:
			
			if ([elementName compare:FSM_XML_ASSOCIATIONS] == NSOrderedSame)
			{
				break;
			}
			else if ([elementName compare:FSM_XML_ASSOCIATION] == NSOrderedSame)
			{
				// Create association:
				NSString *fromState = nil;
				NSString *withAction = nil;
				NSString *toState = nil;
				
				if ([[attributeDict allKeys] containsObject:FSM_XML_ASSOCIATION_FROM_STATE])
					fromState = [attributeDict objectForKey:FSM_XML_ASSOCIATION_FROM_STATE];
				
				if ([[attributeDict allKeys] containsObject:FSM_XML_ASSOCIATION_ACTION])
					withAction = [attributeDict objectForKey:FSM_XML_ASSOCIATION_ACTION];
				
				if ([[attributeDict allKeys] containsObject:FSM_XML_ASSOCIATION_TO_STATE])
					toState = [attributeDict objectForKey:FSM_XML_ASSOCIATION_TO_STATE];
				
				if ( (!fromState) || (!withAction) || (!toState) )
				{
					NSLog(@"Error while parsing association: attributes '%@', '%@' or '%@' missing", FSM_XML_ASSOCIATION_FROM_STATE, FSM_XML_ASSOCIATION_ACTION, FSM_XML_ASSOCIATION_TO_STATE);
					xml->state = FSM_XML_PARSING_ERROR;
					break;
				}
				
				unsigned int from = [(NSNumber *)[xml->states objectForKey:fromState] unsignedIntValue];
				unsigned int action = [(NSNumber *)[xml->actions objectForKey:withAction] unsignedIntValue];
				unsigned int to = [(NSNumber *)[xml->states objectForKey:toState] unsignedIntValue];
				
				[self createAssociationFromState:from withAction:action toState:to];
				
				break;
			}
			
			NSLog(@"Error while parsing associations (found %@)", elementName);
			xml->state = FSM_XML_PARSING_ERROR;
			
			break;
		default:
			break;
	}
}

@end
