
#include <stdio.h>
#include <stdlib.h>

#define VERBOSE 0

#define NUM_GAMES_TO_SIMULATE_FOR_EACH_LINEUP 100
#define NUM_INNINGS_PER_GAME 7
#define OUTS_PER_INNING 3

#define NUM_HIT_TYPES 5
#define HT_BLOOP_SINGLE_IDX 0
#define HT_LINE_DRIVE_SINGLE_IDX 1
#define HT_DOUBLE_IDX 2
#define HT_TRIPLE_IDX 3
#define HT_HOMERUN_IDX 4

typedef struct {
  char* name;
  int battingAverage;
  int hitProfile[NUM_HIT_TYPES];
} player_t;

int simulateInning(player_t* lineup, int numPlayers, int* curBatterIdx) {

  // curBatter is an in-out variable:
  //  - as input it specifies the leadoff hitter 
  //  - as output it specifies the leadoff hitter for the next inning

  int numOuts = 0;
  int manOnFirst = 0;
  int manOnSecond = 0;
  int manOnThird = 0;    
  int runsScored = 0;

  while (numOuts < OUTS_PER_INNING) {
   
    player_t* batter = &lineup[*curBatterIdx];

    // hit or out?
    // draw a random number between 0-1000
    // if it is less than batting average it is a hit
    int hitDraw = ((int) rand()) % 1000;

    if (hitDraw < batter->battingAverage) {

      // a hit!

      // what type of hit?      
      // draw a number between 0-1000
      // then figure out which type of hit it was based on the batter's hitProfile
      int hitTypeDraw = ((int)rand()) % 1000;

      int hitType = -1;          
      int i=0;
      int s=0;
      for (i=0; (i<NUM_HIT_TYPES && hitType < 0); i++) {
	s += batter->hitProfile[i];
	if (hitTypeDraw < s) {
	  hitType = i;
	}
      }
      
      // now simulate the hit
      if (hitType == 0) {
	// bloop single - advances everyone 1 base
	if (VERBOSE) {
	  printf("%s got a bloop single\n", batter->name);
	}

	if (manOnThird) {
	  manOnThird = 0;
	  runsScored++;
	}

	if (manOnSecond) {
	  manOnSecond = 0;
	  manOnThird = 1;
	}

	if (manOnFirst) {
	  manOnFirst = 0;
	  manOnSecond = 1;
	}

	manOnFirst = 1;

      } else if (hitType == 1) {
	// line drive single - advances everyone two bases (except hitter)
	if (VERBOSE) {
	  printf("%s got a line drive single\n", batter->name);
	}

	if (manOnThird) {
	  manOnThird = 0;
	  runsScored++;
	}

	if (manOnSecond) {
	  manOnSecond = 0;
	  runsScored++;
	}

	if (manOnFirst) {
	  manOnFirst = 0;
	  manOnThird = 1;
	}

	manOnFirst = 1;

      } else if (hitType == 2) {
	// double - advances everyone two bases
	if (VERBOSE) {
	  printf("%s got a double\n", batter->name);
	}

	if (manOnThird) {
	  manOnThird = 0;
	  runsScored++;
	}

	if (manOnSecond) {
	  manOnSecond = 0;
	  runsScored++;
	}

	if (manOnFirst) {
	  manOnFirst = 0;
	  manOnThird = 1;
	}

	manOnSecond = 1;

      } else if (hitType == 3) {
	// triple - everyone scores but the hitter
	if (VERBOSE) {
	  printf("%s got a triple\n", batter->name);
	}

	if (manOnThird) {
	  manOnThird = 0;
	  runsScored++;
	}

	if (manOnSecond) {
	  manOnSecond = 0;
	  runsScored++;
	}

	if (manOnFirst) {
	  manOnFirst = 0;
	  runsScored++;
	}
	
	manOnThird = 1;

      } else if (hitType == 4) {
	// homerun - everyone scores!
	if (VERBOSE) {
	  printf("%s got a homerun\n", batter->name);
	}

	if (manOnThird) {
	  manOnThird = 0;
	  runsScored++;
	}

	if (manOnSecond) {
	  manOnSecond = 0;
	  runsScored++;
	}

	if (manOnFirst) {
	  manOnFirst = 0;
	  runsScored++;
	}
	
	runsScored++;

      } else {
	printf("Unknown hit type!\n");
	exit(-1);
      }
 
    } else {
      // an out!
      if (VERBOSE) {
	printf("%s got out\n", batter->name);
      }
      numOuts++;
    }
    
    // advance to the next batter 
    (*curBatterIdx)++;
    if (*curBatterIdx >= numPlayers) {
      *curBatterIdx = 0;
    }
  }

  return runsScored;
}

int simulateGame(player_t *lineup, int numPlayers) {

  int gameRuns = 0;
  int curBatter = 0;

  int i;
  for (i=0; i<NUM_INNINGS_PER_GAME; i++) {
    int inningRuns = simulateInning(lineup, numPlayers, &curBatter);  
    gameRuns += inningRuns;
    if (VERBOSE) {
      printf("inning[%i]: scored %i runs (nextLeadOff=%s)\n", i, inningRuns, lineup[curBatter].name);
    }
  }
  
  if (VERBOSE) {
    printf("scored %i runs this game\n", gameRuns);
  }

  return gameRuns;
}

float simulateNGames(player_t *lineup, int numPlayers) {

  // total runs over all games
  int totalRuns = 0;
  int i;
  for (i=0; i<NUM_GAMES_TO_SIMULATE_FOR_EACH_LINEUP; i++) {
    int gameRuns = simulateGame(lineup, numPlayers);
    totalRuns += gameRuns;
  }

  // return avg runs per game
  return (float)totalRuns / (float)NUM_GAMES_TO_SIMULATE_FOR_EACH_LINEUP;
}

void simulateLineup(player_t *lineup, int numPlayers) { 

  // simulate n games with this lineup and print out the average runs scored per game
  float avgRuns = simulateNGames(lineup, numPlayers);

  printf("%f,", avgRuns);

  int i;
  for (i=0; i<numPlayers; i++) {
    printf("%s(%i),", lineup[i].name, lineup[i].battingAverage);
  }
  printf("\n");
}

void simulateAllPossibleLineups(player_t* roster, int numPlayers, player_t* lineup, int* playerAddedToLineup, int pos) {

  // recursive function used to produces all possible lineups 
  // for each lineup it finds it calls simulateLineup

  // lineup is the current lineup we are working with / creating
  // playerAddedToLineup indicates whether each player has been added to the current lineup
  // pos is the current pos we are assigning to in the current lineup

  if (pos == numPlayers) {
    // found a lineup - run the simulation
    simulateLineup(lineup, numPlayers);
    return;
  }

  int i;
  for (i=0; i<numPlayers; i++) {
    
    // find next player not yet in the lineup or tried in this position 

    if (!playerAddedToLineup[i]) {
	
      // add this player to the lineup
      lineup[pos] = roster[i];
      playerAddedToLineup[i] = 1;
      
      // create rest of lineup 
      simulateAllPossibleLineups(roster, numPlayers, lineup, playerAddedToLineup, pos+1);
      
      // remove this player and try someone else
      playerAddedToLineup[i] = 0;
    }
  }
}

void runSimulation(player_t* roster, int numPlayers) {

  // set up the recursive simulateAllPossibleLineups call

  // create and initializing the working space it needs 
  player_t *lineup = malloc(sizeof(player_t) * numPlayers);  
  int *playerAddedToLineup = malloc(sizeof(int) * numPlayers);
  
  int i;
  for (i=0; i<numPlayers; i++) {
    lineup[i] = roster[i];
    playerAddedToLineup[i]=0;
  }
    
  // then make the first call
  simulateAllPossibleLineups(roster, numPlayers, lineup, playerAddedToLineup, 0);

  // free the space
  free(lineup);
  free(playerAddedToLineup);
}

void validateRange(int value, char* description, int min, int max) {
  if (value < min || value > max) {
    printf("%s value (%i) not in range [%i, %i]\n", description, value, min, max);
    exit(-1);
  }
}

void validateRosterInput(player_t *roster, int numPlayers) {

  int i;
  for (i=0; i<numPlayers; i++) {

    if (VERBOSE) {
      printf("player=%s\n", roster[i].name);
      printf("\tbattingAverage=%i\n", roster[i].battingAverage);
      printf("\tbloopSinglesPerHit=%i\n", roster[i].hitProfile[HT_BLOOP_SINGLE_IDX]);    
      printf("\tlineDriveSinglesPerHit=%i\n", roster[i].hitProfile[HT_LINE_DRIVE_SINGLE_IDX]);    
      printf("\tdoublesPerHit=%i\n", roster[i].hitProfile[HT_DOUBLE_IDX]);
      printf("\ttriplesPerHit=%i\n", roster[i].hitProfile[HT_TRIPLE_IDX]);
      printf("\thrsPerHit=%i\n", roster[i].hitProfile[HT_HOMERUN_IDX]);
    }

    validateRange(roster[i].battingAverage, "batting average", 0, 1000);
    validateRange(roster[i].hitProfile[HT_BLOOP_SINGLE_IDX], "bloop singles per hit", 0, 1000);
    validateRange(roster[i].hitProfile[HT_LINE_DRIVE_SINGLE_IDX], "line drive singles per hit", 0, 1000);
    validateRange(roster[i].hitProfile[HT_DOUBLE_IDX], "doubles per hit", 0, 1000);
    validateRange(roster[i].hitProfile[HT_TRIPLE_IDX], "triples per hit", 0, 1000);
    validateRange(roster[i].hitProfile[HT_HOMERUN_IDX], "homeruns per hit", 0, 1000);

    int totalHitTypesPerHit = 0;
    int j=0;
    for (j=0; j<NUM_HIT_TYPES; j++) {
      totalHitTypesPerHit += roster[i].hitProfile[j];
    }

    validateRange(totalHitTypesPerHit, "total hit types per hit", 1000, 1000);
  }
}

int main(char** argv, int argc) {

  player_t powerHitter = {
    "power",
    600, // .600 batting average    
    150, // 15% bloop singles
    300, // 30% line drive singles
    300, // 30% doubles
    50,  //  5% tripes
    200  // 20% homeruns
  };
  
  player_t hitterHitter = {
    "hitter",
    500, // .500 batting average    
    300, // 30% bloop singles
    400, // 40% line drives
    300, // 30% doubles
    0,   // 0 triples
    0,   // 0 homeruns 
  };
  
  player_t avgHitter = {
    "avg",
    400, // .400 batting average
    400, // 40% bloop singles
    400, // 40% line drives
    200, // 20% doubles
    0,   // 0 triples
    0,   // 0 homeruns
  };

  player_t belavgHitter = {
    "belavg",
    300, // .300 batting average
    800, // 80% bloop singles
    200, // 20% line drives
    0,   // 0% doubles
    0,   // 0 triples
    0    // 0 homeruns     
  };

  player_t weakHitter = {
    "weak",
    200,  // .200 batting average
    1000, // 100% bloop singles
    0,    // 0% line drives
    0,    // 0% doubles
    0,    // 0% triples
    0,    // 0% homeruns    
  };

  // configure roster of players
  player_t roster[] = {
    powerHitter,
    powerHitter,
    hitterHitter,
    avgHitter,
    avgHitter,
    belavgHitter,
    belavgHitter,
    belavgHitter,
    weakHitter,
    weakHitter,
    weakHitter,
  };

  // run the simulation
  int numPlayers = (sizeof(roster) / sizeof(player_t));      
  validateRosterInput(roster, numPlayers);
  runSimulation(roster, numPlayers);
}

