package bot.bender;

import bot.bender0.Bender0;
import bot.bender1.Bender1;
import bot.Config;
import bot.bender2.Bender2;
import bot.dto.ApiKey;
import bot.dto.GameState;
import bot.dto.Move;
import com.google.api.client.http.*;
import com.google.api.client.http.apache.ApacheHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.json.gson.GsonFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import persistence.GameLog;
import persistence.ManageGameLog;
import persistence.ManageState;
import persistence.SharedBuffer;

import java.io.IOException;

public class BenderRunner extends Thread {
    private static final HttpTransport HTTP_TRANSPORT = new ApacheHttpTransport();
    private static final JsonFactory JSON_FACTORY = new GsonFactory();
    private static final HttpRequestFactory REQUEST_FACTORY =
            HTTP_TRANSPORT.createRequestFactory(new HttpRequestInitializer() {
                @Override
                public void initialize(HttpRequest request) {
                    request.setParser(new JsonObjectParser(JSON_FACTORY));
                }
            });
    private static final Logger logger = LogManager.getLogger(BenderRunner.class);
    private static final UncaughtExceptionHandler exceptionHandler =
        new UncaughtExceptionHandler() {
            @Override public void uncaughtException(Thread thread, Throwable throwable) {
                String msg = "Thread" + thread.getId() + " DIED: " + thread.getName();
                logger.fatal(msg, throwable);
                System.err.println(msg);
                throwable.printStackTrace();
            }
        };

    private final ApiKey apiKey;
    private final GenericUrl gameUrl;
    private final String user;

    private final ManageGameLog manageGameLog;
    private final SharedBuffer<GameLog> gameLogBuffer;
    private final ManageState manageState;

    private final String bender;

    private boolean run = true;

    public BenderRunner(String bender, ManageState manageState, ManageGameLog manageGameLog, SharedBuffer<GameLog> gameLogBuffer) {
        this.setUncaughtExceptionHandler(exceptionHandler);
        this.bender = bender;
        this.apiKey = Config.getAPIKey(bender);
        this.gameUrl = Config.getGameURL();
        this.user = Config.getName();
        this.manageGameLog = manageGameLog;
        this.gameLogBuffer = gameLogBuffer;
        this.manageState = manageState;
        this.setName("Thread[" + this.getId() + "]<" + bender + "," + Config.getLearningAlgorithm() + ">");
    }

    public void end() { run = false; }

    @Override
    public void run() {
        HttpContent content;
        HttpRequest request;
        HttpResponse response;
        GameState gameState = null;

        try {
            sleep((int)(Math.random() * 20000));
        } catch (InterruptedException e) {}

        while(run) {


            GameLog gameLog = manageGameLog.getGameLog();
            Bender bender = getBender(manageState, gameLog);


            try {

                // Initial request
                logger.debug("Sending initial request...");
                content = new UrlEncodedContent(apiKey);
                request = REQUEST_FACTORY.buildPostRequest(gameUrl, content);
                request.setReadTimeout(0); // Wait forever to be assigned to a game
                response = request.execute();
                gameState = response.parseAs(GameState.class);

                // URL console output.
                logger.info("Game URL: {}, wir sind Player: {}",
                    gameState.getViewUrl(),
                    gameState.getHero().getId());
                gameLog.setGameURL(gameState.getViewUrl());
                gameLog.setWhoAmI(gameState.getHero().getId());

                // Game loop
                while (!gameState.getGame().isFinished() && !gameState.getHero().isCrashed()) {
                    logger.debug("Taking turn " + gameState.getGame().getTurn());
                    long startTime = System.currentTimeMillis();
                    BotMove direction = bender.move(gameState);
                    if (System.currentTimeMillis()-startTime>800) {
                        logger.info(String.format("Time needed for move: %d", System.currentTimeMillis()-startTime));
                    }
                    Move move = new Move(apiKey.getKey(), direction.toString());


                    HttpContent turn = new UrlEncodedContent(move);
                    HttpRequest turnRequest = REQUEST_FACTORY.buildPostRequest(new GenericUrl(gameState.getPlayUrl()), turn);
                    HttpResponse turnResponse = turnRequest.execute();

                    gameState = turnResponse.parseAs(GameState.class);
                    gameLog.addRound();
                }
            } catch (IOException e) {
                logger.error("Error during game play", e);
                if (gameState != null && gameState.getGame() != null && gameState.getGame().getBoard() != null)
                    logger.error(String.format("The game board (size %d) was:\n%s",
                        gameState.getGame().getBoard().getSize(),
                        gameState.getGame().getBoard().getTiles()));
                gameLog.setCrashed(true);
            }

            GameMap gameMap = new GameMap(gameState);
            gameLog.setTotalMineCount(gameMap.getTotalMineCount());
            gameLog.setWin(isWinner(gameState));
            setRanking(gameState, gameLog);
            manageGameLog.updateGameLog(gameLog);
            if (gameLogBuffer != null)
                gameLogBuffer.addEntity(gameLog);
            logger.debug("Game over");
        }
    }

    public Bender getBender(ManageState manageState, GameLog gameLog){
        if(bender.equals("bender0")){
            return new Bender0(manageState, gameLog);
        } else if (bender.equals("bender1")){
            return new Bender1(manageState, gameLog);
        } else if (bender.equals("bender2")) {
            return new Bender2(manageState, gameLog);
        }
        return null;
    }

    public boolean isWinner(GameState gs) {
        if (gs == null || !gs.getGame().isFinished())
            return false;
        boolean isWinner = true;
        GameState.Hero benderHero = gs.getHero();
        for (GameState.Hero hero : gs.getGame().getHeroes()) {
            if (benderHero.getUserId().equals(hero.getUserId()))
                continue;
            if (hero.getGold() >= benderHero.getGold())
                isWinner = false;
        }
        return isWinner;
    }

    private void setRanking(GameState gs, GameLog gl) {
        for(GameState.Hero hero : gs.getGame().getHeroes()) {
            gl.setHeroForPlace(hero.getId(), hero.getUserId(), hero.getGold());
        }
    }
}

@@ -2574,26 +2574,28 @@
 %22 + this.get
-Id
+Name
 () + %22%5D%3C%22 + 
@@ -2677,16 +2677,84 @@
  end() %7B
+%0A        logger.info(%22Will stop Thread %22 + this.toString());%0A       
  run = f
@@ -2758,16 +2758,20 @@
 = false;
+%0A   
  %7D%0A%0A    
@@ -3470,16 +3470,26 @@
 Timeout(
+1260 * 100
 0); // W
@@ -3496,15 +3496,13 @@
 ait 
-forever
+21min
  to 
@@ -5862,24 +5862,82 @@
 ;%0A        %7D%0A
+        logger.info(%22Stopped Thread %22 + this.toString());%0A
     %7D%0A%0A    p
@@ -6317,19 +6317,80 @@
     
-return null
+throw new RuntimeException(%22Bender %22 + bender + %22 is not instantiable.%22)
 ;%0A  
@@ -6434,24 +6434,66 @@
 State gs) %7B%0A
+        // Check if the game is finished.%0A
         if (

Add some debug infos to BenderRunner thread.