package Exe;

import java.awt.event.KeyListener;
import java.io.*;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.EnumMap;
import java.util.Random;
import pacman.controllers.*;
import pacman.controllers.examples.*;
import pacman.game.Game;
import pacman.game.GameView;

import static pacman.game.Constants.*;

/**
 * This class may be used to execute the game in timed or un-timed modes, with
 * or without visuals. Competitors should implement their controllers in
 * game.entries.ghosts and game.entries.pacman respectively. The skeleton
 * classes are already provided. The package structure should not be changed
 * (although you may create sub-packages in these packages).
 */
public class DataCollect {

    /**
     * The main method. Several options are listed - simply remove comments to
     * use the option you want.
     *
     * @param args the command line arguments
     */
    public static void main(String[] args) throws Exception {
        DataCollect exec = new DataCollect();

        
        /*
         * //run a game in synchronous mode: game waits until controllers
         * respond. int delay=5; boolean visual=true; exec.runGame(new
         * RandomPacMan(),new RandomGhosts(),visual,delay);
         */

        
        int delay = 40;
        int numTrials = 100;
        boolean visual = true;
        
        KeyBoardInput key = new KeyBoardInput();
        exec.m_keyListener = key;
	exec.runGame(new HumanController(key),new RandomGhosts(),visual,delay);
        
    }
    
    /**
     * Constructor, initializes the data file writer
     * @throws IOException 
     */
    public DataCollect() throws IOException {
        m_filewriter = new FileWriter(m_dataFilename);
    }
    
    // file for storing control data
    public static String m_dataFilename = "PacManControlData.arff";
    // file writer
    protected FileWriter m_filewriter = null;
    // listerner for key controling
    protected KeyListener m_keyListener = null;
    // data header
    protected StringBuilder m_header = null;
    
    /**
     * Function for collecting control data
     * @param game: The Game instance
     * @param move: movement by the user
     * @param ghostController: for ghost information
     * @throws Exception 
     */
    protected void collectData(Game game, MOVE move) throws Exception{
        StringBuilder str = new StringBuilder();
        
        int classValue = game.moveToInt(move);
        
        // PLEASE modify Game.getFeature() function to use your own features
        double[] featureVector = game.getFeature(classValue);
        
        // store
        for(int i=0; i<featureVector.length; i++)
            str.append( i<featureVector.length-1 ? featureVector[i]+"," : ((int)featureVector[i])+"\n");
        
        // prepare feature types
        if( m_header==null ){
            m_header=new StringBuilder();
            m_header.append("@relation PacManData\n");
            for(int i=0; i<featureVector.length-1; i++){
                // assume continuous feature
                m_header.append("@attribute attribute_" + i + " real\n");
            }
            // movement is the class with 5 discrete values
            m_header.append("@attribute class {0,1,2,3,4}\n");
            m_header.append("@data\n");
            m_filewriter.write(m_header.toString());
        }
        // write to file
        m_filewriter.write(str.toString());
        
        m_filewriter.flush();
    }
    
    @Override
    public void finalize() throws Exception, Throwable{
        m_filewriter.close();
        super.finalize();
    }

    /**
     * Run a game in asynchronous mode: the game waits until a move is returned.
     * In order to slow thing down in case the controllers return very quickly,
     * a time limit can be used. If fasted gameplay is required, this delay
     * should be put as 0.
     *
     * @param pacManController The Pac-Man controller
     * @param ghostController The Ghosts controller
     * @param visual Indicates whether or not to use visuals
     * @param delay The delay between time-steps
     */
    public void runGame(Controller<MOVE> pacManController, Controller<EnumMap<GHOST, MOVE>> ghostController, boolean visual, int delay) throws Exception {
        Game game = new Game(2,3);

        GameView gv = null;

        if (visual) {
            gv = new GameView(game);
            if( m_keyListener!=null )
                gv.addKeyListener(m_keyListener);
            gv.showGame();
            
        }

        while (!game.gameOver()) {
            MOVE pacmanmove = pacManController.getMove(game.copy(), -1);
                
            // collect huamn control data
            collectData(game, pacmanmove);
                
            game.advanceGame(pacmanmove,
                        ghostController.getMove(game.copy(), -1));

            try {
                Thread.sleep(delay);
            } catch (Exception e) {
            }

            if (visual) {
                gv.repaint();
            }
        }
    }

    /**
     * Run the game with time limit (asynchronous mode). This is how it will be
     * done in the competition. Can be played with and without visual display of
     * game states.
     *
     * @param pacManController The Pac-Man controller
     * @param ghostController The Ghosts controller
     * @param visual Indicates whether or not to use visuals
     */
    
    
    

    /**
     * Run the game in asynchronous mode but proceed as soon as both controllers
     * replied. The time limit still applies so so the game will proceed after
     * 40ms regardless of whether the controllers managed to calculate a turn.
     *
     * @param pacManController The Pac-Man controller
     * @param ghostController The Ghosts controller
     * @param fixedTime Whether or not to wait until 40ms are up even if both
     * controllers already responded
     * @param visual Indicates whether or not to use visuals
     */
    public void runGameTimedSpeedOptimised(Controller<MOVE> pacManController, Controller<EnumMap<GHOST, MOVE>> ghostController, boolean fixedTime, boolean visual) {
        Game game = new Game(0);

        GameView gv = null;

        if (visual) {
            gv = new GameView(game).showGame();
        }

        if (pacManController instanceof HumanController) {
            gv.getFrame().addKeyListener(((HumanController) pacManController).getKeyboardInput());
        }

        new Thread(pacManController).start();
        new Thread(ghostController).start();

        while (!game.gameOver()) {
            pacManController.update(game.copy(), System.currentTimeMillis() + DELAY);
            ghostController.update(game.copy(), System.currentTimeMillis() + DELAY);

            try {
                int waited = DELAY / INTERVAL_WAIT;

                for (int j = 0; j < DELAY / INTERVAL_WAIT; j++) {
                    Thread.sleep(INTERVAL_WAIT);

                    if (pacManController.hasComputed() && ghostController.hasComputed()) {
                        waited = j;
                        break;
                    }
                }

                if (fixedTime) {
                    Thread.sleep(((DELAY / INTERVAL_WAIT) - waited) * INTERVAL_WAIT);
                }

                game.advanceGame(pacManController.getMove(), ghostController.getMove());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (visual) {
                gv.repaint();
            }
        }

        pacManController.terminate();
        ghostController.terminate();
    }

    /**
     * Run a game in asynchronous mode and recorded.
     *
     * @param pacManController The Pac-Man controller
     * @param ghostController The Ghosts controller
     * @param visual Whether to run the game with visuals
     * @param fileName The file name of the file that saves the replay
     */
    public void runGameTimedRecorded(Controller<MOVE> pacManController, Controller<EnumMap<GHOST, MOVE>> ghostController, boolean visual, String fileName) {
        StringBuilder replay = new StringBuilder();

        Game game = new Game(0);

        GameView gv = null;

        if (visual) {
            gv = new GameView(game).showGame();

            if (pacManController instanceof HumanController) {
                gv.getFrame().addKeyListener(((HumanController) pacManController).getKeyboardInput());
            }
        }

        new Thread(pacManController).start();
        new Thread(ghostController).start();

        while (!game.gameOver()) {
            pacManController.update(game.copy(), System.currentTimeMillis() + DELAY);
            ghostController.update(game.copy(), System.currentTimeMillis() + DELAY);

            try {
                Thread.sleep(DELAY);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            game.advanceGame(pacManController.getMove(), ghostController.getMove());

            if (visual) {
                gv.repaint();
            }

            replay.append(game.getGameState() + "\n");
        }

        pacManController.terminate();
        ghostController.terminate();

        saveToFile(replay.toString(), fileName, false);
    }

    /**
     * Replay a previously saved game.
     *
     * @param fileName The file name of the game to be played
     * @param visual Indicates whether or not to use visuals
     */
    public void replayGame(String fileName, boolean visual) {
        ArrayList<String> timeSteps = loadReplay(fileName);

        Game game = new Game(0);

        GameView gv = null;

        if (visual) {
            gv = new GameView(game).showGame();
        }

        for (int j = 0; j < timeSteps.size(); j++) {
            game.setGameState(timeSteps.get(j));

            try {
                Thread.sleep(DELAY);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (visual) {
                gv.repaint();
            }
        }
    }

    //save file for replays
    public static void saveToFile(String data, String name, boolean append) {
        try {
            FileOutputStream outS = new FileOutputStream(name, append);
            PrintWriter pw = new PrintWriter(outS);

            pw.println(data);
            pw.flush();
            outS.close();

        } catch (IOException e) {
            System.out.println("Could not save data!");
        }
    }

    //load a replay
    private static ArrayList<String> loadReplay(String fileName) {
        ArrayList<String> replay = new ArrayList<String>();

        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(fileName)));
            String input = br.readLine();

            while (input != null) {
                if (!input.equals("")) {
                    replay.add(input);
                }

                input = br.readLine();
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }

        return replay;
    }
}