// ============================================================================
// File:               $File$
//
// Project:            
//
// Purpose:            
//
// Author:             Rammi
//
// Copyright Notice:   (c) 2008  Rammi (rammi@caff.de)
//                     This code is in the public domain.
//                     Use at own risk.
//                     No guarantees given.
//
// Latest change:      $Date$
//
// History:	       $Log$
//=============================================================================
package de.caff.asteroid;

import de.caff.asteroid.analysis.DatagramDumper;
import de.caff.asteroid.server.Bridge;
import de.caff.asteroid.human.HumanClient;

import javax.swing.*;
import java.io.IOException;

/**
 *  Starter for automated player of Asteroids game.
 *
 *  It uses a thread for the communication.
 *  It can simulate the game display if run with switch <tt>-d</tt>.
 *
 *  This class is part of a solution for a
 *  <a href="http://www.heise.de/ct/creativ/08/02/details/">competition by the German computer magazine c't</a>.
 */
public class AsteroidPlayerRunner
{
  /** The default player starter package. */
  public static final String DEFAULT_PLAYER_STARTER_PACKAGE = AsteroidPlayerRunner.class.getPackage().getName() + ".";
  /** The optional extension added to player starter names. */
  public static final String OPTIONAL_PLAYER_STARTER_EXTENSION = "PlayerStarter";
  /** The players in the sequence they are started if no explicite player is defined. */
  public static final String[] DEFAULT_PLAYERS = {
          "rammi.Rammi",
          "Sample",
          "Stupid"
  };

  /** Default hostname (localhost). */
  public static final String DEFAULT_HOSTNAME = "127.0.0.1";
  /** Heise online host. */
  public static final String ONLINE_MAME_HOSTNAME = "asteroids.heise.de";

  /**
   *  Create a player starter from a class name.
   *  @param name class name
   *  @return player starter or <code>null</code> it the creation failed
   *  @throws Throwable any exception thrown during creation
   */
  private static PlayerStarter createPlayerStarter(String name)
          throws Throwable
  {
    Class clazz = Class.forName(name);
    return (PlayerStarter)clazz.newInstance();
  }

  /**
   *  Start a player using a player starter with a given name.
   *  @param name name of player starter. This is either a full class name
   *              like <code>de.caff.asteroid.StupidPlayerStarter</code> or
   *              an abbrevated name like <code>Stupid</code> (which will be extended to
   *              the full name above).
   *  @param com communication port
   *  @return <code>null</code> if it was possible to start the player,<br>
   *          the exception which stopped creating or starting the player
   */
  public static Throwable[] startPlayer(String name, Communication com)
  {
    String[] possibleClassNames = createPossiblePlayerStarterClassnames(name);
    Throwable[] exceptions = new Throwable[possibleClassNames.length];
    for (int c = 0;  c < possibleClassNames.length;  ++c) {
      try {
        PlayerStarter starter = createPlayerStarter(possibleClassNames[c]);
        starter.startPlayer(com);
        return null;
      } catch (Throwable throwable) {
        exceptions[c] = throwable;
      }
    }
    return exceptions;
  }

  /**
   *  Get the possible names for classes defined by a player starter name.
   *  @param name player starter name (possibly a shortcut)
   *  @return possible classnames
   */
  private static String[] createPossiblePlayerStarterClassnames(String name)
  {
    return new String[] {
            name,
            name + OPTIONAL_PLAYER_STARTER_EXTENSION,
            DEFAULT_PLAYER_STARTER_PACKAGE + name,
            DEFAULT_PLAYER_STARTER_PACKAGE + name + OPTIONAL_PLAYER_STARTER_EXTENSION
    };
  }

  /**
   * Start method.
   * @param args switch <tt>-d</tt> and/or hostname/ip address
   */
  public static void main(String[] args)
  {
    String hostname = DEFAULT_HOSTNAME;
    boolean withDisplay = false;
    boolean bridgeMode = false;
    int bridgePort = 0;
    boolean nextIsBridgePort = false;
    String dumpfile = null;
    boolean nextIsDumpFile = false;
    String player = null;
    boolean nextIsPlayer = false;
    String onlineName = null; // onlineName for online game
    boolean nextIsOnlineName = false;
    boolean humanClient = false;
    for (String arg: args) {
      if (nextIsBridgePort) {
        nextIsBridgePort = false;
        try {
          bridgePort = Integer.parseInt(arg);
        } catch (NumberFormatException e) {
          System.err.println(String.format("Bridgemode -b requires numeric port, but has %s!", arg));
          System.exit(1);
        }
      }
      else if (nextIsDumpFile) {
        dumpfile = arg;
        nextIsDumpFile = false;
      }
      else if (nextIsPlayer) {
        player = arg;
        nextIsPlayer = false;
      }
      else if (nextIsOnlineName) {
        onlineName = arg;
        nextIsOnlineName = false;
      }
      else if ("-d".equals(arg)) {
        withDisplay = true;
      }
      else if ("-b".equals(arg)) {
        bridgeMode = true;
        nextIsBridgePort = true;
      }
      else if ("-s".equals(arg)) {
        nextIsDumpFile = true;
      }
      else if ("-p".equals(arg)) {
        nextIsPlayer = true;
      }
      else if ("-n".equals(arg)) {
        nextIsOnlineName = true;
      }
      else if ("-P".equals(arg)) {
        if (hostname == DEFAULT_HOSTNAME) {
          hostname = ONLINE_MAME_HOSTNAME;
        }
        humanClient = true;
      }
      else {
        hostname = arg;
      }
    }
    if (nextIsBridgePort) {
      System.err.println("Bridgemode -b requires port number!");
      System.exit(1);
    }
    if (nextIsDumpFile) {
      System.err.println("Save dump -s requires filename!");
      System.exit(1);
    }
    if (nextIsPlayer) {
      System.err.println("Player option -p requires player starter classname!");
      System.exit(1);
    }
    if (nextIsOnlineName) {
      System.err.println("Name option -n requires onlineName");
      System.exit(1);
    }

    if (humanClient ) {
      // human client takes care of the commmunciation itself
      new HumanClient(hostname);
    }
    else {
      try {
        // start communication thread
        Communication com = new Communication(hostname, bridgeMode, onlineName);
        Thread comThread = new Thread(com, "Communication");
        comThread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
          public void uncaughtException(Thread t, Throwable e)
          {
            System.err.println("Communication thread passed out.");
            e.printStackTrace(System.err);
            System.exit(2);
          }
        });
        // start dumper before com so we can be sure we will catch all datagrams
        if (dumpfile != null) {
          try {
            DatagramDumper.registerDumper(com, dumpfile);
          } catch (IOException e) {
            System.err.println("Cannot create dump file:");
            e.printStackTrace(System.err);
            System.exit(1);
          }
        }


        JFrame frame = null;
        if (withDisplay) {
          // add display
          frame = new JFrame(bridgeMode ? "Bridge Display" : "Frame Display");
          FrameDisplay display = new FrameDisplay(640);
          frame.getContentPane().add(display);
          frame.pack();
          frame.setVisible(true);
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          com.addFrameListener(display);
        }

        if (bridgeMode) {
          // bridge mode (no player)
          Bridge bridge = new Bridge(bridgePort, com);
          Thread bridgeThread = new Thread(bridge, "Bridge");
          bridgeThread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler()
          {
            public void uncaughtException(Thread t, Throwable e)
            {
              System.err.println("Bridge thread passed out.");
              e.printStackTrace(System.err);
              System.exit(2);
            }
          });
          bridgeThread.start();
          com.addDatagramListener(bridge);
          // show velocities here, too, and fix scores
          com.setFramePreparer(new FramePreparerSequence(new SimpleVelocityPreparer(),
                                                         new ScoreFixer()));
        }
        else {
          // make autostart for all players
          AutoStarter.install(com);

          if (player != null) {
            // start explicitely defined player
            Throwable[] errors = startPlayer(player, com);
            if (errors != null) {
              System.err.println(String.format("Could start the player '%s', trying different combinations:", player));
              String[] classnames = createPossiblePlayerStarterClassnames(player);
              for (int t = 0;  t < errors.length;  ++t) {
                System.err.println(String.format("\t'%s' failed for the following reason: %s",
                                                 classnames[t], errors[t]));
              }
              System.exit(2);
            }
            if (frame != null) {
              frame.setTitle(String.format("%s [%s]", frame.getTitle(), player));
            }
          }
          else {
            boolean success = false;
            for (String name: DEFAULT_PLAYERS) {
              if (startPlayer(name, com) == null) {
                success = true;
                if (frame != null) {
                  frame.setTitle(String.format("%s [%s]", frame.getTitle(), name));
                }
                break;
              }
            }
            if (!success) {
              // strange things happen at sea
              System.err.println("Couldn't start any of the default players, please specify player onlineName on the command line (switch -p)!");
              System.exit(2);
            }
          }
        }
        comThread.start();
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(2);
      }
    }
  }
}
