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

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import games.strategy.engine.data.Change;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.RelationshipTracker;
import games.strategy.engine.data.Territory;
import games.strategy.engine.data.Unit;
import games.strategy.engine.data.changefactory.ChangeFactory;
import games.strategy.engine.delegate.IDelegateBridge;
import games.strategy.triplea.ai.proAI.ProData;
import games.strategy.triplea.ai.proAI.data.ProBattleResult;
import games.strategy.triplea.ai.proAI.data.ProTerritory;
import games.strategy.triplea.ai.proAI.logging.ProLogger;
import games.strategy.triplea.ai.proAI.util.ProMatches;
import games.strategy.triplea.ai.proAI.util.ProOddsCalculator;
import games.strategy.triplea.attachments.TerritoryAttachment;
import games.strategy.triplea.delegate.BattleDelegate;
import games.strategy.triplea.delegate.BattleTracker;
import games.strategy.triplea.delegate.DelegateFinder;
import games.strategy.triplea.delegate.IBattle;
import games.strategy.triplea.delegate.IBattle.BattleType;
import games.strategy.triplea.delegate.Matches;
import games.strategy.triplea.delegate.OriginalOwnerTracker;
import games.strategy.triplea.delegate.TransportTracker;
import games.strategy.util.Match;

/**
 * Pro AI simulate turn utilities.
 */
public class ProSimulateTurnUtils {

  public static void simulateBattles(final GameData data, final PlayerID player, final IDelegateBridge delegateBridge,
      final ProOddsCalculator calc) {

    ProLogger.info("Starting battle simulation phase");

    final BattleDelegate battleDelegate = DelegateFinder.battleDelegate(data);
    final Map<BattleType, Collection<Territory>> battleTerritories = battleDelegate.getBattles().getBattles();
    for (final Entry<BattleType, Collection<Territory>> entry : battleTerritories.entrySet()) {
      for (final Territory t : entry.getValue()) {
        final IBattle battle =
            battleDelegate.getBattleTracker().getPendingBattle(t, entry.getKey().isBombingRun(), entry.getKey());
        final List<Unit> attackers = (List<Unit>) battle.getAttackingUnits();
        attackers.retainAll(t.getUnits().getUnits());
        final List<Unit> defenders = (List<Unit>) battle.getDefendingUnits();
        defenders.retainAll(t.getUnits().getUnits());
        final Set<Unit> bombardingUnits = new HashSet<>(battle.getBombardingUnits());
        ProLogger.debug("---" + t);
        ProLogger.debug("attackers=" + attackers);
        ProLogger.debug("defenders=" + defenders);
        ProLogger.debug("bombardingUnits=" + bombardingUnits);
        final ProBattleResult result = calc.callBattleCalculator(player, t, attackers, defenders, bombardingUnits);
        final List<Unit> remainingUnits = result.getAverageAttackersRemaining();
        ProLogger.debug("remainingUnits=" + remainingUnits);

        // Make updates to data
        final List<Unit> attackersToRemove = new ArrayList<>(attackers);
        attackersToRemove.removeAll(remainingUnits);
        final List<Unit> defendersToRemove = Match.getMatches(defenders, Matches.UnitIsInfrastructure.invert());
        final List<Unit> infrastructureToChangeOwner = Match.getMatches(defenders, Matches.UnitIsInfrastructure);
        ProLogger.debug("attackersToRemove=" + attackersToRemove);
        ProLogger.debug("defendersToRemove=" + defendersToRemove);
        ProLogger.debug("infrastructureToChangeOwner=" + infrastructureToChangeOwner);
        final Change attackerskilledChange = ChangeFactory.removeUnits(t, attackersToRemove);
        delegateBridge.addChange(attackerskilledChange);
        final Change defenderskilledChange = ChangeFactory.removeUnits(t, defendersToRemove);
        delegateBridge.addChange(defenderskilledChange);
        BattleTracker.captureOrDestroyUnits(t, player, player, delegateBridge, null);
        if (!checkIfCapturedTerritoryIsAlliedCapital(t, data, player, delegateBridge)) {
          delegateBridge.addChange(ChangeFactory.changeOwner(t, player));
        }
        battleDelegate.getBattleTracker().getConquered().add(t);
        battleDelegate.getBattleTracker().removeBattle(battle);
        final Territory updatedTerritory = data.getMap().getTerritory(t.getName());
        ProLogger.debug(
            "after changes owner=" + updatedTerritory.getOwner() + ", units=" + updatedTerritory.getUnits().getUnits());
      }
    }
  }

  public static Map<Territory, ProTerritory> transferMoveMap(final Map<Territory, ProTerritory> moveMap,
      final GameData toData, final PlayerID player) {

    ProLogger.info("Transferring move map");

    final Map<Unit, Territory> unitTerritoryMap = ProData.unitTerritoryMap;

    final Map<Territory, ProTerritory> result = new HashMap<>();
    final List<Unit> usedUnits = new ArrayList<>();
    for (final Territory fromTerritory : moveMap.keySet()) {
      final Territory toTerritory = toData.getMap().getTerritory(fromTerritory.getName());
      final ProTerritory patd = new ProTerritory(toTerritory);
      result.put(toTerritory, patd);
      final Map<Unit, List<Unit>> amphibAttackMap = moveMap.get(fromTerritory).getAmphibAttackMap();
      final Map<Unit, Boolean> isTransportingMap = moveMap.get(fromTerritory).getIsTransportingMap();
      final Map<Unit, Territory> transportTerritoryMap = moveMap.get(fromTerritory).getTransportTerritoryMap();
      final Map<Unit, Territory> bombardMap = moveMap.get(fromTerritory).getBombardTerritoryMap();
      ProLogger.debug("Transferring " + fromTerritory + " to " + toTerritory);
      final List<Unit> amphibUnits = new ArrayList<>();
      for (final Unit transport : amphibAttackMap.keySet()) {
        Unit toTransport = null;
        final List<Unit> toUnits = new ArrayList<>();
        if (isTransportingMap.get(transport)) {
          toTransport = transferLoadedTransport(transport, amphibAttackMap.get(transport), unitTerritoryMap, usedUnits,
              toData, player);
          toUnits.addAll(TransportTracker.transporting(toTransport));
        } else {
          toTransport = transferUnit(transport, unitTerritoryMap, usedUnits, toData, player);
          for (final Unit u : amphibAttackMap.get(transport)) {
            final Unit toUnit = transferUnit(u, unitTerritoryMap, usedUnits, toData, player);
            toUnits.add(toUnit);
          }
        }
        patd.addUnits(toUnits);
        patd.putAmphibAttackMap(toTransport, toUnits);
        amphibUnits.addAll(amphibAttackMap.get(transport));
        if (transportTerritoryMap.get(transport) != null) {
          patd.getTransportTerritoryMap().put(toTransport,
              toData.getMap().getTerritory(transportTerritoryMap.get(transport).getName()));
        }
        ProLogger.trace("---Transferring transport=" + transport + " with units=" + amphibAttackMap.get(transport)
            + " unloadTerritory=" + transportTerritoryMap.get(transport) + " to transport=" + toTransport
            + " with units=" + toUnits + " unloadTerritory=" + patd.getTransportTerritoryMap().get(toTransport));
      }
      for (final Unit u : moveMap.get(fromTerritory).getUnits()) {
        if (!amphibUnits.contains(u)) {
          final Unit toUnit = transferUnit(u, unitTerritoryMap, usedUnits, toData, player);
          patd.addUnit(toUnit);
          ProLogger.trace("---Transferring unit " + u + " to " + toUnit);
        }
      }
      for (final Unit u : moveMap.get(fromTerritory).getBombers()) {
        final Unit toUnit = transferUnit(u, unitTerritoryMap, usedUnits, toData, player);
        patd.getBombers().add(toUnit);
        ProLogger.trace("---Transferring bomber " + u + " to " + toUnit);
      }
      for (final Unit u : bombardMap.keySet()) {
        final Unit toUnit = transferUnit(u, unitTerritoryMap, usedUnits, toData, player);
        patd.getBombardTerritoryMap().put(toUnit, toData.getMap().getTerritory(bombardMap.get(u).getName()));
        ProLogger.trace("---Transferring bombard=" + u + ", bombardFromTerritory=" + bombardMap.get(u) + " to bomard="
            + toUnit + ", bombardFromTerritory=" + patd.getBombardTerritoryMap().get(toUnit));
      }
    }
    return result;
  }

  private static boolean checkIfCapturedTerritoryIsAlliedCapital(final Territory t, final GameData data,
      final PlayerID player, final IDelegateBridge delegateBridge) {

    final PlayerID terrOrigOwner = OriginalOwnerTracker.getOriginalOwner(t);
    final RelationshipTracker relationshipTracker = data.getRelationshipTracker();
    final TerritoryAttachment ta = TerritoryAttachment.get(t);
    if (ta != null && ta.getCapital() != null && terrOrigOwner != null
        && TerritoryAttachment.getAllCapitals(terrOrigOwner, data).contains(t)
        && relationshipTracker.isAllied(terrOrigOwner, player)) {

      // Give capital and any allied territories back to original owner
      final Collection<Territory> originallyOwned = OriginalOwnerTracker.getOriginallyOwned(data, terrOrigOwner);
      final List<Territory> friendlyTerritories =
          Match.getMatches(originallyOwned, Matches.isTerritoryAllied(terrOrigOwner, data));
      friendlyTerritories.add(t);
      for (final Territory item : friendlyTerritories) {
        if (item.getOwner() == terrOrigOwner) {
          continue;
        }
        final Change takeOverFriendlyTerritories = ChangeFactory.changeOwner(item, terrOrigOwner);
        delegateBridge.addChange(takeOverFriendlyTerritories);
        final Collection<Unit> units = Match.getMatches(item.getUnits().getUnits(), Matches.UnitIsInfrastructure);
        if (!units.isEmpty()) {
          final Change takeOverNonComUnits = ChangeFactory.changeOwner(units, terrOrigOwner, t);
          delegateBridge.addChange(takeOverNonComUnits);
        }
      }
      return true;
    }
    return false;
  }

  private static Unit transferUnit(final Unit u, final Map<Unit, Territory> unitTerritoryMap,
      final List<Unit> usedUnits, final GameData toData, final PlayerID player) {

    final Territory unitTerritory = unitTerritoryMap.get(u);
    final List<Unit> toUnits = toData.getMap().getTerritory(unitTerritory.getName()).getUnits()
        .getMatches(ProMatches.unitIsOwnedAndMatchesTypeAndNotTransporting(player, u.getType()));
    for (final Unit toUnit : toUnits) {
      if (!usedUnits.contains(toUnit)) {
        usedUnits.add(toUnit);
        return toUnit;
      }
    }
    return null;
  }

  private static Unit transferLoadedTransport(final Unit transport, final List<Unit> transportingUnits,
      final Map<Unit, Territory> unitTerritoryMap, final List<Unit> usedUnits, final GameData toData,
      final PlayerID player) {

    final Territory unitTerritory = unitTerritoryMap.get(transport);
    final List<Unit> toTransports = toData.getMap().getTerritory(unitTerritory.getName()).getUnits()
        .getMatches(ProMatches.unitIsOwnedAndMatchesTypeAndIsTransporting(player, transport.getType()));
    for (final Unit toTransport : toTransports) {
      if (!usedUnits.contains(toTransport)) {
        final List<Unit> toTransportingUnits = (List<Unit>) TransportTracker.transporting(toTransport);
        if (transportingUnits.size() == toTransportingUnits.size()) {
          boolean canTransfer = true;
          for (int i = 0; i < transportingUnits.size(); i++) {
            if (!transportingUnits.get(i).getType().equals(toTransportingUnits.get(i).getType())) {
              canTransfer = false;
              break;
            }
          }
          if (canTransfer) {
            usedUnits.add(toTransport);
            usedUnits.addAll(toTransportingUnits);
            return toTransport;
          }
        }
      }
    }
    return null;
  }
}
