
#import "TableView.h"
#import "crapswraps.h"
#import "props.h"
  
@implementation TableView
 
- initializePropositions 
{
    int i;
    
	window = [dealer table];
	   
    for(i=0; i<MAX_PLAYERS; i++)
	{
		bets[i].amount = 0;
		bets[i].odds = 0;
		bets[i].isOn = YES;
    }
    
    if((displayFont = [Font newFont:"Helvetica" size:8 matrix:NX_IDENTITYMATRIX]) == nil)
        NXRunAlertPanel("Font Error", "Unable to find font", "Okay", NULL, NULL);
    
    puckFont = [Font newFont:"Times-Roman" size:10 matrix:NX_IDENTITYMATRIX];
    
    for(i=0; i<MAX_PLAYERS; i++) 
	{
        chip[i].textColor = (i+1<(MAX_PLAYERS/2)) ? NX_BLACK : NX_WHITE;
		chip[i].chipColor = NX_WHITE - NX_WHITE/((float)MAX_PLAYERS/((float)i+1.0));
    }
     
    return self;
}

- initializeChipPlacement
{
    int i, numPlayers;
    float width = NX_WIDTH(&bounds), section, center;
    
    numPlayers = [dealer numPlayers];

    section = (width)/numPlayers;
    center = section/2;
    
    for(i=0; i<numPlayers; i++) 
	{
        chip[i].r = 10.0;
		chip[i].x = (section*i)+center;
		chip[i].y = chip[i].r + 2.0; 
    }
    
    return self;
}

- setTag:(int)aTag
{
    tag = aTag;
    
    return self;
}

- setTrackingRectForWindow:aWindow
{
    NXRect aRect;
    
	
	//window = aWindow;
	
    [self getFrame:&aRect];
    [superview convertRect:&aRect toView:nil];
    
    [window setTrackingRect:&aRect
                     inside:NO
                      owner:self
                        tag:tag
                       left:NO
                      right:NO];

	return self;
}

- (int)tag
{
    return tag;
}

	   /***********************
	    *  Betting Methods    *
	    ***********************/

- bet:(int)units forPlayer:player num:(int)playerNum
{
    int point = [dealer point];
    char buf[256];
    
    if(player == nil) return nil;
    
    // Things player can't bet on directly...
    if((tag == COME_FOUR || tag == COME_FIVE || tag == COME_SIX || tag == COME_EIGHT ||
	    tag == COME_NINE || tag == COME_TEN || tag == DONT_COME_FOUR || 
		tag == DONT_COME_FIVE || tag == DONT_COME_SIX || tag == DONT_COME_EIGHT || 
		tag == DONT_COME_NINE || tag == DONT_COME_TEN)) 
	{
		[dealer playSound:WARNSOUND];
		sprintf(buf, "Can't bet on %s directly...", prop[tag].name);
		[dealer say:buf];
		return self;
    } 

    // Do not allow a bet on PASS or DONT_PASS if point has been established.
    if(point != 0 && (tag == PASS || tag == DONT_PASS)) 
	{
		[dealer playSound:WARNSOUND];
		sprintf(buf, "Can't bet on %s after the point has been established...", prop[tag].name);
		[dealer say:buf];
		return nil;
    }
    
    // use default bet if user wants
    if(units == DEFAULT) 
		units = (prop[tag].cost > [dealer tableMin]) ? prop[tag].cost : [dealer tableMin];
    
    // special case HORN
    if(tag == HORN)
        units = prop[tag].cost * [dealer tableMin];
        
    // if this bet is more than the player has in chips, or if units would put
    // the bet above the table maximum, do not allow it.
    if(units > [player amountInBank]) 
	{
		[dealer playSound:WARNSOUND];
		sprintf(buf, "Can't bet more than you have!");
		[dealer say:buf];
		return nil;
    }
    
    if(((units+bets[playerNum].amount+bets[playerNum].odds) > [dealer tableMax])) 
	{
		[dealer playSound:WARNSOUND];
		sprintf(buf, "Betting $%d on %s would put your bet over the table max!",
			units, prop[tag].name);
		[dealer say:buf];
		return nil;
    }
    
    [dealer playSound:CHIPSOUND];
    
    //  add units to bet, subtract from players bank
    bets[playerNum].amount +=  units;
    bets[playerNum].isOn = YES;
    [player addToBank:-units];
    [self  displayFromOpaqueAncestor:&bounds :1 :YES];
	
	sprintf(buf, "House accepts %s's $%d bet on %s.", [player playerName], bets[playerNum].amount, prop[tag].name);
	[dealer say:buf];
    
    return self;
}

- removeBet:(int)units forPlayer:player num:(int)playerNum
{
    int point = [dealer point];
    char buf[256];
    
    if(player == nil) return nil;
    
    // Things player can't bet on directly...
    if((tag == COME_FOUR || tag == COME_FIVE || tag == COME_SIX || tag == COME_EIGHT ||
	    tag == COME_NINE || tag == COME_TEN || tag == DONT_COME_FOUR || 
		tag == DONT_COME_FIVE || tag == DONT_COME_SIX || tag == DONT_COME_EIGHT || 
		tag == DONT_COME_NINE || tag == DONT_COME_TEN)) 
	{
		[dealer playSound:WARNSOUND];
		sprintf(buf, "Can't bet on %s directly...", prop[tag].name);
		[dealer say:buf];
		return nil;
    } 

    // do not allow a player to remove a PASS, DONT_PASS, COME, or DONT_COME bet once
    // point has been established.
    if(point != 0 && (tag == PASS || tag == DONT_PASS)) 
	{
		[dealer playSound:WARNSOUND];
		sprintf(buf, "Can't remove %s bets after the point has been established!", 
			prop[tag].name);
		[dealer say:buf];
		return nil;
    } 
	else if(tag >= COME_FOUR && tag <= COME_TEN) 
	{
		[dealer playSound:WARNSOUND];
		sprintf(buf, "Can't remove %s bets after the point has been established!", 
			prop[tag].name);
		[dealer say:buf];
		return nil;
    } 
	else if(tag >= DONT_COME_FOUR && tag <= DONT_COME_TEN) 
	{
		[dealer playSound:WARNSOUND];
		sprintf(buf, "Can't remove %s bets after the point has been established!", 
			prop[tag].name);
		[dealer say:buf];
		return nil;
    }

    if(units == DEFAULT)
		units = bets[playerNum].amount;
        
    // dont allow a player to subtract more than he has bet
    if(units > bets[playerNum].amount) 
	{
		[dealer playSound:WARNSOUND];
		sprintf(buf, "Can't remove more than you've bet!");
		[dealer say:buf];
		return nil;
    }

    [dealer playSound:CHIPSOUND];

    // subtract units from bet, add that amount to player's bank.
    bets[playerNum].amount -=  units;
    [player addToBank:units];
    
    [self displayFromOpaqueAncestor:&bounds :1 :YES];

    return self;
}

- layOdds:(int)units forPlayer:player num:(int)playerNum
{
    int point = [dealer point];
    char buf[256];
    

    if(player == nil) return nil;
    
    // only allow odds on props that allow odds, and only on PASS and DONT_PASS after
    // point has been established.
    if(!prop[tag].allowsOdds) 
	{
		[dealer playSound:WARNSOUND];
		sprintf(buf, "Odds bets aren't allowed on %s!", prop[tag].name);
		[dealer say:buf];
		return nil;
    }
    
    if(point == 0 && (tag == PASS || tag == DONT_PASS)) 
	{
		[dealer playSound:WARNSOUND];
		sprintf(buf, "Can't buy odds on %s before the point has been established!", prop[tag].name);
		[dealer say:buf];
		return nil;
    }
    
    if(units == DEFAULT)
		units = bets[playerNum].amount;
        
    // dont allow any more odds if units is greater than players bank, or if
    // adding units to odds would make the bet greater than table max.
    if(units > [player amountInBank] || bets[playerNum].amount == 0 ) 
	{
		[dealer playSound:WARNSOUND];
		[dealer say:"You don't have enough money for that bet!"];
		return nil;
    }
    
    if(((units+bets[playerNum].amount+bets[playerNum].odds) > [dealer tableMax])) 
	{
		[dealer playSound:WARNSOUND];
		sprintf(buf, "Laying odds on %s now would put your bet over the table max!", prop[tag].name);
		[dealer say:buf];
		return nil;
    }
    
    // don't allow more than [dealer tableOdds] times odds
    if(((bets[playerNum].odds+units) / bets[playerNum].amount) > [dealer tableOdds]) 
	{
		[dealer playSound:WARNSOUND];
		sprintf(buf, "Can't have more than %dX odds on bets!", [dealer tableOdds]);
		[dealer say:buf];
		return nil;
    }
    
    [dealer playSound:CHIPSOUND];

    // add odds to bet, subtract amount from bank.
    bets[playerNum].odds += units;
    bets[playerNum].isOn = YES;
    [player addToBank:-units];
    [self  displayFromOpaqueAncestor:&bounds :1 :YES];

    return self;
}
 
- removeOdds:(int)units forPlayer:player num:(int)playerNum
{
    char buf[256];

    if(player == nil) return nil;
    
    if(units == DEFAULT)
        units = bets[playerNum].odds;
    
    // don't allow removal of more odds than player has bet
    if(units > bets[playerNum].odds) 
	{
		[dealer playSound:WARNSOUND];
		sprintf(buf, "Can't remove more odds than you've bet!");
		[dealer say:buf];
		return nil;
    } 
    
    [dealer playSound:CHIPSOUND];

    bets[playerNum].odds -= units;
    [player addToBank:units];

    [self  displayFromOpaqueAncestor:&bounds :1 :YES];
    
    return self;
}

/**************************************
 *         Manipulating Bets          *
 **************************************/  
- payOutForRoll:(int)roll
{
    int player, amount=0, odds=0, profit=0, cost=0;
    
    for(player = 0; player < MAX_PLAYERS; player++)
	{
		if(bets[player].amount > 0 && bets[player].isOn) 
		{
			if(tag == FIELD && (roll == 2 || roll == 12))  // special case FIELD
			{
				amount = 2 * prop[tag].payoff * (int) (bets[player].amount /
				     prop[tag].cost);
			}
			else 
			{
				amount = prop[tag].payoff * (int) (bets[player].amount / prop[tag].cost);
	    	}
	    	amount = amount + (int)(bets[player].amount % prop[tag].cost);
	    	amount = amount + bets[player].amount;
	    
	    	// special case HORN.  Payout is 1/4 the bet times the payout for TWO
	    	// THREE, ELEVEN, or TWELVE, depending on what is rolled.
	    	if(tag == HORN) 
			{
	        	switch(roll) 
				{
					case 2:
						amount = prop[TWO].payoff * 
							(int) (bets[player].amount/4/prop[TWO].cost);
						amount = amount + (int)((bets[player].amount/4) %
							prop[TWO].cost);
						break;
					case 3:
						amount = prop[THREE].payoff * 
							(int) (bets[player].amount/4/prop[THREE].cost);
						amount = amount + (int)((bets[player].amount/4) %
							prop[THREE].cost);
						break;
					case 11:
						amount = prop[ELEVEN].payoff * 
							(int) (bets[player].amount/4/prop[ELEVEN].cost);
						amount = amount + (int)((bets[player].amount/4) %
							prop[ELEVEN].cost);
						break;
					case 12:
						amount = prop[TWELVE].payoff * 
							(int) (bets[player].amount/4/prop[TWELVE].cost);
						amount = amount + (int)((bets[player].amount/4) %
							prop[TWELVE].cost);
						break;
				}
				amount = amount + (bets[player].amount/4);
	    	}
	    
	    	// get odds for roll if needed
	    	if(prop[tag].allowsOdds && bets[player].odds > 0) 
			{
				switch(tag) 
				{
					case PASS:
						[dealer trueOdds:&profit for:&cost forRoll:roll];
						break;
					case COME_FOUR:
						[dealer trueOdds:&profit for:&cost forRoll:roll];
						break;
					case COME_FIVE:
						[dealer trueOdds:&profit for:&cost forRoll:roll];
						break;
					case COME_SIX:
						[dealer trueOdds:&profit for:&cost forRoll:roll];
						break;
					case COME_EIGHT:
						[dealer trueOdds:&profit for:&cost forRoll:roll];
						break;
					case COME_NINE:
						[dealer trueOdds:&profit for:&cost forRoll:roll];
						break;
					case COME_TEN:
						[dealer trueOdds:&profit for:&cost forRoll:roll];
						break;
					
					// odds on the DONTs are opposite
					case DONT_PASS:
						[dealer trueOdds:&profit for:&cost forRoll:-[dealer point]];
						break;
					case DONT_COME_FOUR:
						[dealer trueOdds:&profit for:&cost forRoll:-4];
						break;
					case DONT_COME_FIVE:
						[dealer trueOdds:&profit for:&cost forRoll:-5];
						break;
					case DONT_COME_SIX:
						[dealer trueOdds:&profit for:&cost forRoll:-6];
						break;
					case DONT_COME_EIGHT:
						[dealer trueOdds:&profit for:&cost forRoll:-8];
						break;
					case DONT_COME_NINE:
						[dealer trueOdds:&profit for:&cost forRoll:-9];
						break;
					case DONT_COME_TEN:
						[dealer trueOdds:&profit for:&cost forRoll:-10];
						break;
				}
	
				odds = profit * (int)(bets[player].odds / cost);
				odds = odds + (int)(bets[player].odds % cost);
				odds = odds + bets[player].odds;
				bets[player].odds = 0;
			}
	    
	    	bets[player].amount = amount + odds;
    	}
    }
    [self  displayFromOpaqueAncestor:&bounds :1 :YES];
    
    return self;
}

- clearBets
{
    int i;
    
    for(i=0; i<MAX_PLAYERS; i++) 
	{
        if(bets[i].isOn) 
		{
			bets[i].amount = 0;
			bets[i].odds = 0;
		}
    }
    [self  displayFromOpaqueAncestor:&bounds :1 :YES];
    return self;
}

- getBetsFrom:otherView
{
    int i;
    
    for(i=0; i<MAX_PLAYERS; i++) 
	{
        [otherView getBet:&bets[i].amount odds:&bets[i].odds forPlayer:i];
		[otherView setBet:0 odds:0 forPlayer:i];
    }
    
    [self  displayFromOpaqueAncestor:&bounds :1 :YES];
    return self;
}

- addBetsFrom:otherView
{
    int i, bet, odds;
    
    for(i=0; i<MAX_PLAYERS; i++) 
	{
        [otherView getBet:&bet odds:&odds forPlayer:i];
		[otherView setBet:0 odds:0 forPlayer:i];
		bets[i].amount = bets[i].amount + bet;
		bets[i].odds = bets[i].odds + odds;
    }
    
    [self  displayFromOpaqueAncestor:&bounds :1 :YES];
    return self;
}

- getBet:(int *)bet odds:(int *)odds forPlayer:(int)playerNum
{
    *bet = bets[playerNum].amount;
    *odds = bets[playerNum].odds;
    
    return self;
}

- setBet:(int)bet odds:(int)odds forPlayer:(int)playerNum
{
    bets[playerNum].amount = bet;
    bets[playerNum].odds = odds;
    
    [self  displayFromOpaqueAncestor:&bounds :1 :YES];
    return self;
}

- switchBetOnOffForPlayer:(int) playerNum
{
    if(bets[playerNum].isOn == NO)
		bets[playerNum].isOn = YES;
    else
		bets[playerNum].isOn = NO;
    
    [self  displayFromOpaqueAncestor:&bounds :1 :YES];

    return self;
}

- tradeBetsWith:otherView
{
    int i, bet, odds;
    
    for(i=0; i<MAX_PLAYERS; i++) 
	{
        [otherView getBet:&bet odds:&odds forPlayer:i];
		[otherView setBet:bets[i].amount odds:bets[i].odds forPlayer:i];
		bets[i].amount = bet;
		bets[i].odds = odds;
    }
    
    [self  displayFromOpaqueAncestor:&bounds :1 :YES];
    return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
    int i;
    float width;
    char bet[20];
    int point = [dealer point];
	BOOL doPuckSound = NO;
    
    // draw puck if needed
    if((point==4 && tag==COME_FOUR) || (point==5 && tag==COME_FIVE) || 
	   (point==6 && tag==COME_SIX) || (point==8 && tag==COME_EIGHT) || 
	   (point==9 && tag==COME_NINE) || (point==10 && tag==COME_TEN)) 
	{
		[puckFont set];
		drawChip(NX_WIDTH(&bounds)/2, NX_HEIGHT(&bounds)/2+15, 15.0, NX_BLACK);
		width = [displayFont getWidthOf:"Point"];
		drawText("Point", NX_WIDTH(&bounds)/2-(width/2)-2, NX_HEIGHT(&bounds)/2+12, NX_WHITE);
			
		// play sound if we this is the first time we draw the puck
		if(!drawingPuck) 
		{
			//[dealer playSound:PUCKSOUND];
			doPuckSound = YES;
			drawingPuck = YES;
		}
	} 
	else 
		drawingPuck = NO;
    
    for(i=0; i<MAX_PLAYERS; i++) 
	{
        if(bets[i].amount>0) 
		{
			[displayFont set];
			
			drawChip(chip[i].x, chip[i].y, chip[i].r, chip[i].chipColor);
			
			if(bets[i].isOn)
				sprintf(bet, "%d", bets[i].amount);
			else
				sprintf(bet, "OFF");
			
			width = [displayFont getWidthOf:bet];
			drawText(bet, chip[i].x-(width/2), chip[i].y-4, chip[i].textColor);
			
			if(bets[i].odds > 0) 
			{
				drawChip(chip[i].x, chip[i].y+4, chip[i].r, chip[i].chipColor);
				if(bets[i].isOn)
					sprintf(bet, "%d", bets[i].amount+bets[i].odds);
				else
					sprintf(bet, "OFF");
				width = [displayFont getWidthOf:bet];
				drawText(bet, chip[i].x-(width/2), chip[i].y, chip[i].textColor);
			}
		}
    }
    
	if(doPuckSound) [dealer playSound:PUCKSOUND];
	
	return self;
}

- (BOOL)acceptsFirstMouse
{
    return YES;
}

- mouseDown:(NXEvent *)theEvent
/*
 * player has moused down in our view, indicating that they wish to bet here.
 *
 *   if no key is down -- Bet
 *   if shift is down -- remove bet
 *   if alternate key is down - lay odds
 *   if alternate+shift is down -- remove odds
 *   if control+shift -- turn off/on
 */
{
    id player = [dealer currentPlayer];
    
    if(theEvent->flags & NX_ALTERNATEMASK) 
	{
        if(theEvent->flags & NX_SHIFTMASK) 
	    	[self removeOdds:[player selectedChip] forPlayer:player num:[player tag]];
        else
	    	[self layOdds:[player selectedChip] forPlayer:player num:[player tag]];
    } 
	else if(theEvent->flags & NX_SHIFTMASK) 
	{
		[self removeBet:[player selectedChip] forPlayer:player num:[player tag]];
    } 
	else if(theEvent->flags & NX_CONTROLMASK) 
	{
        [self switchBetOnOffForPlayer:[player tag]];
    } else {
		[self bet:[player selectedChip] forPlayer:player num:[player tag]];
    }
    
    return self;
}

- mouseEntered:(NXEvent *)theEvent
{
    [dealer mouseEnteredView:tag];
	
    return self;
}

- mouseExited:(NXEvent *)theEvent
{
    return self;
}

@end
