/*
* Tyr is an AI for StarCraft: Broodwar, 
* 
* Please visit https://github.com/SimonPrins/Tyr for further information.
* 
* Copyright 2015 Simon Prins
*
* This file is part of Tyr.
* Tyr is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
* Tyr is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with Tyr.  If not, see http://www.gnu.org/licenses/.
*/


package com.tyr;

import java.util.ArrayList;
import java.util.HashMap;

import com.tyr.builds.BBS;
import com.tyr.builds.Bio;
import com.tyr.builds.BuildOrder;
import com.tyr.builds.CannonWall;
import com.tyr.builds.CarrierRush;
import com.tyr.builds.DTPush;
import com.tyr.builds.DefensiveTerran;
import com.tyr.builds.ProtossFE;
import com.tyr.builds.PvT;
import com.tyr.builds.StandardZerg;
import com.tyr.builds.TAntiZealot;
import com.tyr.builds.TFastExpand;
import com.tyr.builds.TvPGeneric;
import com.tyr.builds.ZealotPush;
import com.tyr.tasks.Task;
import com.tyr.unitgroups.ScoutGroup;

import bwapi.Race;


/**
 * This class represents a strategy that can be used against an opponent.
 * @author Simon
 *
 */
public class Strategy
{
	/**
	 * Code by which this strategy is known.
	 */
	public String code;
	
	/**
	 * The build that is used against this opponent.
	 */
	private BuildOrder counter;
	
	/**
	 * Is there a strategy that we expect the opponent to be using?
	 * Use the strategies from ScoutGroup.
	 */
	private int opponentStrategy;
	
	/**
	 * At what time do we send out a worker scout?
	 * Can be left as -1 to use default scout timing of the build.
	 */
	private int scoutTiming;
	
	/**
	 * How many worker scouts do we send out?
	 * Can be left as 0 to use default scout timing of the build.
	 */
	private int nscouts;
	
	/**
	 * When this flag is set to true, the name is no longer checked, but the build is automatically selected.
	 */
	public boolean triggerDebug = false;
	
	/**
	 * List of tasks that should be added to the task manager.
	 */
	public Task[] tasks;
	
	/**
	 * The strategy we chose to execute.
	 */
	public static Strategy chosenStrategy;
	
	/**
	 * Flags we stored about this opponent.
	 */
	private static ArrayList<String> oldFlags = new ArrayList<String>();
	
	/**
	 * Flags about the opponent we have added.
	 */
	private static ArrayList<String> newFlags = new ArrayList<String>();
	
	/**
	 * Have we lost before?
	 */
	private static boolean lost = false;
	
	/**
	 * The file as a list of its lines.
	 */
	private static ArrayList<String> file;
	
	/**
	 * This class represents a strategy that can be used against an opponent.
	 * @param code Code by which this strategy is known.
	 * @param counter The build that is used against this opponent.
	 */
	public Strategy(String code, BuildOrder counter)
	{
		this(code, counter, ScoutGroup.unknown);
	}
	
	/**
	 * This class represents a strategy that can be used against an opponent.
	 * @param code Code by which this strategy is known.
	 * @param counter The build that is used against this opponent.
	 * @param opponentStrategy Is there a strategy that we expect the opponent to be using?
	 * 		Use the strategies from ScoutGroup.
	 */
	public Strategy(String code, BuildOrder counter, int opponentStrategy)
	{
		this(code, counter, opponentStrategy, -1, 0, new Task[0]);
	}

	/**
	 * This class represents a strategy that can be used against an opponent.
	 * @param code Code by which this strategy is known.
	 * @param counter The build that is used against this opponent.
	 * @param opponentStrategy Is there a strategy that we expect the opponent to be using?
	 * 		Use the strategies from ScoutGroup.
	 * @param tasks List of tasks that should be added to the task manager.
	 */
	public Strategy(String code, BuildOrder counter, int opponentStrategy, Task[] tasks) 
	{
		this(code, counter, opponentStrategy, -1, 0, tasks);
	}
	
	/**
	 * This class represents a strategy that can be used against an opponent.
	 * @param code Code by which this strategy is known.
	 * @param counter The build that is used against this opponent.
	 * @param opponentStrategy Is there a strategy that we expect the opponent to be using?
	 * 		Use the strategies from ScoutGroup.
	 * @param scoutTiming At what time do we send out a worker scout?
	 * 		Can be left as -1 to use default scout timing of the build.
	 * @param nscouts How many worker scouts do we send out?
	 * 		Can be left as 0 to use default scout timing of the build.
	 * @param tasks List of tasks that should be added to the task manager.
	 */
	public Strategy(String code, BuildOrder counter, int opponentStrategy, int scoutTiming, int nscouts, Task[] tasks)
	{
		this.code = code;
		this.counter = counter;
		
		this.opponentStrategy = opponentStrategy;
		this.scoutTiming = scoutTiming;
		this.nscouts = nscouts;
		
		this.tasks = tasks;
	}
	
	/**
	 * Determine which strategy should be used against this opponent.
	 * @return The strategy to be used against this opponent.
	 */
	public static Strategy determine()
	{
		Strategy[] strategies = determineStrategies();
		return pickStrategy(strategies);
	}

	public static void initialize()
	{
		file = DebugMessages.readFile();
		getFlags();
	}

	/**
	 * Determine which strategy should be used against this opponent.
	 * @param strategies The strategies we can choose from.
	 * @param The file as a list of its lines.
	 * @return The strategy to be used against this opponent.
	 */
	public static Strategy pickStrategy(Strategy[] strategies)
	{
		HashMap<String, Integer> losses = new HashMap<String, Integer>();
		HashMap<String, Integer> wins = new HashMap<String, Integer>();
		HashMap<String, Integer> recentLosses = new HashMap<String, Integer>();
		HashMap<String, Integer> recentWins = new HashMap<String, Integer>();
		for (int i = file.size()-1; i >= 0; i--)
		{
			String line = file.get(i);
			boolean win = false;
			
			String[] words = line.split(" ");
			String code = "";
			for (String word : words)
			{
				if (word.equals("win"))
					win = true;
				
				for (Strategy strat : strategies)
					if (strat.code.equals(word))
						code = word;
				
			}
			inc(win?wins:losses, code);
			if (get(recentWins, code) + get(recentLosses, code) < 5)
				inc(win?recentWins:recentLosses, code);
		}
		
		boolean trialPhase = false;
		for (Strategy strat : strategies)
			if (get(losses, strat.code) < 3)
				trialPhase = true;
		
		Strategy choice = null;
		if (trialPhase)
		{
			int score = Integer.MIN_VALUE;
			for (Strategy strat : strategies)
			{
				int newScore = 0;
				newScore -= get(losses,strat.code);
				newScore += get(wins, strat.code)/4;
				System.out.println("score with " + strat.code + ": " + newScore);
				if (newScore > score)
				{
					score = newScore;
					choice = strat;
				}
			}
		}
		else
		{
			float score = -1;
			for (Strategy strat : strategies)
			{
				float newScore = (float)get(recentWins, strat.code) / ((float)get(recentLosses,strat.code) + (float)get(recentWins,strat.code));
				if (newScore > score)
				{
					score = newScore;
					choice = strat;
				}
			}
		}
		
		DebugMessages.log("Chosen strategy: " + choice.code);
		
		return choice;
	}
	
	private static void inc(HashMap<String, Integer> map, String key)
	{
		if (!map.containsKey(key))
			map.put(key, 0);
		map.put(key, map.get(key) + 1);
	}
	
	private static int get(HashMap<String, Integer> map, String key)
	{
		return map.containsKey(key)?map.get(key):0;
	}
	
	/**
	 * Determine the strategies from which we will choose the best one.
	 * This method assumes that the getFlags() method has been called previously.
	 * @return Array with possible strategies.
	 */
	private static Strategy[] determineStrategies()
	{
		if (Tyr.self.getRace() == Race.Terran)
		{
			if (!flagSet("NZ") && lost)
			{
				if (Tyr.game.enemy().getRace() == Race.Protoss)
				{
					System.out.println("Countering zealots.");
					return new Strategy[] {
							new Strategy("ANTIZ", new TvPGeneric(), ScoutGroup.zealotPush),
							new Strategy("VPUSH", new TAntiZealot(true), ScoutGroup.zealotPush)
						};
				}
				else if (Tyr.game.enemy().getRace() == Race.Zerg)
				{
					return new Strategy[] {
							new Strategy("ANTIZ", new Bio(Bio.DEFENSIVE))
						};
				}
			}
			
			if (Tyr.game.enemy().getRace() == Race.Protoss)
			{
				return new Strategy[] {
						new Strategy("TvP", new TvPGeneric()),
						new Strategy("MPUSH", new TAntiZealot(true, false, true, true)),
						new Strategy("FE", new TFastExpand(new TvPGeneric())),
						new Strategy("BBS", new BBS()),
					};
			}
			else if (Tyr.game.enemy().getRace() == Race.Terran)
			{
				return new Strategy[] {
						new Strategy("TvP", new TvPGeneric()),
						new Strategy("MPUSH", new TAntiZealot(true, false, true, true)),
						new Strategy("FE", new TFastExpand(new TvPGeneric())),
						new Strategy("BBS", new BBS()),
					};
			}
			else if (Tyr.game.enemy().getRace() == Race.Zerg)
			{
				return new Strategy[] {
						new Strategy("BIODEF", new Bio(Bio.DEFENSIVE)),
						new Strategy("BIOFE", new Bio(Bio.GREEDY)),
						new Strategy("BBS", new BBS()),
					};
			}
			else
			{
				return new Strategy[] {
						new Strategy("DEF", new DefensiveTerran())
					};
			}
		}
		else if (Tyr.self.getRace() == Race.Protoss)
		{
			
			final boolean hideBuilds = false;
			if (hideBuilds)
				return new Strategy[] {
					new Strategy("FE", new ProtossFE(ProtossFE.STANDARD, false)),
					new Strategy("CARRIER", new CarrierRush(false, true)),
					new Strategy("DT", new DTPush()),
					new Strategy("PROXYZP", new ZealotPush(true, false)),
					new Strategy("CANNONS", new CannonWall(false)),
					new Strategy("ZP", new ZealotPush(false, false)),
				};
			
			if (Tyr.game.mapFileName().contains("Plasma"))
			{
				return new Strategy[] {
						new Strategy("CARRIER", new CarrierRush(true, false)),
						new Strategy("CARRIERDRAGOON", new CarrierRush())
				};
			}
			if (Tyr.game.mapFileName().contains("BlueStorm"))
			{
				return new Strategy[] {
						new Strategy("CARRIER", new CarrierRush(false, true)),
						new Strategy("ZP", new ZealotPush(false, false, true)),
						new Strategy("FEZEALOTS", new ProtossFE(ProtossFE.ZEALOT_ONLY, false))
				};
			}
			if (Tyr.game.enemy().getRace() == Race.Terran)
			{
				return new Strategy[] {
					new Strategy("CANNONS", new CannonWall(false)),
					new Strategy("PvR", new PvT(25)),
					new Strategy("ZP", new ZealotPush(false, false)),
					new Strategy("FECANNONS", new CannonWall(true))
				};
			}
			else if (Tyr.game.enemy().getRace() == Race.Protoss)
			{
				return new Strategy[] {
					new Strategy("CANNONS", new CannonWall(false)),
					new Strategy("PvR", new PvT(25)),
					new Strategy("ZP", new ZealotPush(false, false)),
					new Strategy("FECANNONS", new CannonWall(true))
				};
			} else if (Tyr.game.enemy().getRace() == Race.Zerg)
			{
				return new Strategy[] {
					new Strategy("CANNONS", new CannonWall(false)),
					new Strategy("PvR", new PvT(25)),
					new Strategy("ZP", new ZealotPush(false, false)),
					new Strategy("FECANNONS", new CannonWall(true))
				};
			}
			else
			{
				return new Strategy[] {
					new Strategy("CANNONS", new CannonWall(false)),
					new Strategy("PvR", new PvT(25)),
					new Strategy("ZP", new ZealotPush(false, false)),
					new Strategy("FECANNONS", new CannonWall(true))
				};
			}
		}
		else
		{
			return new Strategy[] {
					new Strategy("STANDARD", new StandardZerg())
				};
		}
	}
	
	/**
	 * Reads the flags from the file.
	 * Also determines whether we have lost before.
	 */
	public static void getFlags()
	{
		for (String line : file)
		{
			if (line.startsWith("flag:"))
			{
				System.out.println("flag read: " + line.substring(5));
				oldFlags.add(line.substring(5));
				continue;
			}
			
			String[] words = line.split(" ");
			for (String word : words)
				if (word.equals("loss"))
					lost = true;
		}
	}
	
	/**
	 * Has a certain flag been set for the opponent?
	 * @param flag The flag to check.
	 * @return Has a certain flag been set for the opponent?
	 */
	public static boolean flagSet(String flag)
	{
		return oldFlags.contains(flag) || newFlags.contains(flag);
	}
	
	/**
	 * Add a flag to the list of flags for this opponent.
	 * @param flag The flag to be added.
	 */
	public static void addFlag(String flag)
	{
		if (!flagSet(flag))
		{
			System.out.println("flag set: " + flag);
			newFlags.add(flag);
		}
	}
	
	/**
	 * Writes all new flags to file.
	 */
	public static void writeFlags()
	{
		for (String flag : newFlags)
		{
			System.out.println("storing flag: " + flag);
			DebugMessages.saveMessage("flag:" + flag);
			oldFlags.add(flag);
		}
		newFlags = new ArrayList<String>();
	}
	
	/**
	 * Set this strategy to be used.
	 */
	public void set()
	{
		chosenStrategy = this;
		
		// If this profile indeed matches the opponent, we set the settings to counter him.
		Tyr.bot.build = counter;
		Tyr.bot.scout.opponentStrategy = opponentStrategy;
		if(nscouts != 0)
		{
			Tyr.bot.scout.nscouts = nscouts;
			Tyr.bot.scout.workerScoutTiming = scoutTiming;
		}

		for (Task task : tasks)
			Tyr.bot.taskManager.potentialTasks.add(task);
	}
}
