package org.drooms.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.Date;
import java.util.Properties;

import org.drooms.api.Game;
import org.drooms.api.GameProgressListener;
import org.drooms.api.Player;
import org.drooms.api.Playground;
import org.drooms.impl.util.GameCLI;
import org.drooms.impl.util.PlayerAssembly;

/**
 * Main class of the application, used to launch a particular game.
 */
public class DroomsGame {

    private static Game getGameImpl(final String id) {
        try {
            @SuppressWarnings("unchecked")
            final Class<? extends Game> cls = (Class<? extends Game>) Class
                    .forName(id);
            return cls.newInstance();
        } catch (final ClassNotFoundException e) {
            throw new IllegalStateException("Cannot find game implementation: "
                    + id);
        } catch (InstantiationException | IllegalAccessException e) {
            throw new IllegalStateException(
                    "Cannot instantiate game implementation: " + id, e);
        }
    }

    private static String getTimestamp() {
        final Date date = new java.util.Date();
        return new Timestamp(date.getTime()).toString();
    }

    /**
     * Run the game from the command-line. For a description of the command line
     * interface, see {@link GameCLI}.
     * 
     * This method expects couple properties to come out of
     * {@link GameCLI#process(String[])}'s game config {@link Properties}:
     * 
     * <dl>
     * <dt>game.class</dt>
     * <dd>A fully qualified name of a class on the classpath that will be used
     * as the game implementation. If not specified, {@link DefaultGame} will be
     * used.</dd>
     * </dl>
     * 
     * @param args
     *            Command-line arguments.
     */
    public static void main(final String[] args) {
        final GameCLI cli = GameCLI.getInstance();
        final File[] configs = cli.process(args);
        if (configs == null) {
            cli.printHelp();
            System.exit(-1);
        }
        // play the game
        final File reportFolder = (configs.length == 4) ? configs[3]
                : new File("reports/");
        final Properties gameConfig = new Properties();
        // FIXME standardize on InputStream or Reader
        try (InputStream playgroundFile = new FileInputStream(configs[0]);
                Reader gameConfigFile = new FileReader(configs[1]);
                Reader playerConfigFile = new FileReader(configs[2])) {
            // prepare configs
            gameConfig.load(gameConfigFile);
            final Properties playerConfig = new Properties();
            playerConfig.load(playerConfigFile);
            // play and report
            final DroomsGame d = new DroomsGame(configs[0].getName(),
                    DefaultPlayground.read(playgroundFile), new PlayerAssembly(
                            playerConfig).assemblePlayers(), gameConfig,
                    reportFolder);
            d.play();
        } catch (final IOException e) {
            throw new IllegalStateException("Failed reading config files.", e);
        }
    }

    private final Playground p;
    private final String n;
    private final Properties c;
    private final Collection<Player> players;
    private final File f;

    public DroomsGame(final String name, final Playground p,
            final Collection<Player> players, final Properties gameConfig,
            final File reportFolder) {
        this.c = gameConfig;
        this.p = p;
        this.f = reportFolder;
        this.n = name;
        this.players = players;
    }

    public boolean play() {
        final Game g = DroomsGame.getGameImpl(this.c.getProperty("game.class",
                "org.drooms.impl.DefaultGame"));
        final File f = new File(this.f, this.n + "-"
                + DroomsGame.getTimestamp());
        if (!f.exists()) {
            f.mkdirs();
        }
        final GameProgressListener gpl = g
                .play(this.p, this.c, this.players, f);
        // report
        try (Writer w = new FileWriter(new File(f, "report.xml"))) {
            gpl.write(w);
            return true;
        } catch (final IOException e) {
            return false;
        }
    }

}
@@ -662,631 +662,8 @@
  %7B%0A%0A
-    private static Game getGameImpl(final String id) %7B%0A        try %7B%0A            @SuppressWarnings(%22unchecked%22)%0A            final Class%3C? extends Game%3E cls = (Class%3C? extends Game%3E) Class%0A                    .forName(id);%0A            return cls.newInstance();%0A        %7D catch (final ClassNotFoundException e) %7B%0A            throw new IllegalStateException(%22Cannot find game implementation: %22%0A                    + id);%0A        %7D catch (InstantiationException %7C IllegalAccessException e) %7B%0A            throw new IllegalStateException(%0A                    %22Cannot instantiate game implementation: %22 + id, e);%0A        %7D%0A    %7D%0A%0A
     
@@ -836,20 +836,35 @@
 Run the 
-g
+%7B@link DefaultG
 ame
+%7D
  from th
@@ -903,446 +903,64 @@
 n of
- the command line%0A     * interface, see %7B@link GameCLI%7D.%0A     * %0A     * This method expects couple properties to come out of%0A     * %7B@link GameCLI#process(String%5B%5D)%7D's game config %7B@link Properties%7D:%0A     * %0A     * %3Cdl%3E%0A     * %3Cdt%3Egame.class%3C/dt%3E%0A     * %3Cdd%3EA fully qualified name of a class on the classpath that will be used%0A     * as the game implementation. If not specified, %7B@link DefaultGame%7D will be%0A     * used.%3C/dd%3E%0A     * %3C/dl%3E
+%0A     * the command line interface, see %7B@link GameCLI%7D.
 %0A   
@@ -2050,16 +2050,35 @@
         
+ DefaultGame.class,
  Default
@@ -2113,34 +2113,9 @@
 le),
- new PlayerAssembly(%0A     
+%0A
     
@@ -2122,35 +2122,51 @@
                 
-   
+new PlayerAssembly(
 playerConfig).as
@@ -2181,28 +2181,16 @@
 ayers(),
- gameConfig,
 %0A       
@@ -2201,16 +2201,28 @@
         
+ gameConfig,
  reportF
@@ -2551,16 +2551,61 @@
  File f;
+%0A    private final Class%3C? extends Game%3E cls;
 %0A%0A    pu
@@ -2649,33 +2649,67 @@
 nal 
-Playground p,%0A           
+Class%3C? extends Game%3E game,%0A            final Playground p,
  fin
@@ -2738,16 +2738,28 @@
 players,
+%0A           
  final P
@@ -2779,28 +2779,16 @@
 eConfig,
-%0A           
  final F
@@ -2910,16 +2910,41 @@
 = name;%0A
+        this.cls = game;%0A
         
@@ -3014,127 +3014,226 @@
     
-final Game g = DroomsGame.getGameImpl(this.c.getProperty(%22game.class%22,%0A                %22org.drooms.impl.DefaultGame%22));
+Game g;%0A        try %7B%0A            g = this.cls.newInstance();%0A        %7D catch (InstantiationException %7C IllegalAccessException e1) %7B%0A            throw new IllegalStateException(%22Cannot find game class.%22, e1);%0A        %7D
 %0A   

Allow to programatically specify which game type should be instantiated.