#import "Referee.h"
#import "RefereeListener.h"
#import "RefereeSpeaker.h"
#import <appkit/Form.h>
#import <appkit/Matrix.h>
#import <appkit/TextField.h>
#import <appkit/Panel.h>
#import <appkit/Application.h>
#import <appkit/PopUpList.h>
#import <appkit/SavePanel.h>
#import <appkit/Cell.h>
#import <strings.h>
#import <time.h>
#import <stdio.h>

@implementation Referee 

-init;
{
    [super init];
    numberCheckedIn = 0;
    [isActive setEnabled:YES];	/* list of active players */
    roller = [[Random alloc] init];	/* random number generator */
    [self saveRequest:self];	/* open files for data */
    inPhase1 = YES;		/* start out in phase 1 */
    return self;
}

- closeFiles;
{
    fclose(fdataptr);
    fclose(fsubjectptr);
    return self;
}

- saveRequest:sender		/* opens files for saving data */
{
    id                  saveReq;
    char               *loc;
    const char         *fileName = "                   zenda.data";

    saveReq = [SavePanel new];
    [saveReq runModalForDirectory:"/tmp" file:"zenda.data"];
    fileName = [saveReq filename];
    fdataptr = fopen(fileName, "w");
    loc = index(fileName, '.');	/* finds period in name */
    *loc++ = '\0';		/* puts end of string after period */
    strcat(fileName, ".subject");	/* concatenates "sub" to end of
					 * string */
    fsubjectptr = fopen(fileName, "w");
    return self;
}

- startGameBetween:(int)numA And:(int)numB;
{
    char               *nameA, *nameB;
    char               *hostA, *hostB;
    int                 speakerResult, rpushA, rpullA, rpushB, rpullB;
    port_t              playerAPort, playerBPort;

    nameA = playerName[numA];
    nameB = playerName[numB];
    hostA = hostName[numA];
    hostB = hostName[numB];

    [opponentMatrix setIntValue:numB at:numA];	/* update control panel */
    [opponentMatrix setIntValue:numA at:numB];

    if (round % 2) {		/* switch cards every round */
	rpullA = [cardMatrix intValueAt:0];
	rpushA = [cardMatrix intValueAt:1];
	rpullB = [cardMatrix intValueAt:2];
	rpushB = [cardMatrix intValueAt:3];
    } else {
	rpullA = [cardMatrix intValueAt:2];
	rpushA = [cardMatrix intValueAt:3];
	rpullB = [cardMatrix intValueAt:0];
	rpushB = [cardMatrix intValueAt:1];
    }

    printf("Round %d: player %d with (%d,%d) vs player %d with (%d,%d).\n", round, numA, rpullA, rpushA, numB, rpullB, rpushB);

/* send message to each player telling them to start game */

    playerAPort = [self getPortFor:nameA atHost:hostA];
    if (isRandom)
	speakerResult = [[NXApp appSpeaker]
			 remotePlayRandomGameWith:nameB atHost:hostB
			 myPullCard:rpullA myPushCard:rpushA
			 hisPullCard:rpullB hisPushCard:rpushB];
    else
	speakerResult = [[NXApp appSpeaker]
			 remotePlayGameWith:nameB atHost:hostB
			 myPullCard:rpullA myPushCard:rpushA
			 hisPullCard:rpullB hisPushCard:rpushB];
    if (speakerResult != 0)
	[self speakerError:nameA in:"startGameBetween" error:speakerResult];

    playerBPort = [self getPortFor:nameB atHost:hostB];
    if (isRandom)
	speakerResult = [[NXApp appSpeaker]
			 remotePlayRandomGameWith:nameA atHost:hostA
			 myPullCard:rpullB myPushCard:rpushB
			 hisPullCard:rpullA hisPushCard:rpushA];
    else
	speakerResult = [[NXApp appSpeaker]
			 remotePlayGameWith:nameA atHost:hostA
			 myPullCard:rpullB myPushCard:rpushB
			 hisPullCard:rpullA hisPushCard:rpushA];
    if (speakerResult != 0)
	[self speakerError:nameB in:"startGameBetween" error:speakerResult];

    numberPlayersDone = 0;	/* game just started, so no one is done */
    return self;
    
}


- reset:sender;
{
    int                 playerNum;
    char               *name;
    char               *host;
    int                 speakerResult;
    port_t              playerPort;


    fprintf(fdataptr, "Reset------------\n");

/* tell players to reset winnings to zero */
    for (playerNum = 1; playerNum <= numberCheckedIn; playerNum++) {
	name = playerName[playerNum];
	host = hostName[playerNum];
	playerPort = [self getPortFor:name atHost:host];
	speakerResult = [[NXApp appSpeaker] remotePlayerReset];
	if (speakerResult != 0)
	    [self speakerError:name in:"reset" error:speakerResult];
    }
/* update control panel */
    for (playerNum = 1; playerNum <= numberCheckedIn; playerNum++) {
	[roundWinMatrix setIntValue:0 at:playerNum];
	[totalWinMatrix setIntValue:0 at:playerNum];
	[paymentMatrix setIntValue:0 at:playerNum];
	[playMatrix setIntValue:0 at:playerNum];
    }
    [currentMatrix setIntValue:0 at:0];
    [currentMatrix setIntValue:0 at:1];
    [self startGame:self];
    return self;
}

- startGame:sender;
{
    round = 0;
    isRandom = NO;
    [self restartGame:self];
    return self;
}

- startRandomGame:sender;
{
    round = 0;
    isRandom = YES;
    [self restartGame:self];
    return self;
}

- restartGame:sender;
{
    roundsInPhase1 = [durationMatrix intValueAt:0];
    totalRounds = roundsInPhase1 + [durationMatrix intValueAt:1];
    startTime = time((long *)0);/* get starting time for record keeping */
    [self roundIsDone:self];	/* start play */
    return self;
} 



- checkInPlayer:(char *)name atHost:(char *)host;	/* add player to control
							 * panel */
{
    int                 speakerResult;
    port_t              playerPort;

    numberCheckedIn++;
    [isActive selectCellAt:numberCheckedIn :0];
    playerName[numberCheckedIn] = NXCopyStringBuffer(name);
    hostName[numberCheckedIn] = NXCopyStringBuffer(host);
    [hostMatrix setStringValue:NXCopyStringBuffer(host) at :numberCheckedIn];
    playerPort = [self getPortFor:name atHost:host];
    speakerResult = [[NXApp appSpeaker] remotePlayerNumberIs:numberCheckedIn];
    if (speakerResult != 0)
	[self speakerError:name in:"checkInPlayer" error:speakerResult];
    return self;
}

- startPhaseTwo:sender
{
    int                 n;
    char               *name;
    char               *host;
    int                 speakerResult;
    port_t              playerPort;

    for (n = 1; n <= MAXPLAYERS; n++) {
	if ([[isActive cellAt:n :0] state]) {
	    name = playerName[n];
	    host = hostName[n];
	    playerPort = [self getPortFor:name atHost:host];
	    speakerResult = [[NXApp appSpeaker] remoteStartPhaseTwo];
	    if (speakerResult != 0)
		[self speakerError:name in:"startPhaseTwo" error:speakerResult];
	}
    }

    return self;
} 


- updateOpponents
{
    int                 i, n, numberActive, activePlayer[MAXPLAYERS], temp, nShifts;


/*which players are active? */
    numberActive = 0;
    for (n = 1; n <= MAXPLAYERS; n++) {
	if ([[isActive cellAt:n :0] state]) {
	    numberActive++;
	    activePlayer[numberActive] = n;
	} else {
	    [payLight setState:NO at:n :0];
	    [choiceLight setState:NO at:n :0];
	    [doneLight setState:NO at:n :0];
	}
    };
/*first half are type A; second half are type B */
/*(if odd number of players, one in middle sits out)*/
    numberPlayerA = numberActive / 2;
    for (n = 1; n <= numberPlayerA; n++) {
	playerA[n] = activePlayer[n];
	playerB[n] = activePlayer[numberActive + 1 - n];
    }

/*permute the PlayerA list randomly */
    nShifts = [roller randMin:1 max:6];
    for (n = 1; n <= nShifts; n++) {
	temp = playerA[1];
	for (i = 1; i < numberPlayerA; i++)
	    playerA[i] = playerA[i + 1];
	playerA[numberPlayerA] = temp;
    }
    return self;
}

- roundIsDone:sender
{
    int                 n, thisRound, total;
    float               chipValue;

/* update control panel */
    if (inPhase1) {
	[currentMatrix setIntValue:round at:0];
    } else {
	[currentMatrix setIntValue:round-roundsInPhase1 at:1];
    }
    thisRound = total = 0;
    for (n = 1; n <= MAXPLAYERS; n++) {
	thisRound = thisRound + [roundWinMatrix intValueAt:n];
	total = total + [totalWinMatrix intValueAt:n];
    }
    [totalPayBox setIntValue:total];
    [thisRoundBox setIntValue:thisRound];
    chipValue = (float)[chipValueBox intValue] * 0.01;
    [thisRoundDollar setFloatValue:thisRound * chipValue];
    [totalPayDollar setFloatValue:total * chipValue];

    if (round == roundsInPhase1) {
	inPhase1 = NO;
	[self startPhaseTwo:self];
    }
    if (round == totalRounds) {
	[self gameIsOver:self];
    }
    round++;
/*    [self writePayoffs]; */

/* start a new game */
    [self updateOpponents];
    for (n = 1; n <= numberPlayerA; n++) {
	[self startGameBetween:playerA[n] And:playerB[n]];
	[choiceLight setState:YES at:playerA[n] :0];
	[choiceLight setState:YES at:playerB[n] :0];
	[doneLight setState:YES at:playerA[n] :0];
	[doneLight setState:YES at:playerB[n] :0];
	if (!inPhase1) {
	    [payLight setState:YES at:playerA[n] :0];
	    [payLight setState:YES at:playerB[n] :0];
	}
    }
    return self;
}

- reportGameInfoFrom:(int)playerNum side1:(int)mySide side2:(int)hisSide
    push1:(int)myPushCard pull1:(int)myPullCard
    push2:(int)hisPushCard pull2:(int)hisPullCard
    choice1:(int)myChoice choice2:(int)hisChoice
    payoff:(int)myPayoff;
    
{
    int                 winSoFar, myChoiceCard, hisChoiceCard, opponentNum;
    int                 hisPayoff, totalPayoff;
    long                roundTime;

    [doneLight setState:NO at:playerNum :0];

    if (myChoice == 1)
	myChoiceCard = myPushCard;
    else
	myChoiceCard = myPullCard;
    if (hisChoice == 1)
	hisChoiceCard = hisPushCard;
    else
	hisChoiceCard = hisPullCard;

    hisPayoff = 0;
    if (myChoice == 1 && hisChoice == 1)
	hisPayoff = myPushCard + mySide - hisSide;
    if (myChoice == 1 && hisChoice == 0)
	hisPayoff = myPushCard + hisPullCard - hisSide;
    if (myChoice == 0 && hisChoice == 1)
	hisPayoff = mySide;
    if (myChoice == 0 && hisChoice == 0)
	hisPayoff = hisPullCard;

    totalPayoff = myPayoff + hisPayoff;
    opponentNum = [opponentMatrix intValueAt:playerNum];
    roundTime = time((long *)0) - startTime;
    printf("%4d,%5d,%4d,%4d,%4d,%4d,%4d,%4d,%4d,%4d,%4d,%4d,%4d,%4d,%4d,%4d,%4d\n",
	   round, roundTime, playerNum, opponentNum, mySide, hisSide,
	   myPushCard, myPullCard,
	   hisPushCard, hisPullCard, myChoice, hisChoice,
	   myChoiceCard, hisChoiceCard, myPayoff, hisPayoff, totalPayoff);
    fprintf(fdataptr, "%4d %5d %4d %4d %4d %4d %4d %4d %4d %4d %4d %4d %4d %4d %4d %4d %4d\n",
	    round, roundTime, playerNum, opponentNum, mySide, hisSide,
	    myPushCard, myPullCard,
	    hisPushCard, hisPullCard, myChoice, hisChoice,
	    myChoiceCard, hisChoiceCard, myPayoff, hisPayoff, totalPayoff);

/* update control panel */
    [roundWinMatrix setIntValue:myPayoff at:playerNum];
    winSoFar = [totalWinMatrix intValueAt:playerNum] + myPayoff;
    [totalWinMatrix setIntValue:winSoFar at:playerNum];

    numberPlayersDone++;
    if (numberPlayersDone >= 2 * numberPlayerA) {
	[self roundIsDone:self];
    }
    return self;
}

- writePayoffs;
{
    int                 num, chips, lumpSum;
    float               dollars, chipValue;

    lumpSum = [lumpSumBox intValue];
    for (num = 1; num <= numberCheckedIn; num++) {
	chips = [totalWinMatrix intValueAt:num];
	chipValue = (float)[chipValueBox intValue] * 0.01;
	dollars = (float)chips *chipValue + lumpSum;

	printf("%d. %s, Winnings=%d chips (%.2f dollars), %s, %s.\n",
	       num,
	       subjectName[num],
	       chips,
	       dollars,
	       subjectPhone[num],
	       subjectSS[num]);
	fprintf(fsubjectptr,
		"%d. %s, Winnings=%d chips (%.2f dollars), %s, %s.\n\n",
		num,
		subjectName[num],
		chips,
		dollars,
		subjectPhone[num],
		subjectSS[num]);
	fprintf(fsubjectptr,
		"I have received the above amount__________________________________\n\n\n");
    }
    return self;
}



- gameIsOver:sender;
{
    fprintf(fsubjectptr, "-----------final payoffs----------\n");
    [self writePayoffs];
    [self tellPlayersGameIsOver];
    [self closeFiles];
    NXRunAlertPanel("Game over...",
		    "Click here to quit.\n", "Quit", 0, 0, NULL);
    exit(0);
    return self;
}

- tellPlayersGameIsOver;
{
    int                 n;
    char               *name;
    char               *host;
    int                 speakerResult;
    port_t              playerPort;

    for (n = 1; n <= numberCheckedIn; n++) {
	name = playerName[n];
	host = hostName[n];
	playerPort = [self getPortFor:name atHost:host];
	speakerResult = [[NXApp appSpeaker]
			 remoteGameIsOver];
	if (speakerResult != 0)
	    [self speakerError:name in:"tellPlayersGameIsOver" error:speakerResult];
    }
    return self;
} 


- reportPaymentBy:(int)playerNum amount:(int)sidePayment

{
    [paymentMatrix setIntValue:sidePayment at:playerNum];
    [payLight setState:NO at:playerNum :0];
    return self;
}

- reportChoiceBy:(int)playerNum cardPlayed:(int)hisChoice
{
    [playMatrix setIntValue:hisChoice at:playerNum];
    [choiceLight setState:NO at:playerNum :0];
    return self;
}

- reportPlayerInfo:(int)num
    Name:(char *)str1
    Phone:(char *)str2
    SS:(char *)str3;
{
    printf("Number=%d, Name=%s, Phone=%s, SS=%s.\n", num,
	   str1, str2, str3);
    subjectName[num] = NXCopyStringBuffer(str1);
    subjectPhone[num] = NXCopyStringBuffer(str2);
    subjectSS[num] = NXCopyStringBuffer(str3);
    return self;
}

- nudgePlayer:sender;
{
    char               *name;
    char               *host;
    int                 speakerResult, n;
    port_t              playerPort;

    for (n = 1; n <= numberCheckedIn; n++) {
	name = playerName[n];
	host = hostName[n];
	if ([[isActive cellAt:n :0] state] && [[doneLight cellAt:n :0] state]) {
	    printf("Nudging player %d.\n", n);
	    playerPort = [self getPortFor:name atHost:host];
	    speakerResult = [[NXApp appSpeaker] remoteNudge];
	    if (speakerResult != 0)
		[self speakerError:name in:"nudgePlayer" error:speakerResult];
	}
    }
    return self;
}

/*--------------------------------------------------------------*/
/* The following is boilerplate that can be inserted in any app */
/*--------------------------------------------------------------*/

- (port_t)getPortFor:(char *)appName 
 atHost:(char *)appHost
{
    port_t              appPort;

    appPort = NXPortFromName(appName, appHost);
    if (appPort == PORT_NULL)
	NXRunAlertPanel("Define...", "%s Port was not found.\n", 
			"OK", 0, 0, appName);
    [[NXApp appSpeaker] setSendPort:appPort];
    return appPort;
} 


- speakerError:(char *)receiverName in:(char *)methodName error:(int)num 
{
    printf("Error...message to %s in method %s generated error code %d.\n",
	   receiverName, methodName, num);
    return self;
} 

@end 

