package game.players;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import base.Graph;
import base.Node;
import game.AI;
import game.Game;
import game.Player;
import game.map.Castle;
import game.map.PathFinding;
import gui.AttackThread;
import gui.components.JokerPanel.JokerTypes;
import gui.components.MapPanel.Action;

public class StrongAI extends AI {
	
	// These values were determined mostly by trial-and-error
	
	private static double UTILITY_F1 = 5.0;			// Number of adjacent friendly castles
	private static double UTILITY_F2 = 1.0;			// Number of adjacent enemy castles
	private static double UTILITY_F3 = 3.0;			// Number of connected edges
	private static double UTILITY_F4 = -10.0;		// Is surrounded by opponents castles
	private static double UTILITY_F5 = 3.0;			// Size of the castles batch
	private static double UTILITY_F6 = 4.0;			// Castles missing to a full kingdom
	
	private static double REINFORCE_F1 = 1.0;		// Number of adjacent enemy castles
	private static double REINFORCE_F2 = -5.0;		// Is surrounded by opponents castles
	private static double REINFORCE_F3 = 0.5;		// Castle utility
	private static double REINFORCE_F4 = -100.0;	// Is border castle
	private static double REINFORCE_F5 = -2.0;		// Number of troops
	
	private static double ATTACK_F1 = 1.0;			// Castle utility
	
	private static double EVAL_OWN = 3.0;			// Weight of own utilities
	private static double EVAL_OPP = -10.0;			// Weight of opponents utilities
	
	private static int scare_threshold = 5;			// Number of troops to use scare joker
	
	
	 public StrongAI(String name, Color color) {
	        super(name, color);
	 }
	 
    /**
     * Evaluates the current game state
     * @param game the current game
     * @param graph the graph to be evaluated
     * @param p the player
     * @param change the castle that should be considered to belong to p
     * @return the state value
     */
    public static double evaluateState(Game game, Graph<Castle> g, Player p, Optional<Castle> change) {
    	// Temporarily give castle "change" to p
    	Player owner = null;
    	if(change.isPresent()) {
    		owner = change.get().getOwner();
    		change.get().setOwner(p);
    	}
    	// Calculate total state utility
    	double value = 0;
    	for(Player player : game.getPlayers()) {
	    	List<Castle> castles = g.getAllValues().stream()
	    			.filter(x->x.getOwner() == player)
	    			.collect(Collectors.toList());
	    	
	    	double s = (p == player)? EVAL_OWN : EVAL_OPP;
	    	for(Castle c : castles) {
	    		value += s * utilityCastle(g, player, c);
	    	}
    	}
    	// Hand "change" back to its previous owner
    	if(change.isPresent()) {
    		change.get().setOwner(owner);
    	}
    	return value;
    }
    
    /**
    
    /**
     * Analyzes the utility of a given castle to the player p
     * @param g the game graph
     * @param p the player
     * @param c the castle to be analyzed
     * @return the utility value
     */
    public static double utilityCastle(Graph<Castle> g, Player p, Castle c) {
    	Node<Castle> node = g.getNode(c);
    	
    	// Number of adjacent friendly castles
    	double f1 = g.getEdges(node).stream()
    			.filter(x->x.getOtherNode(node).getValue().getOwner() != null)
    			.filter(x->x.getOtherNode(node).getValue().getOwner() == p)
    			.count();
    	// Number of adjacent enemy castles
    	double f2 = g.getEdges(node).stream()
    			.filter(x->x.getOtherNode(node).getValue().getOwner() != null)
    			.filter(x->x.getOtherNode(node).getValue().getOwner() != p)
    			.count();
    	
    	// Number of connected edges
    	double f3 = g.getEdges(node).size() - 1;
    	
    	// Is surrounded by opponents castles
    	double f4 = (g.getEdges(node).stream()
    			.filter(x->x.getOtherNode(node).getValue().getOwner() != null)
    			.filter(x->x.getOtherNode(node).getValue().getOwner() != p)
    			.count() == g.getEdges(node).size() - 1)? 1.0 : 0;
    	
    	// Size of the castles batch
    	double f5 = getBatch(g, p, c, new ArrayList<>()).size();
    	
    	// Castles missing to a full kingdom
    	double f6 = c.getKingdom().getCastles().stream()
    			.filter(x->x.getOwner() == p).count() - c.getKingdom().getCastles().size() + 2;

    	return UTILITY_F1 * f1 + UTILITY_F2 * f2 
    			+ UTILITY_F3 * f3 + UTILITY_F4 * f4 + f5 * UTILITY_F5
    			+ f6 * UTILITY_F6;
    }
    
    /**
     * Analyzes the utility to attack a certain castle
     * @param g the graph
     * @param p the current player
     * @param c the castle
     * @return the reinforcement utility
     */
    public static double utilityReinforce(Graph<Castle> g, Player p, Castle c) {
    	Node<Castle> node = g.getNode(c);

    	// Number of adjacent enemy castles
    	double f1 = g.getEdges(node).stream()
    			.filter(x->x.getOtherNode(node).getValue().getOwner() != null)
    			.filter(x->x.getOtherNode(node).getValue().getOwner() != p)
    			.count();
    	
    	// Is surrounded by opponents castles
    	double f2 = (g.getEdges(node).stream()
    			.filter(x->x.getOtherNode(node).getValue().getOwner() != null)
    			.filter(x->x.getOtherNode(node).getValue().getOwner() != p)
    			.count() == g.getEdges(node).size() - 1)? 1.0 : 0;
    	
    	// Castle utility
    	double f3 = utilityCastle(g, p, c);
    	
    	// Is border castle
    	double f4 = isBorder(g, c)? 0.0 : 1.0;
    	
    	// Number of troops exceeding
    	double f5 = c.getTroopCount();

    	return REINFORCE_F1 * f1 + REINFORCE_F2 * f2 + f3 * REINFORCE_F3
				 + f4 * REINFORCE_F4 + f5 * REINFORCE_F5;
    }
    
    /**
     * Analyzes the utility to attack a certain castle
     * @param g the graph
     * @param p the attacking player
     * @param a the attacking castle
     * @param t the target castle
     * @return
     */
    public static double utilityAttack(Game game, Graph<Castle> g, Player p, Castle a, Castle t) {
    	double f1 = evaluateState(game, g, p, Optional.of(t));
    	
    	double ratio = ((double)a.getTroopCount())/t.getTroopCount();
    
    	return ATTACK_F1 * f1 * ratio;
    }
    
    /**
     * Chooses the initial castles in round one
     * @param game the game
     * @throws InterruptedException
     */
    private void chooseInitialCastles(Game game) throws InterruptedException {
    	 List<Castle> availableCastles = game.getMap().getCastles().stream()
    			 .filter(c -> c.getOwner() == null)
    			 .collect(Collectors.toList());
    	 
    	 while(availableCastles.size() > 0 && getRemainingTroops() > 0) {
             sleep(1000);
             // Find best castle
             Castle best = availableCastles.get(0);
             double bestScore = 0;
             double score = 0;
             for(Castle c : availableCastles) {
            	 score = evaluateState(game, game.getMap().getGraph(), this, Optional.of(c));
            	 if(score > bestScore) {
            		 bestScore = score;
            		 best = c;
            	 }
             }
             availableCastles.remove(best);
             game.chooseCastle(best, this);
         }
    }

    /**
     * Distribute new troops to castles
     * @param game the game
     * @throws InterruptedException
     */
    private void distributeTroops(Game game) throws InterruptedException {
    	Graph<Castle> g = game.getMap().getGraph();
    	List<Castle> castles = game.getMap().getCastles().stream()
    			.filter(x->x.getOwner() == this)
    			.collect(Collectors.toList());
    	
    	while(this.getRemainingTroops() > 0) {
    		Castle best = castles.get(0);
    		double bestScore = 0;
            for(Castle c : castles) {
            	double score = utilityReinforce(g, this, c);
            	if(score > bestScore) {
            		bestScore = score;
            		best = c;
            	}
            }
            sleep(500);
            game.addTroops(this, best, 1);
            
        }
    }
    
    /**
     * Reinforces the castles with troops
     * @param game the game
     */
    private void reinforceCastles(Game game) {
    	Graph<Castle> g = game.getMap().getGraph();
    	// The castles that can send troops
    	List<Castle> distributors = game.getMap().getCastles().stream()
    			.filter(x->x.getOwner() == this)
    			.filter(x->x.getTroopCount() > 1)
    			.filter(x->isBorder(g, x) == false)
    			.collect(Collectors.toList());
    	// The castles that can receive troops
    	List<Castle> receivers = game.getMap().getCastles().stream()
    			.filter(x->x.getOwner() == this)
    			.filter(x->isBorder(g, x) == true)
    			.collect(Collectors.toList());
    	
    	if(receivers.size() == 0 || distributors.size() == 0) return;
    	
    	for(Castle d : distributors) {
    		while(d.getTroopCount() > 1) {
    			double bestScore = 0;
	    		Castle best = receivers.get(0);
	    		// Do not cheat like evil BasicAI,
	    		// only move troops if there is a path
	    		PathFinding path = new PathFinding(g, d, Action.MOVING, this);
	    		for(Castle c : receivers) {
	    			double score = utilityReinforce(g, this, c);
	    			if(score > bestScore && path.getPath(c) != null) {
	    				if(path.getPath(c).size() > 0) {
	    					bestScore = score;
	    					best = c;
	    				}
	    			}
	    		}
	    		if(best == d) break;
	    		
	    		game.moveTroops(d, best, 1);
    		}
    	}
    }
    
    /**
     * Attacks opponents castles
     * @param game the game
     * @throws InterruptedException 
     */
    private boolean attackCastles(Game game) throws InterruptedException {
    	Graph<Castle> g = game.getMap().getGraph();
    	// Castles that can attack
    	List<Castle> attackers = g.getAllValues().stream()
    			.filter(x->x.getOwner() == this)
    			.filter(x->isBorder(g, x))
    			.filter(x->x.getTroopCount() > 1)
    			.collect(Collectors.toList());
    	
    	for(Castle a : attackers) {
    		Node<Castle> na = g.getNode(a);
    		List<Castle> targets = g.getEdges(na).stream()
    				.map(x->x.getOtherNode(na).getValue())
    				.filter(x->x.getOwner() != this)
    				.collect(Collectors.toList());
    		
    		if(targets.isEmpty())
    			continue;
    		
    		// Find best target to attack
    		Castle best = targets.get(0);
    		double bestScore = 0;
    		for(Castle t : targets) {
    			double score = utilityAttack(game, g, this, a, t);
    			if(score > bestScore) {
    				bestScore = score;
    				best = t;
    			}
    		}
    		// Attack, if the chance of taking the castle is more useful than not attacking
    		if(bestScore > evaluateState(game, g, this, Optional.empty()) && a.getTroopCount() > best.getTroopCount()) {
    			if(best.getTroopCount() > scare_threshold || getBatch(g, best.getOwner(), best, new ArrayList<>()).size() == 1) {
    				if(getJokers()[1] == JokerTypes.SCARE_TROOPS) {
    					playScareJoker(best, game);
    				}
    			}
    			 AttackThread attackThread = game.startAttack(a, best, a.getTroopCount());
                 if(fastForward)
                     attackThread.fastForward();

                 attackThread.join();
                 return true;
    		}else {
    			return false;
    		}
    		
    	}
    	return false;
    }
    
    @Override
    protected void actions(Game game) throws InterruptedException {
        if(game.getRound() == 1) {
           chooseInitialCastles(game);
        }else {
            if(getJokers()[0] == JokerTypes.ADD_TROOPS) {
            	playTroopsJoker();
            }
        	distributeTroops(game);
        	boolean shouldAttack = false;
            do {
            	shouldAttack = attackCastles(game);
            	reinforceCastles(game);
            }while(shouldAttack);
        }
    }
    
    /**
     * Checks, whether a given castle has adjacent opponent castles
     * @param g the graph
     * @param castle the castle to be checked
     * @return true, if castle is on border
     */
    private static boolean isBorder(Graph<Castle> g, Castle castle) {
    	Node<Castle> n = g.getNode(castle);
    	int a = (int) g.getEdges(n).stream()
    			.filter(x->x.getOtherNode(n).getValue().getOwner() != null)
    			.filter(x->x.getOtherNode(n).getValue().getOwner() != castle.getOwner())
    			.count();
    	
    	return (a > 0)? true : false;
    }
    
    /**
     * Returns a batch of connected castles around some start castle that belong to the player p
     * @param g the graph
     * @param p the owner of the castles
     * @param castle the start castle
     * @param checked a list of checked castles, initialize as new ArrayList
     * @return a list of castles
     */
    public static List<Castle> getBatch(Graph<Castle> g, Player p, Castle castle, List<Castle> checked) {
    	Node<Castle> n = g.getNode(castle);
    	Set<Castle> adjacent = g.getEdges(n).stream()
    			.map(x->x.getOtherNode(n).getValue())
    			.filter(x->x.getOwner() == p)
    			.filter(x->checked.contains(x) == false)
    			.collect(Collectors.toSet());
    	if(!checked.contains(castle))
    		checked.add(castle);
    	for(Castle c : adjacent) {
    		checked.addAll(getBatch(g, p, c, checked).stream()
    				.filter(x->checked.contains(x) == false)
    				.collect(Collectors.toSet()));
    	}
    	return checked;
    }
}
