// -----------------------------------------------------------------
// Solution for MasterMind game (605.481 Summer 2002)
//
// Uses just slightly modified version of ThreadedEchoServer.java
//
// 7/3/02 Paul McNamee
// -----------------------------------------------------------------

import java.util.*;
import java.net.*;
import java.io.*;

class MMGame extends Vector {
  private static String colors = "RGBWYO";
  String code = null;

  public static char randomChar () {
    int value = (int) (Math.random() * 6) ;
    return colors.charAt(value);
  }

  public MMGame() {
    // Pick random code
    String code = "" + randomChar() + randomChar() + randomChar() + randomChar();
    addElement(code);  // to the 0th place
  }

  public String getCode() {
    return (String) elementAt(0);
  }

  public String getGuess(int i) {
    return (String) elementAt(i);
  }

  public void makeGuess(String g) {
    addElement(g);
  }

  public int numGuesses() {
    return size() - 1;
  }
}


// Looks a lot like GuessClient given out in class
class MMClient extends NetworkClient {
  public MMClient(String host, int port) {
    super(host, port);
  }

  protected void handleConnection(Socket client) throws IOException {
    SocketUtil s = new SocketUtil(client);
    PrintStream out = s.getPrintStream();
    DataInputStream in = s.getDataStream();
    String input = in.readLine();
    while ((input != null) && ! input.startsWith("Goodbye")) {
      System.out.println(input);
      String command = askUser();
      out.println(command);
      input = in.readLine();
      
      // If command was History command, read multiple lines.
      if (input.indexOf("turns") != -1) {
        String num = input.substring(0, input.indexOf(" "));
        int nLines = Integer.parseInt(num);
        for(int i=0; i<nLines; i++) {
          System.out.println(input);
          input = in.readLine();
        }
      }
    }
    System.out.println(input);
    client.close();
  }

  BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

  public String askUser() {
    System.out.print("  Enter command: ");
    String str = null;
    try {
      str = reader.readLine();
    } catch (IOException ioe) {}
    return (str);
  }
}


class MMServer extends ThreadedEchoServer implements Runnable {
 public MMServer (int port) {
    super(port, 0);  // The 0 means there is no limit on the number of connections
  }

  // Note, there is no shared data, so we don't need to use synchronization blocks
  //   (Technically, there is the host and port, but we don't access those.)
  public void threadedHandleConnection(Socket server) throws IOException {
    SocketUtil s = new SocketUtil(server);
    DataInputStream in = s.getDataStream();
    PrintStream out = s.getPrintStream();
    String input = "";
    
    // Build some state local to this thread
    MMGame game = new MMGame();

    out.println("Hello (MMServer)");

    while (! input.equals("quit")) {
      input = in.readLine();  // all commands are exactly one line
      System.out.println("Input was <" + input + ">");

      // Do stuff and print a response
      int action = getAction(input);
      String code = game.getCode();
      String guess = "";

      switch (action) {
      case 0:  // Newgame
        game = new MMGame();
        out.println("I have a new code");
        break;
      case 1:  // Cheat
        out.println("The solution is: " + "<" + code + ">");
        break;
      case 2:  // Guess
        if ((input.length() < 10) || (input.indexOf(" ") == -1) || (game.numGuesses() > 9)) {
          out.println("Syntax: \"guess CCCC\", where C is one of {RGBWYO}. 10 guess max.");
          break;
        }
        guess = input.substring(input.indexOf(" ")).trim().toUpperCase();  // get line after space
        game.makeGuess(guess);
        String result = MMind.response(code, guess);
        if (result.startsWith("" + MMind.NUMCOLORS)) {
          result = "Congratulations! You solved the code. " + result;
        }
        out.println(result);
        break;
      case 3:  // History
        out.println(game.numGuesses() + " turns");
        for(int i=game.numGuesses(); i>=1; i--) {
          String gss = game.getGuess(i);
          String line = "  Turn " + i + " \t" + gss + " \t" + MMind.response(code, gss);
          out.println(line);
        }
        break;
      case 4:  // Quit
        out.println("Goodbye.");
        server.close();
        input = "quit";
        break;
      case 5:  // Shutdown
        out.println("Goodbye.");
        server.close();
        // Throw exception to get out of 'inherited' listen(), or just quit
        System.exit(1);
      default:
        out.println("I didn't understand your command <" + input + ">");
        break;
      }
      out.flush();
    }
  }

  public static int getAction(String command) {
    String [] commands = {"newgame", "cheat", "guess", "history", "quit", "shutdown"};
    for(int i=0; i<commands.length; i++) {
      if (command.startsWith(commands[i])) {
        return i;
      }
    }
    return -1;
  }
}


public class MMind {
  public static final int NUMCOLORS = 4;

  // Will assume code and guess are strings without spaces

  private static boolean [] exactlyRight(String code, String guess) {
    boolean [] result = new boolean[NUMCOLORS];
    for(int i=0; i<NUMCOLORS; i++) {
      if (code.charAt(i) == guess.charAt(i)) {
        result[i] = true;
      }
    }
    return result; 
  }

  // Black pegs indicate the number of correct colors in the correct position
  public static int numBlack(String code, String guess) {
    boolean[] matches = exactlyRight(code, guess);
    int count = 0;
    for(int i=0; i<NUMCOLORS; i++) {
      if (matches[i])
        count++;
    }
    return count;
  }

  // White pegs indicate the number of correct colors in an incorrect position
  public static int numWhite(String code, String guess) {
    boolean[] ignorable = exactlyRight(code, guess);
    boolean [] discountCde = new boolean[4];
    boolean [] discountGss = new boolean[4];
    for(int i=0; i<ignorable.length; i++) {
      discountCde[i] = discountGss[i] = ignorable[i];
    }

    int count = 0;
    for(int i=0; i<NUMCOLORS; i++) {  // loop over colors in guess
      if (!ignorable[i]) {
        // Not a black peg, but must check to see if the color matches another position
        for(int j=0; j<NUMCOLORS; j++) {  // loop over colors in code
          if ((guess.charAt(i) == code.charAt(j)) &&
              !discountCde[i] &&
              !discountGss[j]) {
            count++;
            discountCde[i] = true;  // Consider this no longer, it was counted as a white peg
            discountGss[j] = true;  // Consider this no longer, it was counted as a white peg
            break;  // Stop checking for this guess-char
          }
        }
      } // else was counted as a black peg
    }
    return count;
  }

  public static String response(String code, String guess) {
    int nb = numBlack(code, guess);
    int nw = numWhite(code, guess);
    return nb + " Black and " + nw + " White";
  }

  // Server is default, must specify port *and* host to run client
  public static void main(String [] args) {
    String host = "localhost";
    int port = 5555;

    if (args.length > 0)
      port = Integer.parseInt(args[0]);

    if (args.length > 1) {
      host = args[1];
      MMClient client = new MMClient(host, port);
      client.connect();
    } else {
      MMServer server = new MMServer(port);
      server.listen();
    }
    //System.out.println("woby ybow: " + response("woby", "ybow"));
  }

}
