import java.io.*;
import java.util.Scanner;
import java.util.StringTokenizer;

/*
 * The PlayImplementor Class contains the main method for the unity of the game-playing 
 * program. It also contains static methods which coordinate the game and player classes 
 * in order to pad the details of the workings of each individual class.
 */

public class PlayImplementor {
  
  public static void main(String args[]){
    Game medlee = null;
    try{
      medlee = PlayImplementor.readInGame();
    }
    catch(FileNotFoundException e){
      System.out.println("Specify parameters in file.");
    }
    catch(IllegalStateException f){
      System.out.println("Improperly formatted parameters.");
    }
    
    Qlearner[] opps = new Qlearner[2];
    try{
      opps[0= makeQfor(medlee);
      opps[1= makeQfor(medlee);
    }
    catch(IllegalStateException e){
      System.out.println("An error occurred.");
    }
    
    double[] results = playOut(medlee, opps);
    System.out.println("The first player's score: " + results[0]);
  }
  
  /*
   * The setUp method creates and returns a game by manually requesting the relevant 
   * game parameters from the user. Can create only cyclic (Stage games are played in order 
   * of state number and repeat) or random state (each stage game is played with equal 
   * probability on every round) configurations.
   
   * To input the information, simply type the answers to the questions about the game into 
   * the command prompt as they are asked. For questions requesting an integer, type the 
   * number. For questions requesting a boolean, type "yes" or "no".
   */
  
  public static Game setUp(){
    
    //Read-in variables
    String statesIn;
    String playersIn;
    String actionsIn;
    String roundsIn;
    String random;
    String cyclic;
    String nextPayoff;
    
    //Translated inputs and their computations
    int states;
    int players;
    int actions;
    int rounds;
    int outcomes;
    
    //User-generated array parameters
    int[][][] payoffs;
    double[][][] gamePaths;
    
    //Prompt and read in integer parameters
    BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
    System.out.println("How many states does the game have?");
    try{
      statesIn = r.readLine();
    }
    catch(IOException e){
      System.out.println("Entry error");
      return null;
    }
    states = Integer.parseInt(statesIn);
    if(states < 1){
      System.out.println("Invalid Entry");
      return null;
    }
    
    System.out.println("How many players does the game have?");
    try{
      playersIn = r.readLine();
    }
    catch(IOException e){
      System.out.println("Entry error");
      return null;
    }
    players = Integer.parseInt(playersIn);
    if(players < 1){
      System.out.println("Invalid Entry");
      return null;
    }
    
    System.out.println("How many actions does each player have?");
    try{
      actionsIn = r.readLine();
    }
    catch(IOException e){
      System.out.println("Entry error");
      return null;
    }
    actions = Integer.parseInt(actionsIn);
    if(actions < 2){
      System.out.println("Invalid Entry");
      return null;
    }
    
    System.out.println("How many rounds will the game last?");
    try{
      roundsIn = r.readLine();
    }
    catch(IOException e){
      System.out.println("Entry error");
      return null;
    }
    rounds = Integer.parseInt(roundsIn);
    if(rounds < 1){
      System.out.println("Invalid Entry");
      return null;
    }
    
    //Repetitively read in payoffs
    outcomes = (int)Math.round(Math.pow((double)actions, (double)players));
    //System.out.println("outcomes: " + outcomes);
    payoffs = new int[states][outcomes][players];
    for(int i = 0; i < states; i++){
      for(int j = 0; j < players; j++){
        for(int k = 0; k < outcomes; k++){
          System.out.println("What is the payoff in state " + i + " for player " + j + " of outcome " + k + "?");
          try{
            nextPayoff = r.readLine();
          }
          catch(IOException e){
            System.out.println("Entry error");
            return null;
          }
          payoffs[i][k][j= Integer.parseInt(nextPayoff);
        }
      }
    }
    
    //Determines state arrangement and builds state graph accordingly
    System.out.println("Are the states cyclical? (\"yes\" or \"no\")");
    try{
      cyclic = r.readLine();
    }
    catch(IOException e){
      System.out.println("Entry error");
      return null;
    }
    
    gamePaths = new double[states][outcomes][states];
    if(cyclic.equals("yes")){
      for(int i = 0; i < states; i++){
        for(int j = 0; j < outcomes; j++){
          for(int k = 0; k < states; k++){
            if(k == (i + 1)%states){
              gamePaths[i][j][k1.0;
            }
            else{
              gamePaths[i][j][k0.0;
            }
          }
        }
      }
    }
    else{
      System.out.println("Are the states random? (\"yes\" or \"no\")");
      try{
        random = r.readLine();
      }
      catch(IOException e){
        System.out.println("Entry error");
        return null;
      }
      if(random.equals("yes")){
        for(int i = 0; i < states; i++){
          for(int j = 0; j < outcomes; j++){
            for(int k = 0; k < states; k++){
              gamePaths[i][j][k1/states;
            }
          }
        }
      }
      else{
        System.out.println("Sorry, that's all you can do manually. Try reading in your game from text.");
        return null;
      }
    }
    
    //Creates and hands back the desired game
    return new Game(states, players, actions, rounds, payoffs, gamePaths);
  }
  
  /*
   * ReadInGame method creates and hands back a game created using parameters specified in 
   * a text file called "ReadGame".
   
   * The format of the text file is the number of states, players, actions, and rounds on 
   * different lines followed by, for each state, the utility matrices for each player with 
   * the integer utilities separated by commas and then for each state, a group of 
   * probabilities for each outcome ordered lexicographically such that there is a 
   * probability for each state and they sum to one.
   */
  
  public static Game readInGame()
  throws FileNotFoundException, IllegalStateException{
    
    //Variables to read in from file
    int states;
    int players;
    int actions;
    int rounds;
    int[][][] payoffs;
    double[][][] gamePaths;
    
    //Variables to compute
    int outcomes;
    
    //File specified to be read by scanner
    File gameFile = new File("ReadGame");
    Scanner instr = new Scanner(gameFile);
    
    //For retrieving numbers from comma lists
    StringTokenizer utils;
    StringTokenizer probs;
    double nextProb;
    double sumToOne;
    
    //Read in integer variables
    if(!instr.hasNext()){
      throw new IllegalStateException();
    }
    try{
      states = Integer.parseInt(instr.next());
    }
    catch(NumberFormatException e){
      throw new IllegalStateException();
    }
    
    if(!instr.hasNext()){
      throw new IllegalStateException();
    }
    try{
      players = Integer.parseInt(instr.next());
    }
    catch(NumberFormatException e){
      throw new IllegalStateException();
    }
    
    if(!instr.hasNext()){
      throw new IllegalStateException();
    }
    try{
      actions = Integer.parseInt(instr.next());
    }
    catch(NumberFormatException e){
      throw new IllegalStateException();
    }

    if(!instr.hasNext()){
      throw new IllegalStateException();
    }
    try{
      rounds = Integer.parseInt(instr.next());
    }
    catch(NumberFormatException e){
      throw new IllegalStateException();
    }
    
    //System.out.println("States: " + states + "  Players: " + players + "  Actions: " + actions + "  Rounds: " + rounds);
    
    //Compute number of outcomes and read in all utilities
    outcomes = (int)Math.round(Math.pow((double)actions, (double)players));
    payoffs = new int[states][players][outcomes];
    
    for(int i = 0; i < states; i++){
      for(int j = 0; j < players; j++){
        if(!instr.hasNext()){
          throw new IllegalStateException();
        }
        utils = new StringTokenizer(instr.next()",");
        if(utils.countTokens() != outcomes){
          throw new IllegalStateException();
        }
        for(int k = 0; k < outcomes; k++){
          try{
            payoffs[i][j][k= Integer.parseInt(utils.nextToken());
          }
          catch(NumberFormatException e){
            throw new IllegalStateException();
          }
        }
      }
    }
    
    //Read in all probabilities and check that each list sums to one.
    gamePaths = new double[states][outcomes][states];
    
    for(int i = 0; i < states; i++){
      for(int j = 0; j < outcomes; j++){
        if(!instr.hasNext()){
          throw new IllegalStateException();
        }
        probs = new StringTokenizer(instr.next()",");
        if(probs.countTokens() != states){
          throw new IllegalStateException();
        }
        sumToOne = 0.0;
        for(int k = 0; k < states; k++){
          try{
            nextProb = Double.parseDouble(probs.nextToken());
          }
          catch(NumberFormatException e){
            throw new IllegalStateException();
          }
          gamePaths[i][j][k= nextProb;
          sumToOne += nextProb;
        }
        if(sumToOne != 1.0){
          throw new IllegalStateException();
        }
      }
    }
    
    //Hand back game
    return new Game(states, players, actions, rounds, payoffs, gamePaths);
  }
  
  /*
   * The makeQ method creates and returns a player of type Qlearner with the specified 
   * parameters that is able to play the specified game. Throws an exception if the game 
   * not new or is null.
   */
  
  public static Qlearner makeQ(Game system, double gamma, double alphadecay, double epsilon)
  throws IllegalStateException{
    //Error check
    if(system == null){
      throw new IllegalStateException();
    }
    //Prompts the game for its parameters
    int[] rules = system.getSpecs();
    if(rules[4!= || rules[5!= 1){
      throw new IllegalStateException();
    }
    int states = rules[0];
    int actions = rules[2];
    
    //Returns new Qlearner with copied parameters
    return new Qlearner(actions, states, gamma, alphadecay, epsilon, system.getPayouts());
  }
  
  /*
   * The makeQfor method creates and returns a Qlearner with the value of the gamma 
   * discount factor, the alpha decay rate, and the epsilon exploration frequency set as 
   * .999, .999, and .1 respectively. Throws exception if input is null.
   */
  
  public static Qlearner makeQfor(Game system)
  throws IllegalStateException{
    try{
      return PlayImplementor.makeQ(system, .999.999.1);
    }
    catch(IllegalStateException e){
      throw new IllegalStateException();
    }
  }
  
  /*
   * The PlayOut method takes as input a new game and an array of new players to play 
   * that game. It allows the game and player classes to repetitively coordinate such that 
   * the decisions of the players are read as moves of the game. Returns an array of the 
   * total reward accumulated by each player (can be interpreted as average per-stage 
   * reward in comparison of players). Throws an exception if the inputs are in error.
   */
  
  public static double[] playOut(Game table, Player[] dudes)
  throws IllegalStateException{
    //Checks for compatibility of game and players
    if(table == null || !table.isNew()){
      throw new IllegalStateException();
    }
    if(dudes == null){
      throw new IllegalStateException();
    }
    int qNum = dudes.length;
    int chairs = table.getNumPlayers();
    if(qNum != chairs){
      throw new IllegalStateException();
    }
    for(int i = 0; i < qNum; i++){
      if(!dudes[i].isNew()){
        throw new IllegalStateException();
      }
    }
    
    //Holds current situation and completion information
    int gameState = 0;
    
    //Player/Game coordination arrays
    int[] moves = new int[qNum];
    int[] cookies = new int[qNum];
    
    //Initializes totals to be outputted
    double[] totals = new double[qNum];
    for(int i = 0; i < qNum; i++){
      totals[i0;
    }
    
    //Repetitively prompts a move from the players and the resulting change in the game as it prints the moves of each player and the rewards they each receive. Stops the loop when the game has returned a state of -1 meaning the game has finished.
    int moveNum = 0;
    
    do{
      System.out.println("Move Number " + moveNum);
      System.out.println("Game state: " + gameState);
      for(int i = 0; i < qNum; i++){
        moves[i= dudes[i].move();
        System.out.println("The move of player " + i + " is " + moves[i]);
      }
      cookies = table.getRewards(moves);
      for(int i = 0; i < qNum; i++){
        System.out.println("The reward for player " + i + " is " + cookies[i]);
      }
      for(int i = 0; i < qNum; i++){
        totals[i+= cookies[i];
      }
      gameState = table.changeState(moves);
      if(gameState != -1){
        for(int i = 0; i < qNum; i++){
          dudes[i].update(moves[i], cookies[i], gameState);
        }
      }
      moveNum++;
    }
    while(gameState != -1);
    
    return totals;
  }
}