//
//  Actions.m
//  longspear
//
//  Created by Niko Matsakis on Sat Jun 12 2004.
//  Copyright (c) 2004 __MyCompanyName__. All rights reserved.
//

#import "Actions.h"
#import "Config.h"

@implementation Action

+ (id) _setFieldNamed:(NSString*)name to:(NSString*)value on:entity target:target
{
    /* I know these could (and should) be subclasses, and I've obviously retained that possibility
    in the interface, but it just took so many fewer lines to make them a union that I went with
    that for the impl */
    Action *action = [[[Action alloc] init] autorelease];
    action->_target = target;   // don't retain to avoid causing cycles
    action->_data.setField.name = [name retain];
    action->_data.setField.value = [value retain];
    action->_data.setField.entity = [entity retain];
    action->_type = SETFIELD;
    return action;
}

+ (void) setFieldNamed:(NSString*)name to:(NSString*)value on:entity target:target
{
    [[Action _setFieldNamed:name to:value on:entity target:target] commit];
}

+ (void) add:(int)amount toFieldNamed:(NSString*)name on:entity target:target
{
    int value = [entity fieldAsInt:name]+amount;
    [Action setFieldNamed:name
                       to:[NSString stringWithFormat:@"%d", value]
                       on:entity
                   target:target];
}

+ _removeObject:object from:(NSMutableArray*)array target:target
{
    Action *action = [[[Action alloc] init] autorelease];
    action->_target = target;   // don't retain to avoid causing cycles
    action->_data.removeObject.object = [object retain];
    action->_data.removeObject.array = [array retain];
    action->_type = REMOVEOBJ;
    return action;
}

+ (void) removeObject:object from:(NSMutableArray*)array target:target
{
    [[Action _removeObject:object from:array target:target] commit];
}

+ _addObject:object to:(NSMutableArray*)array atIndex:(int)index target:target
{
    Action *action = [[[Action alloc] init] autorelease];
    action->_target = target;   // don't retain to avoid causing cycles
    assert (index >= 0);
    action->_data.addObject.object = [object retain];
    action->_data.addObject.index = index;
    action->_data.addObject.array = [array retain];
    action->_type = ADDOBJ;
    return action;
}

+ (void) addObject:object to:(NSMutableArray*)array atIndex:(int)index target:target
{
    if (index == -1) index = [array count];
    [[Action _addObject:object to:array atIndex:index target:target] commit];
}

+ _setInitiativeIndexOf:(Combat*)combat to:(int)newval nextTurn:(BOOL)turn target:target
{
    Action *action = [[[Action alloc] init] autorelease];
    action->_target = target;   // don't retain to avoid causing cycles
    action->_data.setInitiative.combat = [combat retain];
    action->_data.setInitiative.value = newval;
    action->_data.setInitiative.nextTurn = turn;
    action->_type = SETINIT;
    return action;
}

+ (void) setInitiativeIndexOf:(Combat*)combat to:(int)newval nextTurn:(BOOL)turn target:target
{
    [[Action _setInitiativeIndexOf:combat to:newval nextTurn:turn target:target] commit];
}

+ (void) addCombatant:object toCombat:(Combat*)combat target:target // inserts sorted
{
    NSMutableArray *entities = [combat entities];
    
    // First find the index where it belongs.
    int objinitiative = [object fieldAsInt:kINITSCORE], i;
    for (i = [entities count]-1; i >= 0; i--) {
        Entity *otherguy = [entities objectAtIndex:i];
        if ([otherguy fieldAsInt:kINITSCORE] > objinitiative) break;
    }
    i++;
    
    int initindex = [combat initiativeIndex];
    
    [Action addObject:object to:entities atIndex:i target:target];
    
    // Adjust initiative index as needed to ensure that it
    // remains selecting the same character
    if (i <= initindex) {
        [Action setInitiativeIndexOf:combat
                                  to:initindex+1
                            nextTurn:NO
                              target:target];
    }
}

+ (Action*) _updateMonsterDatabase:(NSArray*)db target:target
{
    Action *action = [[[Action alloc] init] autorelease];
    action->_target = target;   // don't retain to avoid causing cycles
    action->_data.monster.db = [db retain];
    action->_type = UPDATEMONSTERS;
    return action;
} 

+ (void) addMonsterToDatabase:(Entity*)monster target:target
{
    
    // Find the right place to put the monster so that they
    // stay in alphabetical order.  Remove any monsters
    // with the same name. 
    NSMutableArray *db = [[[NSMutableArray alloc] initWithArray:MonsterDatabase()] autorelease];
    int i, cnt = [db count];
    NSString *newname = [monster fieldAsString:kNAME];
    for (i = 0; i < cnt; i++) {
        // Stop at the first monster whose name compares as "greater"
        // than this name.
        Entity *dbmon = [db objectAtIndex:i];
        NSString *dbnm = [dbmon fieldAsString:kNAME];
        NSComparisonResult res = [newname caseInsensitiveCompare:dbnm];
        if (res == NSOrderedAscending)
            break;
        if (res == NSOrderedSame) {
            // if we find one with the same name remove it, then stop because
            // this is the right spot:
            [db removeObjectAtIndex:i];
            cnt--;
            break;
        }
    }
    
    // If the name is greater than all names in the database, we'll fall off the end
    // here with i == cnt.  That's okay.
    
    [db insertObject:monster atIndex:i];
    
    [[Action _updateMonsterDatabase:db target:target] commit];
}

+ (void) removeMonsterFromDatabase:(int)index target:target
{
    NSMutableArray *db = [[[NSMutableArray alloc] initWithArray:MonsterDatabase()] autorelease];
    [db removeObjectAtIndex:index];
    [[Action _updateMonsterDatabase:db target:target] commit];
}

- (void) commit
{
    id undoaction = nil;
    id notifyobject = nil;
    NSString *notifystr = kCHANGED;
    
    switch (_type) {
        case SETFIELD: {
            id oldvalue = [[[_data.setField.entity fieldAsString:_data.setField.name] retain] autorelease];
            if (![oldvalue isEqualTo:_data.setField.value]) {
                [_data.setField.entity setFieldNamed:_data.setField.name
                                                  to:_data.setField.value];
                undoaction = [Action _setFieldNamed:_data.setField.name 
                                                 to:oldvalue
                                                 on:_data.setField.entity
                                             target:_target];
                notifyobject = [_data.setField.entity notifyObject];
            }
            break;
        }
            
        case REMOVEOBJ: {
            int objidx = [_data.removeObject.array indexOfObject:_data.removeObject.object];
            if (objidx != NSNotFound) {
                undoaction = [Action _addObject:_data.removeObject.object
                                             to:_data.removeObject.array
                                        atIndex:objidx
                                         target:_target];
                [_data.removeObject.array removeObjectAtIndex:objidx];
            }
            notifyobject = _data.removeObject.array;
            
            break;
        }
            
        case ADDOBJ: {
            undoaction = [Action _removeObject:_data.addObject.object
                                          from:_data.addObject.array
                                        target:_target];
            
            assert (_data.addObject.index >= 0);
            [_data.addObject.array insertObject:_data.addObject.object
                                        atIndex:_data.addObject.index];
            
            notifyobject = _data.addObject.array;
            break;
        }
            
        case SETINIT: {
            // TODO --- ensure undo returns same rolled dice
            int curinit = [_data.setInitiative.combat initiativeIndex];
            undoaction = [Action _setInitiativeIndexOf:_data.setInitiative.combat
                                                    to:curinit
                                              nextTurn:_data.setInitiative.nextTurn
                                                target:_target];
            [_data.setInitiative.combat setInitiativeIndex:_data.setInitiative.value];
            notifyobject = _data.setInitiative.combat;
            if (_data.setInitiative.nextTurn) {
                // post an additional notification...
                [NotificationCenter() postNotificationName:kNEXTTURN object:notifyobject];
            }
            break;
        }
        
        case UPDATEMONSTERS: {
            NSArray *olddb = MonsterDatabase ();
            undoaction = [Action _updateMonsterDatabase:olddb target:_target];
            UpdateMonsterDatabase (_data.monster.db);
            notifyobject = kCONFIGCHANGED;   // for want of a better object
            notifystr = kCONFIGCHANGED;
            break;
        }
    }
    
    /* Send out a notification to let people know we changed something */
    if (notifyobject) {
        [NotificationCenter() postNotificationName:notifystr object:notifyobject];
    }
    
    if (undoaction) {
        NSLog (@"Adding Undo Action For: %@", self);
        NSUndoManager *undom = [_target undoManager];
        
        [undom registerUndoWithTarget:_target
                             selector:@selector(undoAction:)
                               object:undoaction];
    }
    
    NSLog (@"Committed action: %@, undo action: %@", self, undoaction);
}

- (NSString*) description
{
    switch (_type) {
        case SETFIELD: {
            return [NSString stringWithFormat:@"Action: set field '%@' of '%@' to '%@'",
                _data.setField.name, _data.setField.entity, _data.setField.value];
        }
            
        case REMOVEOBJ: {
            return [NSString stringWithFormat:@"Action: remove object '%@' from '%@'",
                _data.removeObject.object, _data.removeObject.array];
        }
            
        case ADDOBJ: {
            return [NSString stringWithFormat:@"Action: add object '%@' to '%@' at index %d",
                _data.addObject.object, _data.addObject.array, _data.addObject.index];
        }
            
        case SETINIT: {
            return [NSString stringWithFormat:@"Action: set initiative to '%d' of %@",
                _data.setInitiative.value, _data.setInitiative.combat];
        }
        
        case UPDATEMONSTERS: {
            return [NSString stringWithFormat:@"Action: update monster database"];
        }
    }
    return @"invalid action";
}

- (void) dealloc
{
    switch (_type) {
        case SETFIELD: {
            [_data.setField.entity release];
            [_data.setField.name release];
            [_data.setField.value release];
            break;
        }
            
        case REMOVEOBJ: {
            [_data.removeObject.array release];
            [_data.removeObject.object release];
            break;
        }
            
        case ADDOBJ: {
            [_data.addObject.array release];
            [_data.addObject.object release];
            break;
        }
            
        case SETINIT: {
            [_data.setInitiative.combat release];
            break;
        }
        
        case UPDATEMONSTERS: {
            [_data.monster.db release];
            break;
        }
    }
    
    [super dealloc];
}


@end
