#import "CombatController.h"
#import "Config.h"
#import "Actions.h"
#import "NSArrayExtensions.h"
#import "NSStringExtensions.h"
#import "NSFormExtensions.h"
#import "dice.h"
#import "FormPrompter.h"

// Tabs of the rolls drawer:
#define SAVTHR 0
#define ATTDMG 1

@implementation CombatController

- initWithCombat:(Combat*)combat
{
    if ((self = [super initWithWindowNibName:@"Combat"])) {
        _combat = [combat retain];
    }
    return self;
}

- (void) dealloc
{
    [NotificationCenter() removeObserver:self];
    [_combat release];
    [_charactersManager release];
    [_combatantsManager release];
    [_aftermathManager release];
    [_monsterDatabaseManager release];
    [_selectedEffectsManager release];
    [_selectedFieldsManager release];
    [super dealloc];
}

- (NSString*) description
{
    return [NSString stringWithFormat:@"CombatController for combat %@", [_combat description]];
}

- (Combat*) combat
{
    return _combat;
}

- (void) listen
{
    // listen for when the list of characters in this campaign changes
    // so we can update the initiative choice display
    [NotificationCenter() addObserver:self
                             selector:@selector(configurationChanged:)
                                 name:kCONFIGCHANGED
                               object:nil];

    [NotificationCenter() addObserver:self
                             selector:@selector(initiativeChanged:)
                                 name:kNEXTTURN
                               object:nil];
    
    // listen for when the selection changes on the combat table so 
    // that we can update the current character display
    [NotificationCenter() addObserver:self
                             selector:@selector(selectedCombatantsChanged:)
                                 name:NSTableViewSelectionDidChangeNotification
                               object:_combatants];
                               
    // listen for when the list of characters in this campaign changes
    // so we can update the initiative choice display
    [NotificationCenter() addObserver:self
                             selector:@selector(campaignCharactersChanged:)
                                 name:kCHANGED
                               object:[[_combat campaign] characters]];
    
    // listen for when the combat itself changes since that indicates an
    // initiative change
    [NotificationCenter() addObserver:self
                             selector:@selector(combatChanged:)
                                 name:kCHANGED
                               object:_combat];
    [NotificationCenter() addObserver:self
                             selector:@selector(combatChanged:)
                                 name:kCHANGED
                               object:[_combat entities]];
    
}

- (void) windowDidLoad
{
    NSLog (@"Window is loaded: %d", [self isWindowLoaded]);
    [self showWindow:self];
    [[self window] makeKeyAndOrderFront:self];
}

- (void) awakeFromNib
{
    NSLog (@"CombatController awakeFromNib %p", self);

    /* Create managers for each table.  Note that the "current character effects" manager's array
    will change as initiative changes, but that's no big deal */
    _charactersManager = [[FieldTableManager alloc] initWithColumns:kCOMBATCHARSET
                                                               data:[_combat combatCharacters]
                                                        NSTableView:_characters
                                                           readOnly:NO
                                                             notify:_combat
                                                           delegate:self];
    _combatantsManager = [[FieldTableManager alloc] initWithColumns:kCOMBATSET 
                                                               data:[_combat entities]
                                                        NSTableView:_combatants
                                                           readOnly:NO
                                                             notify:_combat
                                                           delegate:self];
    _aftermathManager = [[FieldTableManager alloc] initWithColumns:kAFTERMATHSET 
                                                              data:[_combat aftermath]
                                                       NSTableView:_aftermath
                                                          readOnly:NO
                                                            notify:_combat
                                                          delegate:self];
    _monsterDatabaseManager = [[FieldTableManager alloc] initWithColumns:kMONSTERDBSET 
                                                                    data:MonsterDatabase()
                                                             NSTableView:_monsterDatabase
                                                                readOnly:YES
                                                                  notify:nil
                                                                delegate:self];
    _selectedEffectsManager = [[FieldTableManager alloc] initWithColumns:kEFFECTSET 
                                                                    data:[_combat targetedBy]
                                                             NSTableView:_selectedEffects
                                                                readOnly:NO
                                                                  notify:_combat
                                                                delegate:self];

    _selectedFieldsManager = [[EntityFieldListingManager alloc] initWithView:_selectedFields
                                                                    delegate:self];
    
    [self listen];
    [self campaignCharactersChanged:nil];
    [self combatChanged:nil];
    [self selectedCombatantsChanged:nil];
    
    [_monsterDatabase setTarget:self];
    [_monsterDatabase setDoubleAction:@selector(monsterSelected:)];
}

- (IBAction)addCharacters:(id)sender
{
    NSEnumerator *combchars = [[_combat combatCharacters] objectEnumerator];
    NSMutableArray *entities = [_combat entities];
    Entity *combchar;
    while ((combchar = [combchars nextObject])) {
        NSString *initscore = [combchar fieldAsString:kINITSCORE];
        NSLog (@"combchar %@ init score %@", combchar, initscore);
        if ([initscore length]) {
            if (![entities containsObject:combchar]) {
                NSLog (@"combchar %@ adding to combat entities %@", combchar, entities);
                [Action addCombatant:combchar
                            toCombat:_combat
                              target:self];
            }
        }
    }
}

- (IBAction)addMonsters:(id)sender
{
    NSMutableDictionary *fields = [_monsterFields valuesAsDictionary];
    
    // determine the tags to display (if any)
    NSString *tags = [fields objectForKey:@"Tags"];
    int tagstart=-1, tagstop=-1;
    if ([tags length] > 0) {
        NSArray *tagsarray = [tags componentsSeparatedByString:@"-"];
        if ([tagsarray count] == 2) {
            NSString *tag1 = [tagsarray objectAtIndex:0];
            NSString *tag2 = [tagsarray objectAtIndex:1];
            tagstart = [tag1 intValue];
            tagstop = [tag2 intValue];
        }
        else {
            int tagval = [tags intValue];
            if (tagval) tagstart = tagstop = tagval;
        }
    }
    NSLog (@"addMonsters: tags=%@ tagstart=%d tagstop=%d", tags, tagstart, tagstop);
    [fields removeObjectForKey:@"Tags"];

    // grab the initiative bonus, but ensure it's within reasonable bounds
    int initbonus = [[fields objectForKey:kINITIATIVE] intValue];
    if (initbonus < 0) initbonus = 0;
    if (initbonus > 1000) initbonus = 0;

    NSString *basename = [[[fields objectForKey:kNAME] retain] autorelease];
    NSLog (@"addMonsters: basename='%@' initbonus=%d", basename, initbonus);
    
    while (tagstart <= tagstop) {
        int tag = tagstart++;
        
        /* set custom name */
        if (tag >= 0) {
            [fields setObject:[NSString stringWithFormat:@"%@ #%d", basename, tag]
                       forKey:kNAME];
        }
        
        Entity *monster = [[[Entity alloc] initWithBase:nil notifyObject:_combat] autorelease];
        [monster setFieldsFrom:fields interpret:YES];
        [Action addCombatant:monster
                    toCombat:_combat
                      target:self];
    }
}

- (IBAction)addMonsterTemplate:(id)sender
{
    NSMutableDictionary *fields = [_monsterFields valuesAsDictionary];
    [fields removeObjectForKey:@"Tags"]; // this need not go into the database...
    Entity *monster = [[[Entity alloc] initWithBase:nil notifyObject:nil] autorelease];
    [monster setFieldsFrom:fields interpret:NO];
    [Action addMonsterToDatabase:monster target:self];
}

- (IBAction)makeCurrent:(id)sender
{
    int nextidx = [_combatants selectedRow];
    if (nextidx >= 0) {
        [Action setInitiativeIndexOf:_combat
                                  to:nextidx
                            nextTurn:YES
                              target:self];
    }
}

- (IBAction)nextInitiative:(id)sender
{
    int nextidx = [_combat initiativeIndex]+1;
    if (nextidx >= [[_combat entities] count]) nextidx = 0;
    [Action setInitiativeIndexOf:_combat
                              to:nextidx
                        nextTurn:YES
                          target:self];
}

- (void) applySelector:(SEL)sel toSelected:(NSTableView*)view userdata:userdata
{
    NSIndexSet *indexset = [view selectedRowIndexes];
    NSArray *selents = [[_combat entities] objectsAtIndices:indexset];
    NSEnumerator *selected = [selents objectEnumerator];
    Entity *entity;
    
    while ((entity = [selected nextObject])) {
        [self performSelector:sel withObject:entity withObject:userdata];
    }
}


- (IBAction)addEffect:(id)sender
{
    NSLog (@"addEffect\n");
    [FormPrompter promptForItems:[NSArray arrayWithObjects:@"Name", @"Rounds", @"Comments", nil]
                          target:self
                        selector:@selector(addEffectWithFields:)
                          window:[self window]];
}

- (void) addEffectWithFields:(NSDictionary*)fields
{
    NSLog (@"addEffectWithFields: %@\n", fields);
    
    // Add effect right before the current initiative
    Entity *effect = [[[Entity alloc] initWithBase:nil notifyObject:_combat] autorelease];
    [effect setFieldNamed:kTYPE to:kEFFECT];
    [effect setFieldsFrom:fields interpret:YES];
    
    // Add the selected people as targeted by this effect.
    // This affects their "Affected By" field.  Note that we don't
    // bother making an Action for this "setTargetList" call 
    // because when you undo this step, you will remove this entity
    // from the list in its entirety so it won't matter if we undo
    // this individual setTargetList call.
    NSArray *selected = [_combatantsManager selectedItems];
    [effect setTargetList:selected actionTarget:self];

    // Add the effect right before the current initiative
    [Action addObject:effect
                   to:[_combat entities]
              atIndex:[_combat initiativeIndex]
               target:self];
               
    // And adjust the initiative so that the current character
    // remains selected, not the effect (note that this does not count
    // as a new turn)
    [Action setInitiativeIndexOf:_combat
                              to:[_combat initiativeIndex]+1
                        nextTurn:NO
                          target:self];
}

- (IBAction)retire:(id)sender
{
    [FormPrompter promptForItems:[NSArray arrayWithObject:@"Fate"]
                          target:self
                        selector:@selector(retireWithFate:)
                          window:[self window]];
}

- (void) retireWithFate:(NSDictionary*)res
{
    NSString *fate = [res objectForKey:@"Fate"];
    [self applySelector:@selector(retireCombatant:userdata:)
             toSelected:_combatants
               userdata:fate];
}

- (void) retireCombatant:(Entity*)entity userdata:fate
{
    [[entity retain] autorelease];
    
    int index = [[_combat entities] indexOfObject:entity];
    int initindex = [_combat initiativeIndex];
    
    [Action setFieldNamed:kFATE
                       to:fate
                       on:entity
                       target:self];                       
    [Action removeObject:entity
                    from:[_combat entities]
                  target:self];
    [Action addObject:entity
                   to:[_combat aftermath]
              atIndex:-1
               target:self];

    if (index < initindex) {
        // if this is not the character w/ current initiative, that 
        // character will be shifted up one, so adjust initiative index
        [Action setInitiativeIndexOf:_combat
                                  to:initindex-1 
                            nextTurn:NO 
                              target:self];
    }
    else if (index == initindex) {
        if (index < [[_combat entities] count]) {
            // if we removed the guy who is currently
            // selected, select the guy after him (if any)
            [Action setInitiativeIndexOf:_combat
                                      to:initindex
                                nextTurn:YES
                                  target:self];
        }
        else {
            // if they just removed the last guy in the list, reset to
            // position zero unless the list is empty, of course
            [Action setInitiativeIndexOf:_combat
                                      to:([[_combat entities] count] ? 0 : -1)
                                nextTurn:YES
                                  target:self];
        }
    }
    
}

- (IBAction)takeDamage:(id)sender
{
    [FormPrompter promptForItems:[NSArray arrayWithObjects:@"Damage", nil]
                          target:self
                        selector:@selector(takeDamage2:)
                          window:[self window]];
}

- (void) takeDamage2:(NSDictionary*)dmgdic
{
    NSString *damagestr = [dmgdic objectForKey:@"Damage"];
    NSLog (@"Damage: %@", damagestr);
    
    [self applySelector:@selector(takeDamage3:amount:)
             toSelected:_combatants
               userdata:damagestr];
}

- (void) takeDamage3:(Entity*)entity amount:(NSString*)amount
{
    int damageval = [amount rolledValue];
    NSLog (@"Damage: %@=%d to %@", amount, damageval, entity);
    [Action add:-damageval
   toFieldNamed:kHITPOINTS 
             on:entity
         target:self];
}

- (void) monsterSelected:(id)sender
{
    Entity *monstertmpl = [_monsterDatabaseManager selectedItem];
    [_monsterFields setValuesFromDictionary:[monstertmpl fields]];
}

- (IBAction)filterMonsters:(id)sender
{
    NSString *strval = [_monsterSearch stringValue];
    NSArray *words = [strval componentsSeparatedByString:@" "];
    NSEnumerator *monsters = [MonsterDatabase() objectEnumerator];
    NSMutableArray *array = [[[NSMutableArray alloc] initWithCapacity:128] autorelease];
    Entity *monster;
    
    while ((monster = [monsters nextObject])) {
        BOOL failed=NO;
        NSString *name = [monster fieldAsString:kNAME];
        NSEnumerator *wordsenum = [words objectEnumerator];
        NSString *word;
        
        while ((word = [wordsenum nextObject])) {
            if ([word length] && ![name containsCIString:word]) {
                failed = YES;
                break;
            }
        }
        
        if (!failed) {
            [array addObject:monster];
        }
    }
    
    [_monsterDatabaseManager setEntityList:array];
}

- (void) showRollsDrawerWithTab:(int)tab
{
    [_rollDrawer open];
    [_rollTab selectTabViewItemAtIndex:tab];
}

- (IBAction) rollAttacks:(id)sender
{
    Entity *cur = [_combatantsManager selectedItem];
    NSArray *repeatpattern;
    NSTextStorage *text;
    
    text = [_attackRolls textStorage];
    [text setAttributedString:
        [[cur fieldAsString:kATTACKS] interpretDiceValuesWithRepeatPattern:nil
                                                           savingPatternTo:&repeatpattern]];
    text = [_damageRolls textStorage];
    [text setAttributedString:
        [[cur fieldAsString:kDAMAGE] interpretDiceValuesWithRepeatPattern:repeatpattern
                                                          savingPatternTo:nil]];
                                                          
    [self showRollsDrawerWithTab:ATTDMG];
}    

- (IBAction) rollSaves:(id)sender
{
    Entity *cur = [_combatantsManager selectedItem];
    NSTextStorage *text;
    
    text = [_savingThrows textStorage];
    [text setAttributedString:
        [[cur fieldAsString:kSAVES] interpretDiceValuesWithRepeatPattern:nil
                                                         savingPatternTo:nil]];
    
    [self showRollsDrawerWithTab:SAVTHR];
}    

/* This routine gets triggered when the combat object
has changed.  This means that either (a) a field has changed
or (b) the initiative has changed or (c) we've added some new
attendants */

- (void) combatChanged:(id)sender
{
    /* */
    
    /* even if the selected table rows didn't change, the
    monsters in those rows may have */
    [self selectedCombatantsChanged:nil];
}

- (void) initiativeChanged:(id)sender
{
    Entity *cur = [_combat currentEntity];
    
    [_rollDrawer close];
    
    if (!cur) 
        return;
        
    /* change the selection so that we are selecting the person whose
        turn it is */
    [_combatants selectRow:[_combat initiativeIndex] byExtendingSelection:NO];

    /* decrement remaining rounds for EFFECTs; we could also decrement
        negative hit points... but that would be confusing if they are 
        stabilized, I'll leave that to the user for now */
    NSString *type = [cur fieldAsString:kTYPE];
    if ([type isEqualToString:kEFFECT]) {
        // Decrement the number of rounds
        [Action add:-1 toFieldNamed:kROUNDS on:cur target:self];
        
        // Check to see if we just hit 0; if so purge this effect from the list.
        int roundsremain = [cur fieldAsInt:kROUNDS];
        if (roundsremain <= 0) {
            // adjust the "Affected By" fields of our targets
            [cur removeTargetListActionTarget:self];
            
            // remove the effect from the list
            [Action removeObject:cur
                            from:[_combat entities]
                          target:self];
                          
            // and change the initiative index so as to trigger 
            // any required refreshes
            [Action setInitiativeIndexOf:_combat
                                      to:[_combat initiativeIndex]
                                nextTurn:YES
                                  target:self];
        }
    }    
    else if (Flag (kAUTOROLLATTACKS)) {
        [self rollAttacks:self];
    }
}
    
- (void) selectedCombatantsChanged:(id)sender
{    
    /* triggered when the selection changes on the combatants table */
    Entity *cur = [_combatantsManager selectedItem];
    if (cur) {
        /* derive the information */
        [_selectedName setStringValue:[cur fieldAsString:kNAME]];
        [_selectedEffectsManager setEntityList:[_combat whoTargets:cur]];
        [_selectedFieldsManager setEntity:cur];
    }
    else {
        /* clear out the information */
        [_selectedName setStringValue:@"No character selected"];
        [_selectedEffectsManager setEntityList:[NSArray arrayWithObjects:nil]];
        [_selectedFieldsManager setEntity:nil];
    }
}

/* Delegation duties */

- (void) fieldTableManager:(FieldTableManager*)manager draggedObjects:(NSArray*)objects toIndex:(int)row
{
    // we don't usually care if a drag occurred
    if (manager != _combatantsManager)
        return;
    
    // but we do care if it is the list of combatatns
    // that were dragged b/c we must adjust their initiative
    // index.  We'll adjust it to be equal to the initiative of the
    // object just above them in the array listing, if any.
    NSArray *entities = [_combat entities];
    id initscore;
    if (row == 0) {
        // they were dragged to be first... pick the item just below them, if any
        row += [objects count];
        if (row < [entities count])
            initscore = [[entities objectAtIndex:row] fieldAsString:kINITSCORE];
        else
            initscore = [[objects objectAtIndex:row] fieldAsString:kINITSCORE];
    }
    else 
        initscore = [[entities objectAtIndex:row-1] fieldAsString:kINITSCORE];
        
    // now adjust the initiatives on the dudes we dragged
    int i, cnt = [objects count];
    for (i = 0; i < cnt; i++) {
        [Action setFieldNamed:kINITSCORE
                           to:initscore
                           on:[objects objectAtIndex:i]
                       target:self];
    }
    
    // finally make the first one that we dragged the current initiative
    [Action setInitiativeIndexOf:_combat
                              to:row
                        nextTurn:YES
                          target:self];
}

- (NSAttributedString*)fieldTableManager:(FieldTableManager*)manager
                        attributizeValue:(NSString*)value
                                  forRow:(int)row
                                   field:(NSString*)field
                                    font:(NSFont*)origfont
                                  object:(id)entity
{
    // For everything but the main combat table, always use the default string
    if (manager != _combatantsManager)
        return [[[NSAttributedString alloc] initWithString:value] autorelease];

    NSColor *color = [NSColor blackColor];
    NSFont *font = origfont;
    NSFontManager *fontmanager = [NSFontManager sharedFontManager];
    
    // Is this the current creature?  Use bold.
    int initindex = [_combat initiativeIndex];
    if (row == initindex)
        font = [fontmanager convertFont:font toHaveTrait:NSBoldFontMask];
        
    // Is this a spell?  Display in italics.
    if ([[entity fieldAsString:kTYPE] isEqualToString:kEFFECT])
        font = [fontmanager convertFont:font toHaveTrait:NSItalicFontMask];
        
    // Is this creature affected by a spell that is the current
    // creature?
    if ([[[_combat currentEntity] targets] containsObject:entity])
        color = [NSColor redColor];
        
    // Has this creature already acted?  Use gray.
    else if (row < initindex) 
        color = [NSColor grayColor];
        
    // Compose final string.
    NSDictionary *atts = [NSDictionary dictionaryWithObjectsAndKeys:
        color, NSForegroundColorAttributeName, font, NSFontAttributeName, nil];
        
    return [[[NSAttributedString alloc] initWithString:value attributes:atts] autorelease];
}

/* Notification Receivers */

/* Called when the list of characters in the campaign
object changes.  Allows the combat to update its list of
wrappers.  We need to be involved so we can pass our self
pointer to the combat object for undo purposes. */
- (void) campaignCharactersChanged:object
{
    [_combat reconcileCombatCharacters:self];
}

- (void) configurationChanged:object
{
    // in case the monster database changed, refilter it according
    // to their search critera, this will also update the list in
    // the manager
    [self filterMonsters:_monsterSearch];
}

/* Undo handling */

- (void) undoAction:(Action*)a
{
    [a commit];
}

- (NSUndoManager*) undoManager
{
    return [[self window] undoManager];
}

@end
