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

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import games.strategy.engine.data.GameData;
import games.strategy.engine.data.NamedAttachable;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.ProductionFrontier;
import games.strategy.engine.data.ProductionRule;
import games.strategy.engine.data.UnitType;
import games.strategy.triplea.ai.proAI.logging.ProLogger;
import games.strategy.triplea.attachments.UnitAttachment;
import games.strategy.triplea.delegate.Matches;

public class ProPurchaseOptionMap {

  private final List<ProPurchaseOption> landFodderOptions;
  private final List<ProPurchaseOption> landAttackOptions;
  private final List<ProPurchaseOption> landDefenseOptions;
  private final List<ProPurchaseOption> airOptions;
  private final List<ProPurchaseOption> seaDefenseOptions;
  private final List<ProPurchaseOption> seaTransportOptions;
  private final List<ProPurchaseOption> seaCarrierOptions;
  private final List<ProPurchaseOption> seaSubOptions;
  private final List<ProPurchaseOption> aaOptions;
  private final List<ProPurchaseOption> factoryOptions;
  private final List<ProPurchaseOption> specialOptions;

  public ProPurchaseOptionMap(final PlayerID player, final GameData data) {

    ProLogger.info("Purchase Options");

    // Initialize lists
    landFodderOptions = new ArrayList<>();
    landAttackOptions = new ArrayList<>();
    landDefenseOptions = new ArrayList<>();
    airOptions = new ArrayList<>();
    seaDefenseOptions = new ArrayList<>();
    seaTransportOptions = new ArrayList<>();
    seaCarrierOptions = new ArrayList<>();
    seaSubOptions = new ArrayList<>();
    aaOptions = new ArrayList<>();
    factoryOptions = new ArrayList<>();
    specialOptions = new ArrayList<>();

    // Add each production rule to appropriate list(s)
    final ProductionFrontier productionFrontier = player.getProductionFrontier();
    if (productionFrontier == null || productionFrontier.getRules() == null) {
      return;
    }
    for (final ProductionRule rule : productionFrontier.getRules()) {

      // Check if rule is for a unit
      final NamedAttachable resourceOrUnit = rule.getResults().keySet().iterator().next();
      if (!(resourceOrUnit instanceof UnitType)) {
        continue;
      }
      final UnitType unitType = (UnitType) resourceOrUnit;

      // Add rule to appropriate purchase option list
      if ((UnitAttachment.get(unitType).getMovement(player) <= 0
          && !(UnitAttachment.get(unitType).getCanProduceUnits()))
          || Matches.unitTypeConsumesUnitsOnCreation().match(unitType) || UnitAttachment.get(unitType).getIsSuicide()) {
        final ProPurchaseOption ppo = new ProPurchaseOption(rule, unitType, player, data);
        specialOptions.add(ppo);
        ProLogger.debug("Special: " + ppo);
      } else if (Matches.unitTypeCanProduceUnits().match(unitType)
          && Matches.unitTypeIsInfrastructure().match(unitType)) {
        final ProPurchaseOption ppo = new ProPurchaseOption(rule, unitType, player, data);
        factoryOptions.add(ppo);
        ProLogger.debug("Factory: " + ppo);
      } else if (Matches.unitTypeIsAaForBombingThisUnitOnly().match(unitType)) {
        final ProPurchaseOption ppo = new ProPurchaseOption(rule, unitType, player, data);
        aaOptions.add(ppo);
        ProLogger.debug("AA: " + ppo);
      } else if (Matches.unitTypeIsLand().match(unitType)) {
        final ProPurchaseOption ppo = new ProPurchaseOption(rule, unitType, player, data);
        landFodderOptions.add(ppo);
        if (ppo.getAttack() >= ppo.getDefense() || ppo.isAttackSupport() || ppo.getMovement() > 1) {
          landAttackOptions.add(ppo);
        }
        if (ppo.getDefense() >= ppo.getAttack() || ppo.isDefenseSupport() || ppo.getMovement() > 1) {
          landDefenseOptions.add(ppo);
        }
        ProLogger.debug("Land: " + ppo);
      } else if (Matches.unitTypeIsAir().match(unitType)) {
        final ProPurchaseOption ppo = new ProPurchaseOption(rule, unitType, player, data);
        airOptions.add(ppo);
        ProLogger.debug("Air: " + ppo);
      } else if (Matches.unitTypeIsSea().match(unitType)) {
        final ProPurchaseOption ppo = new ProPurchaseOption(rule, unitType, player, data);
        if (!ppo.isSub()) {
          seaDefenseOptions.add(ppo);
        }
        if (ppo.isTransport()) {
          seaTransportOptions.add(ppo);
        }
        if (ppo.isCarrier()) {
          seaCarrierOptions.add(ppo);
        }
        if (ppo.isSub()) {
          seaSubOptions.add(ppo);
        }
        ProLogger.debug("Sea: " + ppo);
      }
    }
    if (landAttackOptions.isEmpty()) {
      landAttackOptions.addAll(landDefenseOptions);
    }
    if (landDefenseOptions.isEmpty()) {
      landDefenseOptions.addAll(landAttackOptions);
    }

    // Print categorized options
    ProLogger.info("Purchase Categories");
    logOptions(landFodderOptions, "Land Fodder Options: ");
    logOptions(landAttackOptions, "Land Attack Options: ");
    logOptions(landDefenseOptions, "Land Defense Options: ");
    logOptions(airOptions, "Air Options: ");
    logOptions(seaDefenseOptions, "Sea Defense Options: ");
    logOptions(seaTransportOptions, "Sea Transport Options: ");
    logOptions(seaCarrierOptions, "Sea Carrier Options: ");
    logOptions(seaSubOptions, "Sea Sub Options: ");
    logOptions(aaOptions, "AA Options: ");
    logOptions(factoryOptions, "Factory Options: ");
    logOptions(specialOptions, "Special Options: ");
  }

  public List<ProPurchaseOption> getAllOptions() {
    final Set<ProPurchaseOption> allOptions = new HashSet<>();
    allOptions.addAll(getLandOptions());
    allOptions.addAll(airOptions);
    allOptions.addAll(getSeaOptions());
    allOptions.addAll(aaOptions);
    allOptions.addAll(factoryOptions);
    allOptions.addAll(specialOptions);
    return new ArrayList<>(allOptions);
  }

  public List<ProPurchaseOption> getLandOptions() {
    final Set<ProPurchaseOption> landOptions = new HashSet<>();
    landOptions.addAll(landFodderOptions);
    landOptions.addAll(landAttackOptions);
    landOptions.addAll(landDefenseOptions);
    return new ArrayList<>(landOptions);
  }

  private List<ProPurchaseOption> getSeaOptions() {
    final Set<ProPurchaseOption> seaOptions = new HashSet<>();
    seaOptions.addAll(seaDefenseOptions);
    seaOptions.addAll(seaTransportOptions);
    seaOptions.addAll(seaCarrierOptions);
    seaOptions.addAll(seaSubOptions);
    return new ArrayList<>(seaOptions);
  }

  public List<ProPurchaseOption> getLandFodderOptions() {
    return landFodderOptions;
  }

  public List<ProPurchaseOption> getLandAttackOptions() {
    return landAttackOptions;
  }

  public List<ProPurchaseOption> getLandDefenseOptions() {
    return landDefenseOptions;
  }

  public List<ProPurchaseOption> getAirOptions() {
    return airOptions;
  }

  public List<ProPurchaseOption> getSeaDefenseOptions() {
    return seaDefenseOptions;
  }

  public List<ProPurchaseOption> getSeaTransportOptions() {
    return seaTransportOptions;
  }

  public List<ProPurchaseOption> getSeaCarrierOptions() {
    return seaCarrierOptions;
  }

  public List<ProPurchaseOption> getSeaSubOptions() {
    return seaSubOptions;
  }

  public List<ProPurchaseOption> getAaOptions() {
    return aaOptions;
  }

  public List<ProPurchaseOption> getFactoryOptions() {
    return factoryOptions;
  }

  public List<ProPurchaseOption> getSpecialOptions() {
    return specialOptions;
  }

  private static void logOptions(final List<ProPurchaseOption> purchaseOptions, final String name) {
    final StringBuilder sb = new StringBuilder(name);
    for (final ProPurchaseOption ppo : purchaseOptions) {
      sb.append(ppo.getUnitType().getName());
      sb.append(", ");
    }
    sb.delete(sb.length() - 2, sb.length());
    ProLogger.debug(sb.toString());
  }
}
