package de.ctdo.crashtest.game;

import de.ctdo.crashtest.domotics.*;
import de.ctdo.crashtest.gui.*;
import de.ctdo.crashtest.irc.*;
import de.ctdo.crashtest.mpd.IMPDController;
import de.ctdo.crashtest.mpd.MPDController;

import java.util.Random;

public class TheGame implements StatemachineListener, GuiEventListener, IRCEventListener {
    private final IGuiControl guiControl;
    private final IIrcClient ircClient;
    private final IStatemachine machine;
    private final IBuntiClient bunti;
    private final IMPDController mpdController;
    private final IRelaisboard relaisboard;
    private int gamerRating = 3;
    private Thread discoThread;
    private boolean shouldStopDisco;
    private boolean gemActivated = false;

    public TheGame(IGuiControl guiControl1) {
        guiControl = guiControl1;
        ircClient = new IrcClient("crashtest", "#crashtest","irc.ctdo.de");
        bunti = new BuntiClient("bunti.ctdo.de", 8080);
        mpdController = new MPDController("dampfradio.raum.ctdo.de");
        relaisboard = new Relaisboard("/dev/ttyUSB0");
        machine = new Statemachine();
        initGame();
    }

    /**
     * Event listener for state change events from statemachine
     * @param newState  the new game state from statemachine
     */
    @Override
    public void stateChanged(Statemachine.state newState) {

        ircClient.say("New State: " + newState);

        switch (newState) {
            case NEUTRAL:
                discoStop();
                bunti.setPar56(255,255,255);
                bunti.setLampel(false,false,false);
                machine.stopTimer();

                break;
            case IDLE:
                gamerRating = 3;
                machine.stopTimer();
                resetDomotics();
                mpdController.clearPlaylist();
                discoStop();

                guiControl.setExtra("");
                guiControl.setWall("");

                playRandomStartMix();
                mpdController.setVolume(50);

                guiControl.showCountDown(false);
                break;
            case ENTERED_ROOM:
                relaisboard.setRelais(7, false); // disable light barrier over relais
                mpdController.playSong("crashtest", "entered_room");
                mpdController.setVolume(70);

                bunti.setLampel(false,false,false);
                bunti.setPar56(20,0,100);

                guiControl.showCountDown(true);

                machine.startTimer(60*8);
                break;
            case TABLE_GAME_ONE:
                relaisboard.setRelais(6, true); // enable third green circle

                mpdController.playSong("crashtest", "table_game_one");
                mpdController.setVolume(70);

                bunti.setLampel(true,false,false);
                bunti.setPar56(20,0,100);
                guiControl.showCountDown(true);
                break;
            case TABLE_GAME_TWO:
                mpdController.playSong("crashtest", "table_game_two");
                mpdController.setVolume(67);

                bunti.setLampel(false,true,false);
                bunti.setPar56(100,0,100);

                guiControl.showCountDown(true);
                break;
            case TABLE_GAME_THREE:
                mpdController.playSong("crashtest", "table_game_three");
                mpdController.setVolume(63);

                bunti.setLampel(false,true,false);
                bunti.setPar56(255,35,0);

                guiControl.showCountDown(true);
                break;
            case TABLE_GAME_FOUR:
                mpdController.playSong("crashtest", "table_game_four");
                mpdController.setVolume(60);

                bunti.setLampel(false,true,false);
                bunti.setPar56(200,100,0);

                guiControl.showCountDown(true);
                break;
            case TABLE_GAME_FIVE:
                mpdController.playSong("crashtest", "table_game_five");
                mpdController.setVolume(57);

                bunti.setLampel(false,true,false);
                bunti.setPar56(150,150,0);

                guiControl.showCountDown(true);
                break;
            case TABLE_GAME_SIX:
                mpdController.playSong("crashtest", "table_game_six");
                mpdController.setVolume(53);

                bunti.setLampel(false,true,false);
                bunti.setPar56(100,200,0);

                guiControl.showCountDown(true);
                break;
            case TABLE_GAME_DONE:
                mpdController.playSong("crashtest", "table_game_done");
                mpdController.setVolume(50);

                bunti.setLampel(false,false,true);
                bunti.setPar56(100,255,0);

                guiControl.showCountDown(true);
                machine.pauseTimer(true);

                // spieler haben 8 Minuten, wenn sie es in weniger als 4 minuten schaffen
                // gibts +1, in weniger als 2 minuten gibts +2
                if(machine.getTimerSecondsLeft() >= 6*60 ) {
                    rate(2, "table game faster than 2 minutes");
                } else if(machine.getTimerSecondsLeft() > 4*60) {
                    rate(1, "table game faster than 4 minutes");
                }
                if(machine.getStateChangeCounter() > 100) {
                    rate(-1, "more than 100 tries");
                }

                sayScore();

                relaisboard.setRelais(6, false);     // disable third green circle
                relaisboard.blinkRelais(2, 700);     // hint Button

                break;
            case ROKET_STARTED:
                mpdController.playSong("crashtest", "roket_started");
                mpdController.addToPlayList("crashtest", "roket_started2");
                mpdController.setVolume(50);

                bunti.setLampel(false,true,false);
                bunti.setPar56(0, 255, 0);

                relaisboard.toggleRelais(0, 300);    // r0kets toogle
                relaisboard.toggleRelais(3, 10000);  // oven
                relaisboard.blinkRelaisStop(2);      // stop hint button lamp
                relaisboard.setRelais(2, false);

                guiControl.setWall("Pizza ist fertig!");
                guiControl.showCountDown(true);
                machine.startTimer(7*60);
                break;
            case ROKET_DONE:
                mpdController.playSong("crashtest", "roket_done");
                mpdController.setVolume(60);

                bunti.setLampel(false,false,true);
                bunti.setPar56(255, 255, 255);

                guiControl.setWall("willkommen im chaostreff dortmund");
                guiControl.showCountDown(true);
                machine.pauseTimer(true);

                // spieler haben 7 Minuten, wenn sie es in weniger als 4 minuten schaffen
                // gibts +1, in weniger als 2 minuten gibts +2
                if(machine.getTimerSecondsLeft() >= 5*60 ) {
                    rate(2, "r0kets faster than 2 minutes");
                } else if(machine.getTimerSecondsLeft() > 3*60) {
                    rate(1, "r0kets faster than 4 minutes");
                }

                sayScore();
                discoStart();
                break;
        }

    }

    /**
     * Event listener for timer ticks from statemachine.
     * @param tsecondsLeft  the seconds left on the current game
     */
    @Override
    public void timerTick(int tsecondsLeft) {
        guiControl.setCountDown(tsecondsLeft);
        if(tsecondsLeft == 0) {
            ircClient.say("timer expired");
            Statemachine.state bla = machine.getCurrentState();

            if( bla != Statemachine.state.TABLE_GAME_DONE &&
                bla != Statemachine.state.ROKET_DONE ) {
                rate(-2, "game not done in time");
                sayScore();
            }
        }
    }

    /**
     * Event listener for keyPress events from the GUI
     * @param key  the pressed key
     */
    @Override
    public void keyPress(char key) {
        machine.handleInput(key);
    }

    /**
     * Event lister for window closing event from the GUI
     */
    @Override
    public void windowClosing() {
        discoStop();
        resetDomotics();
        relaisboard.close();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException ignored) { }

        bunti.setPar56(0xff,0xff,0xff);
        ircClient.say("bye");
        System.exit(0);
    }

    /**
     * Event listener for IRC Messages
     * @param message the message from the IRC user
     */
    @Override
    public void handleMessage(final String message) {
        if(message.startsWith("help")) {
            handleHelpCommand(message);
        } else if(message.equals("reset")) {
            machine.reset();
            mpdController.clearPlaylist();
            playRandomStartMix();
            resetDomotics();
        } else if(message.startsWith("state")) {
            handleStateCommand(message);
        } else if(message.startsWith("timer")) {
            handleTimerCommand(message);
        } else if(message.startsWith("score")) {
            sayScore();
        } else if(message.startsWith("wall")) {
            guiControl.setWall(message.substring("wall".length()).trim());
        } else if(message.startsWith("extra")) {
            guiControl.setExtra(message.substring("extra".length()).trim());
        } else if(message.startsWith("relais")) {
            handleRelaisCommand(message);
        } else if(message.startsWith("disco on")) {
            discoStart();
        } else if(message.startsWith("disco off")) {
            discoStop();
        } else if(message.startsWith("gem")) {
            handleGemCommand();
        } else {
            ircClient.say("y u no use valid command?");
        }
    }

    private void initGame() {
        guiControl.addListener(this);
        ircClient.addListener(this);
        machine.addListener(this);

        machine.reset();
        relaisboard.open();
        resetDomotics();
    }

    private void rate(int rating, String text) {
        gamerRating += rating;
        ircClient.say("rated: " + rating + " (" + gamerRating + ") " + text);
        if(gamerRating > 5) gamerRating = 5;
        if(gamerRating < 1) gamerRating = 1;
    }

    private void handleTimerCommand(final String message) {
        String params = message.substring("timer".length()).trim().toLowerCase();

        if(params.startsWith("start")) {
            String timeStr = params.substring("start".length()).trim();

            if(timeStr.length() > 0) {
                try {
                    int time = Integer.parseInt(timeStr);

                    guiControl.showCountDown(true);
                    machine.startTimer(time);
                    ircClient.say("got it, starting with " + time + " seconds");
                }
                catch (NumberFormatException e) {
                    ircClient.say("your number looks a bit odd...");
                }
            } else {
                ircClient.say("invalid parameter");
            }            
        } else if(params.startsWith("stop")) {
            machine.stopTimer();
            guiControl.showCountDown(false);
        } else if(params.startsWith("pause")) {
            machine.pauseTimer(true);
        } else if(params.startsWith("resume")) {
            machine.pauseTimer(false);
        }
    }

    private void handleStateCommand(final String message) {
        if(message.trim().equals("state")) {
            ircClient.say(machine.getCurrentState().name());
        } else {
            String params = message.substring("state".length()).trim().toLowerCase();

            Boolean ok = false;
            for(Statemachine.state st: Statemachine.state.values()) {
                if(st.name().toLowerCase().equals(params)) {
                    ok = true;
                    machine.setNewState(st);
                    break;
                }
            }

            if(!ok) ircClient.say("ehm, impossibruu!");
        }
    }

    // Yes, it makes no sense, but I want have it anyway. For more Spass am Geraet!
    private void handleGemCommand() {
        if (!gemActivated) {
            java.util.Random random = new java.util.Random();
            int scry = random.nextInt(100); 
            if (scry >= 99) {
                ircClient.say("Perfect Gem Activated");
            } else if (scry >= 90) {
                ircClient.say("Moooooooo!");
            } else {
                ircClient.say("Gem Activated");
            }
        } else {
            ircClient.say("Gem Deactivated");
        }
        gemActivated = !gemActivated;
    }

    private void handleRelaisCommand(final String message) {
        String params = message.substring("relais".length()).trim().toLowerCase();

        if(params.startsWith("lamp on")) {
            relaisboard.setRelais(2, true);
        } else if(params.startsWith("lamp off")) {
            relaisboard.setRelais(2, false);
        } else if(params.startsWith("oven on")) {
            relaisboard.setRelais(3, true);
        } else if(params.startsWith("oven off")) {
            relaisboard.setRelais(3, false);
        } else if(params.startsWith("rokets")) {
            relaisboard.toggleRelais(0, 300);
        } else if(params.startsWith("lamp blink")) {
            relaisboard.blinkRelais(2, 700);
        } else if(params.startsWith("lamp stop")) {
            relaisboard.blinkRelaisStop(2);
        }
    }

    private void handleHelpCommand(final String message) {
        if(message.equals("help")) {
            ircClient.say("commands: help, reset, state, timer, wall, extra, score, relais, disco, gem");
        } else if(message.contains("reset")) {
            ircClient.say("resets the game to IDLE");
        } else if(message.contains("state")) {
            ircClient.say("get or set game state");
            ircClient.say("> state <NEWSTATE>");
            ircClient.say("valid states: ");

            int counter = 0;
            StringBuilder sb = new StringBuilder();
            for(Statemachine.state st: Statemachine.state.values()) {
                sb.append(st.name());
                sb.append("    ");
                if(++counter == 4) {
                    counter = 0;
                    ircClient.say(sb.toString());
                    sb.setLength(0);
                }
            }

            if(sb.length() > 0) {
                ircClient.say(sb.toString());
            }
        } else if(message.contains("timer")) {
            ircClient.say("control timer. commands are: start, stop, pause, resume.");
            ircClient.say("for start give lenght in seconds as parameter");
            ircClient.say("e.g.: timer start 120");
        } else if(message.contains("wall")) {
            ircClient.say("set text message on the screen");
        } else if(message.contains("extra")) {
            ircClient.say("set small extra message on the screen");
        } else if(message.contains("score")) {
            ircClient.say("i will tell you the current game score");
        } else if(message.contains("relais")) {
            ircClient.say("control the relais board");
            ircClient.say("valid commands: lamp on, lamp off, lamp blink, lamp stop, oven on, oven off, rokets");
        } else if(message.contains("disco")) {
            ircClient.say("party! use: disco {on,off}");
        } else if(message.contains("gem")) {
            ircClient.say("The chat gem is working as intended.");
        } else {
            ircClient.say("dafuq?");
        }
    }

    private void sayScore() {
        ircClient.say("stateChangeCounter: " + machine.getStateChangeCounter());

        int secondsLeft = machine.getTimerSecondsLeft();
        int seconds = machine.getTimerSeconds();
        int secondsUsed = seconds - secondsLeft;

        int mins  = seconds / 60;
        int minsUsed  = secondsUsed / 60;
        int minsLeft  = secondsLeft / 60;

        ircClient.say(String.format("time: %d:%02d  used: %d:%02d  left: %d:%02d",
                                    mins, seconds % 60, minsUsed, secondsUsed % 60, minsLeft, secondsLeft % 60));

        ircClient.say("gamerRating: " + gamerRating);
    }

    private void resetDomotics() {
        relaisboard.setRelais(7, true);  // enable light barrier over relais
        relaisboard.setRelais(6, true);  // enable third green circle
        relaisboard.setRelais(2, false); // disable the lamp
        relaisboard.setRelais(3, false); // disable the oven
        bunti.setPar56(0, 0, 0);
        bunti.setLampel(false, false, false);
    }

    private void discoStop() {
        if(discoThread != null) {
            shouldStopDisco = true;
            discoThread.interrupt();
            try {
                discoThread.join(500);
            } catch (InterruptedException ignored) { }
        }
    }

    private void discoStart() {
        discoStop();

        Runnable r = new Runnable() {
            @Override
            public void run() {
                try {
                    while(true) {
                        if(shouldStopDisco) return;
                        bunti.setPar56(0, 255,   0,   0);
                        bunti.setPar56(1,   0, 255,   0);
                        bunti.setPar56(2,   0, 255,   0);
                        bunti.setPar56(3, 255,   0,   0);
                        if(shouldStopDisco) return;
                        Thread.sleep(500);
                        bunti.setPar56(0,   0,   0, 255);
                        bunti.setPar56(1, 255, 255,   0);
                        bunti.setPar56(2, 255, 255,   0);
                        bunti.setPar56(3,   0,   0, 255);
                        if(shouldStopDisco) return;
                        Thread.sleep(500);
                        bunti.setPar56(0, 255, 128,   0);
                        bunti.setPar56(1,   0, 255, 255);
                        bunti.setPar56(2,   0, 255, 255);
                        bunti.setPar56(3, 255, 128,   0);
                        if(shouldStopDisco) return;
                        Thread.sleep(500);
                        bunti.setPar56(0,   0, 255, 255);
                        bunti.setPar56(1,   0, 255,   0);
                        bunti.setPar56(2,   0, 255,   0);
                        bunti.setPar56(3,   0, 255, 255);
                        if(shouldStopDisco) return;
                        Thread.sleep(500);
                    }
                } catch (InterruptedException ignored) { }
            }
        };

        discoThread = new Thread(r);
        shouldStopDisco = false;
        discoThread.start();
    }

    private void playRandomStartMix() {
        Random r = new Random();
        int num = r.nextInt(3)+1;
        if(num == 1) {
            mpdController.playSong("crashtest", "idle");
        } else if(num == 2) {
            mpdController.playSong("crashtest", "idle2");
        } else if(num == 3) {
            mpdController.playSong("crashtest", "idle3");
        } else if(num == 4) {
            mpdController.playSong("crashtest", "idle4");
        }

        mpdController.skipRandomStart();
    }

}
