/* indent:4  tabsize:8  font:fixed-width */

#import "Solitaire.h"
#import "ModuleList.h"
#import "localstrings.h"
#import "GameModule.h"

/* NOTE:  This class is split into categories; see also SolitaireMore.m */

static id _SolEngine;

id SolEngine(void)
/* 
 * called by game modules to get access to game engine 
 */
{
    return _SolEngine;
}

id SolGameController(void)
/*
 * shortcut to current game controller
 */
{
    return [_SolEngine gameController];
}


static void initMenu(Menu *menu)
/*
 * Sets the updateAction for every menu item which sends to the
 * First Responder (i.e. their target is nil).  When autoupdate is on,
 * every event will be followed by an update of each of the menu items
 * which is visible.  This keep all unavailable menu items dimmed out
 * so that the user knows what options are available at any given time.
 * Returns the activate menu if is found in this menu.
 */ 
{
    int count;
    Matrix *matrix;
    MenuCell *cell;
    id matrixTarget, cellTarget;

    matrix = [menu itemList];
    matrixTarget = [matrix target];

    count = [matrix cellCount];
    while (count--) {
	cell = [matrix cellAt:count :0];
	cellTarget = [cell target];
	if (!matrixTarget && !cellTarget) {
	    [cell setUpdateAction:@selector(menuItemUpdate:) forMenu:menu];
	} else if ([cell hasSubmenu]) {
	    initMenu(cellTarget);
	}
    }
}

@implementation Solitaire

/*----------------------------------------------------------------------------
|
|    - appDidInit:sender
|
|    returns:  (id) self
|
|-----------------------------------------------------------------------------
|
|    Application delegate message.
|			
\----------------------------------------------------------------------------*/

- appDidInit:sender
{
    const char *autoLaunch;

    _SolEngine = self;
    
    // want to autoupdate menu items
    [NXApp setAutoupdate:YES];
    initMenu([NXApp mainMenu]);

    // get set to install the inspector
    [commonInspector getFrame:&inspectorFrame];
    currentInspector = nil;

    // make arrow keys work in game selection browser
    [gameSelectionBrowser acceptArrowKeys:YES andSendActionMessages:YES];
    [settingsPanel makeFirstResponder:gameSelectionBrowser];
    
    // go find game modules, then select and load the default
    [self getGameType];
    [self setGameIndex];

    [settingsPanel setFrameAutosaveName:"SettingsPanel"];

    // hey, *somebody* might want to autolaunch Solitaire
    autoLaunch = NXGetDefaultValue([NXApp appName], "NXAutoLaunch");
    if (strcmp(autoLaunch,"YES"))
    {
        [settingsPanel makeKeyAndOrderFront:self];
        windowHasBeenDisplayed = YES;
    }
    else 
        [NXApp hide:self];
    
    srandom(time(0));

    return self;
}

/*----------------------------------------------------------------------------
|
|    - appDidUnhide:sender
|
|    returns:  (id) self
|
|-----------------------------------------------------------------------------
|
|    Application delegate message.  Display settings panel if program is
|    being unhidden for the first time (normally after an autolaunch).
|			
\----------------------------------------------------------------------------*/

- appDidUnhide:sender
{
    if (!windowHasBeenDisplayed)
    {
        [settingsPanel makeKeyAndOrderFront:self];
        windowHasBeenDisplayed = YES;
    }
    return self;
}


/*----------------------------------------------------------------------------
|
|    - (BOOL)appAcceptsAnotherFile:sender
|
|    returns:  (BOOL)  YES if more games can be loaded
|                      NO if not
|
|-----------------------------------------------------------------------------
|
|    Application delegate message.
|			
\----------------------------------------------------------------------------*/

- (BOOL)appAcceptsAnotherFile:sender
{
    _SolEngine = self;

    return YES;
}


/*----------------------------------------------------------------------------
|
|    - (int)app:sender openFile:(const char *)filename type:(const char *)aType
|
|    returns:  (BOOL)  YES if file was opened
|                      NO if not
|
|-----------------------------------------------------------------------------
|
|    Application delegate message requesting that a file be opened.
|			
\----------------------------------------------------------------------------*/

- (int)app:sender openFile:(const char *)filename type:(const char *)aType
{
    _SolEngine = self;

    if (aType && !strcmp(aType, "solitaire"))
    {
	char fname[60];
	char dirname[MAXPATHLEN+1];
	char *p;

	// separate filename and directory
	strcpy(dirname, filename);
	if (p = rindex(dirname, '/'))
	{
	    *p = '\0';
	    strcpy(fname, p+1);
	}
	else
	{
	    strcpy(fname, filename);
	    strcpy(dirname, "");
	}

	[self setGameType:fname dir:dirname];
        return YES;
    }
    else
    {
        NXBeep();
        NXRunAlertPanel(LOCALIZED_OPEN_TITLE, LOCALIZED_OPEN_ERROR,
	                NULL, NULL, NULL, filename);
        return NO;
    }
    
}

/*--------------------------------------------------------------------------
|
|	- appWillTerminate:sender
|
|	returns: (id) self
|
|---------------------------------------------------------------------------
|
|	This is an Application delegate message sent just before the app 
|	exits.
|	
\--------------------------------------------------------------------------*/

- appWillTerminate:sender
{
    /* kill the active game */
    [[self gameController] endGame:self];
    return self;
}


/*--------------------------------------------------------------------------
|
|	- app:powerOffIn:andSave:
|
|	returns: (id) self
|
|---------------------------------------------------------------------------
|
|	This is an Application delegate message sent just before the app 
|	exits.
|	
\--------------------------------------------------------------------------*/

- app:sender powerOffIn:(int)ms andSave:(int)aFlag
{
    /* kill the active game */
    [[self gameController] endGame:self];
    return self;
}


/*----------------------------------------------------------------------------
|
|    - selectGameIndex:
|
|    returns:  (id) self
|
|-----------------------------------------------------------------------------
|
|    Target of game selection browser.
|			
\----------------------------------------------------------------------------*/

- selectGameIndex:sender
{
    int index = [[gameSelectionBrowser matrixInColumn:0] selectedRow];

    /* nothing to do if already selected */
    if (index == realGameIndex) return self;

    /* kill the active game */
    [[self gameController] endGame:self];
    
    realGameIndex = index;
    [self setGameIndex];
    
    return self;
}


/*----------------------------------------------------------------------------
|
|    - setGameIndex
|
|    returns:  (id) self
|
|-----------------------------------------------------------------------------
|
|    Load the chosen game.
|			
\----------------------------------------------------------------------------*/

- setGameIndex
{
    id myController;
    id newInspector;

    // save selected game to defaults
    NXWriteDefault([NXApp appName], "gameType", 
                   [moduleList realNameAt:realGameIndex]); 


    // get the game controller (dynamically loads first time)
    if (!(myController = [self gameController]))
    {
        [self installInspector:nullInspector ownedBy:nil];
        return self;
    }
   
    // now plug in the inspector
    newInspector = [myController inspector:self];
    if (!newInspector) newInspector = commonInspector;
    
    [self installInspector:newInspector ownedBy:myController];
    
    // start a new game
    [myController startGame:self];
    
    return self;
}


/*----------------------------------------------------------------------------
|
|    - installInspector:newInspector ownedBy:myController
|
|    returns:  (id) self
|
|-----------------------------------------------------------------------------
|
|    Display the inspector.
|			
\----------------------------------------------------------------------------*/

- installInspector:newInspector ownedBy:myController
{
    if (newInspector != currentInspector)
    {
        [oldInspectorOwner inspectorWillBeRemoved];
        oldInspectorOwner = myController;
        
        // don't want it to resize the box.  Suboptimal technique...
        [newInspector setFrame:&inspectorFrame];

        [invisibleInspectorBox setContentView:newInspector];
        currentInspector = newInspector;

        [myController inspectorInstalled];

        [invisibleInspectorBox display];
    }
    return self;
}


/*----------------------------------------------------------------------------
|
|    - gameController
|
|    returns:  (id) current game controller
|
|-----------------------------------------------------------------------------
|
|    Return the game controller for the selected game.  Dynamically load it
|    if necessary.
|			
\----------------------------------------------------------------------------*/

- gameController
{
    id theController;
    
    if (![moduleList controllerAt:realGameIndex])
    {
	NXBundle *bundle;
        const char *name;
        ModuleInfo *mp;
        NXZone *theZone;
	
        mp = [moduleList objectAt:realGameIndex];
	
	// don't try to load if it failed on a previous attempt
	if ([mp failed])
	{
	    return nil;
	}
	
        name = [mp realName];
	bundle = [mp bundle];
	
	// each module gets its own zone
	theZone = NXCreateZone(vm_page_size, vm_page_size, YES);
	NXNameZone(theZone, name);

        theController = [[[bundle classNamed:name] 
	                        allocFromZone:theZone] initFromBundle:bundle
				withName:name];
	if (!theController)
	{
	    [mp setFailed:YES];
	    NXDestroyZone(theZone);
            NXRunAlertPanel([NXApp appName], LOCALIZED_LOAD_ERROR, NULL,
	                    NULL, NULL, name);
	    return nil;
	}
	
	[mp setController:theController];
    }

    return [moduleList controllerAt:realGameIndex];
}

/*----------------------------------------------------------------------------
|
|    - (BOOL)browser:sender columnIsValid:(int)column
|
|    returns:  (BOOL)	YES if browser contents are valid
|			NO if browser contents are not valid
|
|-----------------------------------------------------------------------------
|
|    Delegate message of game selection browser.
|			
\----------------------------------------------------------------------------*/

- (BOOL)browser:sender columnIsValid:(int)column
{
    return browserValid;
}

/*----------------------------------------------------------------------------
|
|    - addCellWithString:(const char *)str at:(int)row toMatrix:matrix
|
|    returns:  (id) self
|
|-----------------------------------------------------------------------------
|
|    Description here.
|			
\----------------------------------------------------------------------------*/

- addCellWithString:(const char *)str at:(int)row toMatrix:matrix
{
    id theCell;
    
    [matrix insertRowAt:row];
    theCell = [matrix cellAt:row :0];
    [theCell setStringValue:str];
    [theCell setLoaded:YES];
    [theCell setLeaf:YES];
    return self;
}


/*----------------------------------------------------------------------------
|
|    - (int)browser:sender fillMatrix:matrix inColumn:(int)column
|
|    returns:  (int) the number of items in 'column'
|
|-----------------------------------------------------------------------------
|
|    Delegate message from game selection browser.  Loads the browser.
|			
\----------------------------------------------------------------------------*/

- (int)browser:sender fillMatrix:matrix inColumn:(int)column
{
    const char *ptr;
    int i;
    
    // this shouldn't happen...
    if (browserValid) return [matrix cellCount];

    for (i = 0; i < [moduleList count]; i++)
    {
        [self addCellWithString:[moduleList nameAt: i] 
	                     at:i
		       toMatrix:matrix];
    }
           
    realGameIndex = 0;

    // select the active game
    ptr = NXGetDefaultValue([NXApp appName], "gameType");
    if (!ptr) ptr = "Klondike";
    if (ptr)
    {
        for (i = 0; i < [moduleList count]; i++)
        if (strcmp(ptr, [moduleList realNameAt:i]) == 0)
        {
            realGameIndex = i;
            break;
        }
    }
    
    browserValid = YES;
    return [matrix cellCount];
}

/*----------------------------------------------------------------------------
|
|    - newGame:sender
|
|    returns:  (id) self
|
|-----------------------------------------------------------------------------
|
|    Shuffle and deal the selected game.
|			
\----------------------------------------------------------------------------*/

- newGame:sender
{
    [[self gameController] startGame:self];
    
    return self;
}


/*----------------------------------------------------------------------------
|
|    - restartGame:sender
|
|    returns:  (id) self
|
|-----------------------------------------------------------------------------
|
|    Restart the current game.
|			
\----------------------------------------------------------------------------*/

- restartGame:sender
{
    [[self gameController] restartGame:self];
    return self;
}

/*----------------------------------------------------------------------------
|
|    - showRules:sender
|
|    returns:  (id) self
|
|-----------------------------------------------------------------------------
|
|    Show rules for the current game.
|			
\----------------------------------------------------------------------------*/

- showRules:sender
{
    [[self gameController] showRules:self];
    return self;
}

/*----------------------------------------------------------------------------
|
|    - showInfoPanel:
|
|    returns:  (id) self
|
|-----------------------------------------------------------------------------
|
|    Display info panel.
|			
\----------------------------------------------------------------------------*/

- showInfoPanel:sender
{
    if (!infoPanel)
    {
        [NXApp loadNibSection:"Info.nib" 
	                owner:self 
	            withNames:NO
                     fromZone:[self zone]];

        [infoPanel setFrameUsingName:"InfoPanel"];
    }
    [infoPanel makeKeyAndOrderFront:nil];
    return self;
}


/*----------------------------------------------------------------------------
|
|    - windowWillClose:sender
|
|    returns:  (id) self
|
|-----------------------------------------------------------------------------
|
|    Window delegate message.
|			
\----------------------------------------------------------------------------*/

- windowWillClose:sender
{
    if (sender == infoPanel)
    {
	infoPanel = nil;
	[sender setDelegate:nil];
    }
    else if (sender == winPanel)
    {
	winPanel = nil;
	[sender setDelegate:nil];
    }
    return self;
}


/*----------------------------------------------------------------------------
|
|    - windowDidMove:
|
|    returns:  (id) self
|
|-----------------------------------------------------------------------------
|
|    Window delegate message.
|			
\----------------------------------------------------------------------------*/

- windowDidMove:sender
{
    if (sender == infoPanel)
    {
        [sender saveFrameUsingName:"InfoPanel"];
    }
    else if (sender == winPanel)
    {
    }
    return self;
}


/*----------------------------------------------------------------------------
|
|    - (CardSize)returnCardSize
|
|    returns:  (CardSize) current card size
|
|-----------------------------------------------------------------------------
|
|    Request for card size.
|			
\----------------------------------------------------------------------------*/

- (CardSize)returnCardSize
{
    return [preferencesObject returnCardSize];
}

/*----------------------------------------------------------------------------
|
|    - (CardBack)returnCardBack
|
|    returns:  (CardBack) current card back
|
|-----------------------------------------------------------------------------
|
|    Request for card back.
|			
\----------------------------------------------------------------------------*/

- (CardBack)returnCardBack
{
    return [preferencesObject returnCardBack];
}

/*----------------------------------------------------------------------------
|
|    - (NXColor)returnBackgroundColor
|
|    returns:  (NXColor) current background color
|
|-----------------------------------------------------------------------------
|
|    Request for background color.
|			
\----------------------------------------------------------------------------*/

- (NXColor)returnBackgroundColor
{
    return [preferencesObject returnBackgroundColor];
}

/*----------------------------------------------------------------------------
|
|    - returnImageForSize:(CardSize)aSize
|
|    returns:  (NXImage *) custom card back
|
|-----------------------------------------------------------------------------
|
|    Request for card back custom image.
|			
\----------------------------------------------------------------------------*/

- returnImageForSize:(CardSize)aSize
{
    return [preferencesObject returnImageForSize:aSize];
}



/*----------------------------------------------------------------------------
|
|    - win
|
|    returns:  (id) self
|
|-----------------------------------------------------------------------------
|
|    Default win routine.
|			
\----------------------------------------------------------------------------*/

- win
{
    if (!winPanel)
	[NXApp loadNibSection:"Winner.nib" 
	                owner:self 
	            withNames:NO 
		     fromZone:[self zone]];

    [winPanel makeKeyAndOrderFront:self];
    [[Sound findSoundFor:"Trumpet.snd"] play];

    return self;
}


/*----------------------------------------------------------------------------
|
|    - (BOOL)menuItemUpdate:(MenuCell *)menuCell
|
|    returns:  (BOOL) YES if cell has changed its appearance
|
|-----------------------------------------------------------------------------
|
|   Method called by all menu items which send their actions to the
|   First Responder.  First, if the object which would respond were the
|   action sent down the responder chain also responds to the message
|   validateCommand:, then it is sent validateCommand: to determine
|   whether that command is valid now, otherwise, if there is a responder
|   to the message, then it is assumed that the item is valid.
|   The method returns YES if the cell has changed its appearance (so that
|   the caller (a Menu) knows to redraw it).
|			
\----------------------------------------------------------------------------*/

- (BOOL)menuItemUpdate:(MenuCell *)menuCell
{
    SEL action;
    id responder, target;
    BOOL enable;

    target = [menuCell target];
    enable = [menuCell isEnabled];

    if (!target) {
	action = [menuCell action];
	responder = [NXApp calcTargetForAction:action];
	if ([responder respondsTo:@selector(validateCommand:)]) {
	    enable = [responder validateCommand:menuCell];
	} else {
	    enable = responder ? YES : NO;
	}
    }

    if ([menuCell isEnabled] != enable) {
	[menuCell setEnabled:enable];
	return YES;
    } else {
	return NO;
    }
}


@end
