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

#import "Card.h"

/*---------------------------------------------------------------------------
|
|    Card Globals
|
|    The arrays _CardPipPositions and _CardCardPips provide information about
|    the positioning of pips on cards.  There are 17 possible positions,
|    described in pipPositions, which are selectively refrerred to for each 
|    of 13 possible card values by a character string.
|
|    The letters A-Q are used to represent pips present in the given card.
|
|    The id's _CardFront, _CardBack etc... are images shared by all instances
|    of Card
|
\----------------------------------------------------------------------------*/

static NSPoint _CardPipPositions[] = {
    {17, 121}, {40, 121}, {63, 121},
    {40, 104},
    {40, 95},
    {17, 86}, {63, 86},
     {17, 69}, {40, 69}, {63, 69},
    {17, 52}, {63, 52},
    {40, 43},
    {40, 34},
    {17, 17}, {40, 17}, {63, 17} 
};

char *_CardCardPips[] = {
    "I",
    "BP",
    "BIP",
    "ACOQ",
    "ACIOQ",
    "ACHJOQ",
    "ACEHJOQ",
    "ACEHJMOQ",
    "ACFGIKLOQ",
    "ACDFGKLNOQ",
    "CO",
    "CO",
    "AQ"
};

static NSString *cardValueStrings[] = {    
    @"Ace", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9",
    @"Jack", @"Queen", @"King", nil
};

static NSString *cardSuitStrings[] = {
    @"Clubs", @"Spades", @"Diamonds", @"Hearts", nil
};

static NSString *cardColorStrings[] = {
    @"Black", @"Red", nil
};

static NSImage* _CardFrontImage = nil;
static NSImage* _CardBackImage = nil;
static NSImage* _CardSymbolsImage = nil;
static NSImage* _CardKingClubsImage = nil;
static NSImage* _CardKingDiamondsImage = nil;
static NSImage* _CardKingHeartsImage = nil;
static NSImage* _CardKingSpadesImage = nil;
static NSImage* _CardQueenClubsImage = nil;
static NSImage* _CardQueenDiamondsImage = nil;
static NSImage* _CardQueenHeartsImage = nil;
static NSImage* _CardQueenSpadesImage = nil;
static NSImage* _CardJackClubsImage = nil;
static NSImage* _CardJackDiamondsImage = nil;
static NSImage* _CardJackHeartsImage = nil;
static NSImage* _CardJackSpadesImage = nil;
static NSImage* _CardAceOfSpadesImage = nil;
    
static NSImage* _preDrawnImage[52];

static BOOL _preDrawnImagesAvailable;


@implementation Card

/*"
   Instances of this class provide low-level functionality for card games.
   Cards can have a suit, a value, and an orientation.  These values can be
   initialized, modified and determined by clients of the class.  Cards also
   know how to render themselves on screen.

   The card images can either be drawn by compositing the partial card images
   contained in the TIFF files in the CardSet, or the 52 cards in a deck can
   be pre-drawn and stored in shared NXImage caches (which are private class
   variables) for reuse. The shared NXImage caches increase performance, but
   with the penalty of increased memory usage.
"*/

//-------------------------------------------------------------------
//	Initializing the class
//-------------------------------------------------------------------

+ (void) initialize
/*"
   Initializes the class object. The partial card images from CardSet TIFFS
   are loaded here. 
"*/
{
    if (self == [Card class])
    {
        NSBundle* bundle = [NSBundle bundleForClass:self];
        NSString* imagePath = nil;

        imagePath = [bundle pathForResource:@"Card" ofType:@"tiff"];
        _CardFrontImage = [[NSImage alloc] initWithContentsOfFile:imagePath];

        imagePath = [bundle pathForResource:@"Back" ofType:@"tiff"];
        _CardBackImage = [[NSImage alloc] initWithContentsOfFile:imagePath];

        imagePath = [bundle pathForResource:@"KingClubs" ofType:@"tiff"];
        _CardKingClubsImage = [[NSImage alloc] initWithContentsOfFile:imagePath];

        imagePath = [bundle pathForResource:@"KingDiamonds" ofType:@"tiff"];
        _CardKingDiamondsImage = [[NSImage alloc] initWithContentsOfFile:imagePath];

        imagePath = [bundle pathForResource:@"KingHearts" ofType:@"tiff"];
        _CardKingHeartsImage = [[NSImage alloc] initWithContentsOfFile:imagePath];

        imagePath = [bundle pathForResource:@"KingSpades" ofType:@"tiff"];
        _CardKingSpadesImage = [[NSImage alloc] initWithContentsOfFile:imagePath];

        imagePath = [bundle pathForResource:@"QueenClubs" ofType:@"tiff"];
        _CardQueenClubsImage = [[NSImage alloc] initWithContentsOfFile:imagePath];

        imagePath = [bundle pathForResource:@"QueenDiamonds" ofType:@"tiff"];
        _CardQueenDiamondsImage = [[NSImage alloc] initWithContentsOfFile:imagePath];

        imagePath = [bundle pathForResource:@"QueenHearts" ofType:@"tiff"];
        _CardQueenHeartsImage = [[NSImage alloc] initWithContentsOfFile:imagePath];

        imagePath = [bundle pathForResource:@"QueenSpades" ofType:@"tiff"];
        _CardQueenSpadesImage = [[NSImage alloc] initWithContentsOfFile:imagePath];

        imagePath = [bundle pathForResource:@"JackClubs" ofType:@"tiff"];
        _CardJackClubsImage = [[NSImage alloc] initWithContentsOfFile:imagePath];

        imagePath = [bundle pathForResource:@"JackDiamonds" ofType:@"tiff"];
        _CardJackDiamondsImage = [[NSImage alloc] initWithContentsOfFile:imagePath];

        imagePath = [bundle pathForResource:@"JackHearts" ofType:@"tiff"];
        _CardJackHeartsImage = [[NSImage alloc] initWithContentsOfFile:imagePath];

        imagePath = [bundle pathForResource:@"JackSpades" ofType:@"tiff"];
        _CardJackSpadesImage = [[NSImage alloc] initWithContentsOfFile:imagePath];

        imagePath = [bundle pathForResource:@"AceOfSpades" ofType:@"tiff"];
        _CardAceOfSpadesImage = [[NSImage alloc] initWithContentsOfFile:imagePath];

        imagePath = [bundle pathForResource:@"Symbols" ofType:@"tiff"];
        _CardSymbolsImage = [[NSImage alloc] initWithContentsOfFile:imagePath];
    }
}


+ (void) drawCardImages
/*"
   Creates shared NSImage caches of all 52 cards in the deck. Uses the instance
   method #preDrawCards: to do the actual drawing.
"*/
{
    Card* 	tempCard;
    int 	i,j;
    NSSize 	aSize;

    if(_preDrawnImagesAvailable) return;	// don't draw again
    aSize = [_CardFrontImage size];
    for(i=0; i<4; i++){
        for(j=0; j<13; j++){
            tempCard = [[Card alloc] initSuit:i value:j faceUp:YES];
            _preDrawnImage[(i*13)+j] = [[NSImage alloc] initWithSize:aSize];
            [_preDrawnImage[(i*13)+j] addRepresentation:
                [[[NSCustomImageRep alloc] initWithDrawSelector:@selector(preDrawCard:) delegate:tempCard] autorelease]];
            [_preDrawnImage[(i*13)+j] lockFocus];
            [_preDrawnImage[(i*13)+j] unlockFocus];
            [tempCard release];
        }
    }
    _preDrawnImagesAvailable = YES;
}


+ (void) freeCardImages
/*"
   Frees the shared NSImage caches of all 52 cards.
"*/
{
    int i;
    
    if(!_preDrawnImagesAvailable) return;	// don't free again
    for(i=0; i<52; i++){
    	[_preDrawnImage[i] release];
    }
    _preDrawnImagesAvailable = NO;
}


+ (void) setCardBackImage:theImage
/*"
   Sets a custom image used to draw the back of the card.
"*/
{
    if(theImage){
        [_CardBackImage autorelease];
    	_CardBackImage = [theImage copyWithZone:[self zone]];
    }
}


+ (void) setCardBack:(CardBack)aBack
/*"
   Sets the card back used to draw the back of the card.

   _{#{Constant	Image}}
   _{CS_DEFAULT	Original image from version 1.0.}
   _{CS_TRAD		Traditional pattern.}
   _{CS_SHIP		Ship at sea.}
   _{CS_CUSTOM	Custom image.}
"*/
{
    NSBundle* bundle = [NSBundle bundleForClass:self];
    NSString* imagePath = nil;

    switch (aBack)
    {
        case CS_DEFAULT:
            imagePath = [bundle pathForResource:@"Back" ofType:@"tiff"];
            break;
        case CS_TRAD:
            imagePath = [bundle pathForResource:@"TradBack" ofType:@"tiff"];
            break;
        case CS_SHIP:
            imagePath = [bundle pathForResource:@"Ship" ofType:@"tiff"];
            break;
        default:
            imagePath = [bundle pathForResource:@"Back" ofType:@"tiff"];
            break;
    }
    
    if (imagePath != nil) {
        NSImage* newImage = [[NSImage alloc] initWithContentsOfFile:imagePath];
        if (newImage != nil) {
            [self setCardBackImage:newImage];
        }
    }
}

+ (NSString *)stringForCardSuit:(CardSuit)aSuit
/*"
**	Returns a string that represents aSuit. This will be one of Spades, Clubs,
**	Diamonds or Hearts. The current way of doing this is not localized.
"*/
{
    if (aSuit > CS_HIGHSUIT)
        [NSException raise:NSRangeException format:
            @"***%s: index (%d) beyond bounds (%d)",
            sel_getName(_cmd), aSuit, CS_HIGHSUIT];

    return cardSuitStrings[aSuit];
}

+ (NSString *)stringForCardValue:(CardValue)aValue
/*"
**	Returns a string that represents aValue. This will be one of Ace, Jack, 
**	Queen, King, or 1 through 10. This current implementation does not lend 
**	itself to localization.
"*/
{
    if (aValue > CS_HIGHVALUE)
        [NSException raise:NSRangeException format:
            @"***%s: index (%d) beyond bounds (%d)",
            sel_getName(_cmd), aValue, CS_HIGHVALUE];

    return cardValueStrings[aValue];
}

+ (NSString *)stringForCardColor:(CardColor)aColor
/*"
**	Returns a string that represents aColor. It will either return Red or 
**	Black. 
"*/
{
    if (aColor > CS_HIGHCOLOR)
        [NSException raise:NSRangeException format:
            @"***%s: index (%d) beyond bounds (%d)",
            sel_getName(_cmd), aColor, CS_HIGHCOLOR];

    return cardColorStrings[aColor];
}


//-------------------------------------------------------------------
//	Creating and freeing
//-------------------------------------------------------------------

- init
/*"
   Initialize a new Card object.  This method invokes the
   #initSuit:value:faceUp: method.  The suit is set to CS_CLUBS, the value
   is set to CS_ACE, and the card is face down.  Returns self.
"*/
{
    return [self initSuit:CS_ACE value:CS_CLUBS faceUp:NO];
}


- initSuit:(CardSuit)aSuit value:(CardValue)aValue
/*"
   Initializes a card with suit aSuit and value aValue, face down, by calling
   #initSuit:value:faceUp:. Returns self.
"*/
{
    return [self initSuit:aSuit value:aValue faceUp:NO];
}


- initSuit:(CardSuit)aSuit value:(CardValue)aValue faceUp:(BOOL)anOrientation
/*"
   Initializes a card with suit aSuit, value aValue, and orientation anOrientation.
   This is the designated initializer for Card object instances. Returns self.
"*/
{
    [super init];
    suit = aSuit;
    value = aValue;
    faceUp = anOrientation;
    return self;
}


- copyWithZone:(NSZone *)zone
/*"
   Returns a new Card object, allocated from the specified zone, which is an
   exact duplicate of the sender.
"*/
{
    return [[[self class] allocWithZone:zone] initSuit:suit value:value faceUp:faceUp];
}

- (NSString *)description
{
    NSString *theString;

    theString = [NSString stringWithFormat:@"%@ : face %@ %@ of %@",
        [super description], (faceUp ? @"up" : @"down"),
        [Card stringForCardValue:value], [Card stringForCardSuit:suit]];
    return theString;
}


- (void) setSuit:(CardSuit)aSuit
/*"
   Sets the card's suit to aSuit. Valid suit values are:

   _{#{Constant Suit}}
   _{CS_LOWSUIT	Clubs}
   _{CS_CLUBS		Clubs}
   _{CS_SPADES		Spades}
   _{CS_DIAMONDS	Diamonds}
   _{CS_HEARTS		Hearts}
   _{CS_HIGHSUIT	Hearts}
"*/
{
    suit = aSuit; 
}


- (void) setValue:(CardValue)aValue
/*"
   Sets the card's value to aValue.  Valid card values are:

   _{#{Constant		Card Value}}
   _{CS_LOWVALUE	Ace}
   _{CS_ACE			Ace}
   _{CS_TWO			Two}
   _{CS_THREE		Three}
   _{CS_FOUR		Four}
   _{CS_FIVE		Five}
   _{CS_SIX			Six}
   _{CS_SEVEN		Seven}
   _{CS_EIGHT		Eight}
   _{CS_NINE		Nine}
   _{CS_TEN			Ten}
   _{CS_JACK		Jack}
   _{CS_QUEEN		Queen}
   _{CS_KING		King}
   _{CS_HIGHVALUE	King}
"*/
{
    value = aValue; 
}


- (void) flip
/*"
   Turns the card over.
"*/
{
    faceUp = !faceUp; 
}


- (void) setFaceUp:(BOOL)anOrientation
/*"
   Sets the card's orientation to anOrientation.
"*/
{
    faceUp = anOrientation; 
}


- (CardSuit) suit
/*"
   Returns the receiver's suit.  The returned value will be one of:

   _{#{Constant	Suit}}
   _{CS_LOWSUIT	Clubs}
   _{CS_CLUBS	Clubs}
   _{CS_SPADES	Spades}
   _{CS_DIAMONDS Diamonds}
   _{CS_HEARTS	Hearts}
   _{CS_HIGHSUIT Hearts}
"*/
{
    return suit;
}


- (CardValue) value
/*"
   Returns the receiver's value.  The returned value will be one of:

   _{#{Constant		Card Value}}
   _{CS_LOWVALUE	Ace}
   _{CS_ACE			Ace}
   _{CS_TWO			Two}
   _{CS_THREE		Three}
   _{CS_FOUR		Four}
   _{CS_FIVE		Five}
   _{CS_SIX			Six}
   _{CS_SEVEN		Seven}
   _{CS_EIGHT		Eight}
   _{CS_NINE		Nine}
   _{CS_TEN			Ten}
   _{CS_JACK		Jack}
   _{CS_QUEEN		Queen}
   _{CS_KING		King}
   _{CS_HIGHVALUE	King}
"*/
{
    return value;
}


- (BOOL) isFaceUp
/*"
   Returns YES if receiver is face up, otherwise returns NO.
"*/
{
    return faceUp;
}


- (CardColor) cardColor
/*"
   Returns the receiver's color.  The returned value is equal to one of these constants:

   _{#{Constant	Color}}
   _{CS_BLACK	Card's suit is either spades or clubs.}
   _{CS_RED		Card's suit is either hearts or diamonds.}
"*/
{
    if ((suit == CS_SPADES) || (suit == CS_CLUBS))
    {
        return CS_BLACK;
    }
    return CS_RED;
}


-(BOOL) isEqual:(Card*)aCard
/*"
   Returns YES if the receiver and aCard have the same suit and value.
"*/
{
	if (self == aCard)
		return YES;
	
	if ((value == [aCard value]) &&
		(suit == [aCard suit]))
		return YES;
	
	return NO;
}


-(BOOL) isOneLargerThan:(Card*)aCard wrap:(BOOL)flag
/*"
   Returns YES if the receiver's value is exactly one larger than aCard.
   The cards can either be in the same suit or different ones. If flag
   is YES then the case of the receiver being an ace and aCard being
   a king would return YES.
"*/
{
	if (value == [aCard value] + 1)
		return YES;
	
	if (flag && ((value == CS_LOWVALUE) &&
				 ([aCard value] == CS_HIGHVALUE)))
		return YES;
	
	return NO;
}


-(BOOL) isOneSmallerThan:(Card*)aCard wrap:(BOOL)flag
/*"
   Returns YES if the receiver's value is exactly one smaller than aCard.
   The cards can either be in the same suit or different ones. If flag
   is YES then the case of the receiver being a king and aCard being
   an ace would return YES.
"*/
{
	if (value == [aCard value] - 1)
		return YES;
	
	if (flag && ((value == CS_HIGHVALUE) &&
				 ([aCard value] == CS_LOWVALUE)))
		return YES;
	
	return NO;
}


- (void) preDrawCard:(NSImage*)theImage
/*"
   Pre-draws theSharedNSImage, both outline and contents, by calling
   #drawOutlineAt: and #drawContentsAt: for point {0.0, 0.0}.
"*/
{
    NSPoint thePoint={0.,0.};
    
    [self drawOutlineAt:thePoint];
    [self drawContentsAt:thePoint];
}


- (void) drawCardAt:(NSPoint)thePoint
/*"
   Draws the card, both outline and contents, at thePoint, by calling
   #drawOutlineAt: and #drawContentsAt:. If the 52 cards of the deck have been
   pre-drawn, the shared NSImage cache is used. Otherwise, the card is drawn
   by compositing the partial images.
"*/
{
    if(_preDrawnImagesAvailable && faceUp) {
    	[_preDrawnImage[(suit*13)+value] compositeToPoint:thePoint 
		operation:NSCompositeSourceOver];
        return;
    }
    [self drawOutlineAt:thePoint];
    [self drawContentsAt:thePoint]; 
}


- (void) drawOutlineAt:(NSPoint)thePoint
/*"
   Draws the card's border at thePoint.
"*/
{
    [_CardFrontImage compositeToPoint:thePoint operation:NSCompositeSourceOver]; 
}


- (void) drawContentsAt:(NSPoint)thePoint
/*"
   Draws the contents of the card (but not the border) at thePoint.
"*/
{
    if (!faceUp)
    {
    
	/*------------------------------------------------------ 
	|
	|    Draw the back of the card
	|
	\------------------------------------------------------*/

        NSPoint destPoint = {thePoint.x + 8, thePoint.y + 7};

        [_CardBackImage compositeToPoint:destPoint 
		operation:NSCompositeSourceOver];
	
    } else {
    
	/*---------------------------------------------------------
	|
	|    Draw the upper left / lower right value indicator
	|
	\---------------------------------------------------------*/

        {
            NSRect sourceRect = {{value * 22, 80}, {11, 20}};
            NSPoint destPoint = {thePoint.x + 4, thePoint.y + 141};
			
            if (([self cardColor] == CS_RED))
            {
                sourceRect.origin.x += 11;
            }
            [_CardSymbolsImage compositeToPoint:destPoint fromRect:sourceRect operation:NSCompositeSourceOver];
			
            sourceRect.origin.y -= 20;
            destPoint.x = thePoint.x + 92;
            destPoint.y = thePoint.y + 6;
			
            [_CardSymbolsImage compositeToPoint:destPoint fromRect:sourceRect operation:NSCompositeSourceOver];
        }

	/*----------------------------------------------------------
	|
	|    Draw the upper left / lower right suit indicator
	|
	\----------------------------------------------------------*/

        {
            NSRect sourceRect = {{suit * 13, 15}, {13, 15}};
            NSPoint destPoint = {thePoint.x + 3, thePoint.y + 126};
			
            [_CardSymbolsImage compositeToPoint:destPoint fromRect:sourceRect operation:NSCompositeSourceOver];
            sourceRect.origin.x += 52;
            destPoint.x = thePoint.x + 91;
            destPoint.y = thePoint.y + 26;
            [_CardSymbolsImage compositeToPoint:destPoint fromRect:sourceRect operation:NSCompositeSourceOver];
        }

	/*---------------------------------------------------------
	|
	|    Draw special card images for face cards and the
	|    ace of spades
	|
	\---------------------------------------------------------*/

        {
            NSPoint destPoint = {thePoint.x + 16, thePoint.y + 16};

            switch (value)
              {
                case CS_JACK:
                    switch (suit)
                        {
                        case CS_CLUBS:
                            [_CardJackClubsImage compositeToPoint:destPoint operation:NSCompositeSourceOver];
                            break;
                        case CS_DIAMONDS:
                            [_CardJackDiamondsImage compositeToPoint:destPoint operation:NSCompositeSourceOver];
                            break;
                        case CS_HEARTS:
                            [_CardJackHeartsImage compositeToPoint:destPoint operation:NSCompositeSourceOver];
                            break;
                        case CS_SPADES:
                            [_CardJackSpadesImage compositeToPoint:destPoint operation:NSCompositeSourceOver];
                            break;
                        }
                    return;

                case CS_QUEEN:
                    switch (suit)
                        {
                        case CS_CLUBS:
                            [_CardQueenClubsImage compositeToPoint:destPoint operation:NSCompositeSourceOver];
                            break;
                            case CS_DIAMONDS:
                                [_CardQueenDiamondsImage compositeToPoint:destPoint operation:NSCompositeSourceOver];
                                break;
                            case CS_HEARTS:
                                [_CardQueenHeartsImage compositeToPoint:destPoint operation:NSCompositeSourceOver];
                                break;
                            case CS_SPADES:
                                [_CardQueenSpadesImage compositeToPoint:destPoint operation:NSCompositeSourceOver];
                                break;
                                }
                    return;

            case CS_KING:
                switch (suit)
                  {
                    case CS_CLUBS:
                        [_CardKingClubsImage compositeToPoint:destPoint operation:NSCompositeSourceOver];
                        break;
                    case CS_DIAMONDS:
                        [_CardKingDiamondsImage compositeToPoint:destPoint operation:NSCompositeSourceOver];
                        break;
                    case CS_HEARTS:
                        [_CardKingHeartsImage compositeToPoint:destPoint operation:NSCompositeSourceOver];
                        break;
                    case CS_SPADES:
                        [_CardKingSpadesImage compositeToPoint:destPoint operation:NSCompositeSourceOver];
                        break;
                  }
                return;
		
        default:
            break;
              }
			
	    /*----------------------------------------------------
	    |
	    |    Not only draw the special image for the ace of
	    |    spades, but avoid the standard suit pip drawing
	    |
	    \----------------------------------------------------*/

            if ((value == CS_ACE) && (suit == CS_SPADES)) {
                [_CardAceOfSpadesImage compositeToPoint:destPoint 		operation:NSCompositeSourceOver];
                return;
            }
        }
		
	/*------------------------------------------------------
	|
	|    Draw suit pips on the card based on patterns
	|    defined by the _CardCardSymbols and
	|    _CardCardPips arrays
	|
	\------------------------------------------------------*/

	{
	    NSRect suitRect = {{suit * 26, 31}, {26, 29}};
	    NSRect upsideSuitRect = {{104 + suit * 26, 31}, {26, 29}};
	    char *pipString = _CardCardPips[value];

        while (*pipString) {
            NSPoint tempPoint = _CardPipPositions[*pipString - 'A'];
            tempPoint.x += thePoint.x;
            tempPoint.y += thePoint.y;
            [_CardSymbolsImage compositeToPoint:tempPoint                                       fromRect:(*pipString < 'K') ? suitRect : upsideSuitRect	operation:NSCompositeSourceOver];
            pipString++;
        }
    }
    } 
}


- (id) initWithCoder:(NSCoder *)aDecoder
/*"
   Unarchives an instance of Card. Returns self.
"*/
{
    [aDecoder decodeValuesOfObjCTypes:"iic", &suit, &value, &faceUp];
    return self;
}


- (void) encodeWithCoder:(NSCoder *)aCoder
/*"
   Writes the Card object to aCoder. This message is sent is response to archiving;
   you never send this message directly.
"*/
{
    [aCoder encodeValuesOfObjCTypes:"iic", &suit, &value, &faceUp];
}

@end

