package games.strategy.triplea.ai.proAI.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import games.strategy.engine.data.GameData;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.Territory;
import games.strategy.engine.data.Unit;
import games.strategy.triplea.ai.proAI.ProData;
import games.strategy.triplea.ai.proAI.data.ProTerritory;
import games.strategy.triplea.attachments.UnitAttachment;
import games.strategy.triplea.delegate.DiceRoll;
import games.strategy.triplea.delegate.Matches;
import games.strategy.triplea.delegate.TerritoryEffectHelper;
import games.strategy.triplea.delegate.UnitBattleComparator;

/**
 * Pro AI attack options utilities.
 */
public class ProSortMoveOptionsUtils {

  public static Map<Unit, Set<Territory>> sortUnitMoveOptions(final Map<Unit, Set<Territory>> unitAttackOptions) {

    final List<Map.Entry<Unit, Set<Territory>>> list = new LinkedList<>(unitAttackOptions.entrySet());
    Collections.sort(list, (o1, o2) -> {

      // Sort by number of move options then cost of unit then unit type
      if (o1.getValue().size() != o2.getValue().size()) {
        return (o1.getValue().size() - o2.getValue().size());
      } else if (ProData.unitValueMap.getInt(o1.getKey().getType()) != ProData.unitValueMap
          .getInt(o2.getKey().getType())) {
        return (ProData.unitValueMap.getInt(o1.getKey().getType())
            - ProData.unitValueMap.getInt(o2.getKey().getType()));
      }
      return o1.getKey().getType().getName().compareTo(o2.getKey().getType().getName());
    });
    final Map<Unit, Set<Territory>> sortedUnitAttackOptions = new LinkedHashMap<>();
    for (final Map.Entry<Unit, Set<Territory>> entry : list) {
      sortedUnitAttackOptions.put(entry.getKey(), entry.getValue());
    }
    return sortedUnitAttackOptions;
  }

  public static Map<Unit, Set<Territory>> sortUnitNeededOptions(final PlayerID player,
      final Map<Unit, Set<Territory>> unitAttackOptions, final Map<Territory, ProTerritory> attackMap,
      final ProOddsCalculator calc) {
    final GameData data = ProData.getData();

    final List<Map.Entry<Unit, Set<Territory>>> list = new LinkedList<>(unitAttackOptions.entrySet());
    Collections.sort(list, (o1, o2) -> {

      // Find number of territories that still need units
      int numOptions1 = 0;
      for (final Territory t : o1.getValue()) {
        final ProTerritory patd = attackMap.get(t);
        if (patd.getBattleResult() == null) {
          patd.setBattleResult(calc.estimateAttackBattleResults(t, patd.getUnits(),
              patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
        }
        if (!patd.isCurrentlyWins()) {
          numOptions1++;
        }
      }
      int numOptions2 = 0;
      for (final Territory t : o2.getValue()) {
        final ProTerritory patd = attackMap.get(t);
        if (patd.getBattleResult() == null) {
          patd.setBattleResult(calc.estimateAttackBattleResults(t, patd.getUnits(),
              patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
        }
        if (!patd.isCurrentlyWins()) {
          numOptions2++;
        }
      }

      // Sort by number of move options then cost of unit then unit type
      if (numOptions1 != numOptions2) {
        return (numOptions1 - numOptions2);
      }
      if (ProData.unitValueMap.getInt(o1.getKey().getType()) != ProData.unitValueMap.getInt(o2.getKey().getType())) {
        return (ProData.unitValueMap.getInt(o1.getKey().getType())
            - ProData.unitValueMap.getInt(o2.getKey().getType()));
      }
      return o1.getKey().getType().getName().compareTo(o2.getKey().getType().getName());
    });
    final Map<Unit, Set<Territory>> sortedUnitAttackOptions = new LinkedHashMap<>();
    for (final Map.Entry<Unit, Set<Territory>> entry : list) {
      sortedUnitAttackOptions.put(entry.getKey(), entry.getValue());
    }
    return sortedUnitAttackOptions;
  }

  public static Map<Unit, Set<Territory>> sortUnitNeededOptionsThenAttack(final PlayerID player,
      final Map<Unit, Set<Territory>> unitAttackOptions, final Map<Territory, ProTerritory> attackMap,
      final Map<Unit, Territory> unitTerritoryMap, final ProOddsCalculator calc) {
    final GameData data = ProData.getData();

    final List<Map.Entry<Unit, Set<Territory>>> list = new LinkedList<>(unitAttackOptions.entrySet());
    Collections.sort(list, (o1, o2) -> {

      // Sort by number of territories that still need units
      int numOptions1 = 0;
      for (final Territory t : o1.getValue()) {
        final ProTerritory patd = attackMap.get(t);
        if (patd.getBattleResult() == null) {
          patd.setBattleResult(calc.estimateAttackBattleResults(t, patd.getUnits(),
              patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
        }
        if (!patd.isCurrentlyWins()) {
          numOptions1++;
        }
      }
      int numOptions2 = 0;
      for (final Territory t : o2.getValue()) {
        final ProTerritory patd = attackMap.get(t);
        if (patd.getBattleResult() == null) {
          patd.setBattleResult(calc.estimateAttackBattleResults(t, patd.getUnits(),
              patd.getMaxEnemyDefenders(player, data), patd.getBombardTerritoryMap().keySet()));
        }
        if (!patd.isCurrentlyWins()) {
          numOptions2++;
        }
      }
      if (numOptions1 != numOptions2) {
        return (numOptions1 - numOptions2);
      }
      if (numOptions1 == 0) {
        return 0;
      }

      // Sort by attack efficiency
      int minPower1 = Integer.MAX_VALUE;
      for (final Territory t : o1.getValue()) {
        if (!attackMap.get(t).isCurrentlyWins()) {
          final List<Unit> defendingUnits = t.getUnits().getMatches(Matches.enemyUnit(player, data));
          final List<Unit> sortedUnitsList = new ArrayList<>(attackMap.get(t).getUnits());
          Collections.sort(sortedUnitsList, new UnitBattleComparator(false, ProData.unitValueMap,
              TerritoryEffectHelper.getEffects(t), data, false, false));
          Collections.reverse(sortedUnitsList);
          final int powerWithout =
              DiceRoll.getTotalPower(DiceRoll.getUnitPowerAndRollsForNormalBattles(sortedUnitsList, defendingUnits,
                  false, false, data, t, TerritoryEffectHelper.getEffects(t), false, null), data);
          sortedUnitsList.add(o1.getKey());
          Collections.sort(sortedUnitsList, new UnitBattleComparator(false, ProData.unitValueMap,
              TerritoryEffectHelper.getEffects(t), data, false, false));
          Collections.reverse(sortedUnitsList);
          final int powerWith = DiceRoll.getTotalPower(DiceRoll.getUnitPowerAndRollsForNormalBattles(sortedUnitsList,
              defendingUnits, false, false, data, t, TerritoryEffectHelper.getEffects(t), false, null), data);
          final int power = powerWith - powerWithout;
          if (power < minPower1) {
            minPower1 = power;
          }
        }
      }
      final UnitAttachment ua1 = UnitAttachment.get(o1.getKey().getType());
      if (ua1.getIsAir()) {
        minPower1 *= 10;
      }
      final double attackEfficiency1 = (double) minPower1 / ProData.unitValueMap.getInt(o1.getKey().getType());
      int minPower2 = Integer.MAX_VALUE;
      for (final Territory t : o2.getValue()) {
        if (!attackMap.get(t).isCurrentlyWins()) {
          final List<Unit> defendingUnits = t.getUnits().getMatches(Matches.enemyUnit(player, data));
          final List<Unit> sortedUnitsList = new ArrayList<>(attackMap.get(t).getUnits());
          Collections.sort(sortedUnitsList, new UnitBattleComparator(false, ProData.unitValueMap,
              TerritoryEffectHelper.getEffects(t), data, false, false));
          Collections.reverse(sortedUnitsList);
          final int powerWithout =
              DiceRoll.getTotalPower(DiceRoll.getUnitPowerAndRollsForNormalBattles(sortedUnitsList, defendingUnits,
                  false, false, data, t, TerritoryEffectHelper.getEffects(t), false, null), data);
          sortedUnitsList.add(o2.getKey());
          Collections.sort(sortedUnitsList, new UnitBattleComparator(false, ProData.unitValueMap,
              TerritoryEffectHelper.getEffects(t), data, false, false));
          Collections.reverse(sortedUnitsList);
          final int powerWith = DiceRoll.getTotalPower(DiceRoll.getUnitPowerAndRollsForNormalBattles(sortedUnitsList,
              defendingUnits, false, false, data, t, TerritoryEffectHelper.getEffects(t), false, null), data);
          final int power = powerWith - powerWithout;
          if (power < minPower2) {
            minPower2 = power;
          }
        }
      }
      final UnitAttachment ua2 = UnitAttachment.get(o2.getKey().getType());
      if (ua2.getIsAir()) {
        minPower2 *= 10;
      }
      final double attackEfficiency2 = (double) minPower2 / ProData.unitValueMap.getInt(o2.getKey().getType());
      if (attackEfficiency1 != attackEfficiency2) {
        if (attackEfficiency1 < attackEfficiency2) {
          return 1;
        } else {
          return -1;
        }
      }

      // Check if unit types are equal and is air then sort by average distance
      if (o1.getKey().getType().equals(o2.getKey().getType())) {
        final boolean isAirUnit = UnitAttachment.get(o1.getKey().getType()).getIsAir();
        if (isAirUnit) {
          int distance1 = 0;
          for (final Territory t : o1.getValue()) {
            if (!attackMap.get(t).isCurrentlyWins()) {
              distance1 += data.getMap().getDistance_IgnoreEndForCondition(unitTerritoryMap.get(o1.getKey()), t,
                  ProMatches.territoryCanMoveAirUnitsAndNoAa(player, data, true));
            }
          }
          int distance2 = 0;
          for (final Territory t : o2.getValue()) {
            if (!attackMap.get(t).isCurrentlyWins()) {
              distance2 += data.getMap().getDistance_IgnoreEndForCondition(unitTerritoryMap.get(o2.getKey()), t,
                  ProMatches.territoryCanMoveAirUnitsAndNoAa(player, data, true));
            }
          }
          if (distance1 != distance2) {
            return distance1 - distance2;
          }
        }
      }
      return o1.getKey().getType().getName().compareTo(o2.getKey().getType().getName());
    });
    final Map<Unit, Set<Territory>> sortedUnitAttackOptions = new LinkedHashMap<>();
    for (final Map.Entry<Unit, Set<Territory>> entry : list) {
      sortedUnitAttackOptions.put(entry.getKey(), entry.getValue());
    }
    return sortedUnitAttackOptions;
  }

}
