package games.strategy.triplea.delegate;

import static games.strategy.triplea.delegate.BattleStepStrings.ATTACKER_WITHDRAW;
import static games.strategy.triplea.delegate.BattleStepStrings.FIRE;
import static games.strategy.triplea.delegate.BattleStepStrings.REMOVE_CASUALTIES;
import static games.strategy.triplea.delegate.BattleStepStrings.REMOVE_SNEAK_ATTACK_CASUALTIES;
import static games.strategy.triplea.delegate.BattleStepStrings.SELECT_CASUALTIES;
import static games.strategy.triplea.delegate.BattleStepStrings.SELECT_SUB_CASUALTIES;
import static games.strategy.triplea.delegate.BattleStepStrings.SUBS_FIRE;
import static games.strategy.triplea.delegate.BattleStepStrings.SUBS_SUBMERGE;
import static games.strategy.triplea.delegate.GameDataTestUtil.aaGun;
import static games.strategy.triplea.delegate.GameDataTestUtil.addTo;
import static games.strategy.triplea.delegate.GameDataTestUtil.americans;
import static games.strategy.triplea.delegate.GameDataTestUtil.armour;
import static games.strategy.triplea.delegate.GameDataTestUtil.battleDelegate;
import static games.strategy.triplea.delegate.GameDataTestUtil.bidPlaceDelegate;
import static games.strategy.triplea.delegate.GameDataTestUtil.bomber;
import static games.strategy.triplea.delegate.GameDataTestUtil.british;
import static games.strategy.triplea.delegate.GameDataTestUtil.carrier;
import static games.strategy.triplea.delegate.GameDataTestUtil.chinese;
import static games.strategy.triplea.delegate.GameDataTestUtil.destroyer;
import static games.strategy.triplea.delegate.GameDataTestUtil.factory;
import static games.strategy.triplea.delegate.GameDataTestUtil.fighter;
import static games.strategy.triplea.delegate.GameDataTestUtil.germans;
import static games.strategy.triplea.delegate.GameDataTestUtil.givePlayerRadar;
import static games.strategy.triplea.delegate.GameDataTestUtil.infantry;
import static games.strategy.triplea.delegate.GameDataTestUtil.italians;
import static games.strategy.triplea.delegate.GameDataTestUtil.load;
import static games.strategy.triplea.delegate.GameDataTestUtil.makeGameLowLuck;
import static games.strategy.triplea.delegate.GameDataTestUtil.move;
import static games.strategy.triplea.delegate.GameDataTestUtil.moveDelegate;
import static games.strategy.triplea.delegate.GameDataTestUtil.placeDelegate;
import static games.strategy.triplea.delegate.GameDataTestUtil.purchaseDelegate;
import static games.strategy.triplea.delegate.GameDataTestUtil.removeFrom;
import static games.strategy.triplea.delegate.GameDataTestUtil.russians;
import static games.strategy.triplea.delegate.GameDataTestUtil.submarine;
import static games.strategy.triplea.delegate.GameDataTestUtil.techDelegate;
import static games.strategy.triplea.delegate.GameDataTestUtil.territory;
import static games.strategy.triplea.delegate.GameDataTestUtil.transport;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;

import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import games.strategy.debug.ClientLogger;
import games.strategy.engine.data.Change;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.GameParseException;
import games.strategy.engine.data.ITestDelegateBridge;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.RepairRule;
import games.strategy.engine.data.Route;
import games.strategy.engine.data.TechnologyFrontier;
import games.strategy.engine.data.Territory;
import games.strategy.engine.data.TerritoryEffect;
import games.strategy.engine.data.Unit;
import games.strategy.engine.data.UnitType;
import games.strategy.engine.data.changefactory.ChangeFactory;
import games.strategy.engine.random.ScriptedRandomSource;
import games.strategy.triplea.Constants;
import games.strategy.triplea.TripleAUnit;
import games.strategy.triplea.attachments.TechAttachment;
import games.strategy.triplea.attachments.TerritoryAttachment;
import games.strategy.triplea.attachments.UnitAttachment;
import games.strategy.triplea.delegate.IBattle.BattleType;
import games.strategy.triplea.delegate.dataObjects.CasualtyDetails;
import games.strategy.triplea.delegate.dataObjects.CasualtyList;
import games.strategy.triplea.delegate.dataObjects.MoveValidationResult;
import games.strategy.triplea.delegate.dataObjects.PlaceableUnits;
import games.strategy.triplea.delegate.dataObjects.TechResults;
import games.strategy.triplea.delegate.remote.IAbstractPlaceDelegate;
import games.strategy.triplea.player.ITripleAPlayer;
import games.strategy.triplea.xml.TestMapGameData;
import games.strategy.util.IntegerMap;

public class WW2V3Year41Test {
  private GameData gameData;
  private final ITripleAPlayer dummyPlayer = mock(ITripleAPlayer.class);

  @Before
  public void setUp() throws Exception {
    when(dummyPlayer.selectCasualties(any(), any(), anyInt(), any(), any(), any(), any(), any(), any(),
        anyBoolean(), any(), any(), any(), any(), anyBoolean())).thenAnswer(new Answer<CasualtyDetails>() {
          @Override
          public CasualtyDetails answer(final InvocationOnMock invocation) throws Throwable {
            final CasualtyList defaultCasualties = invocation.getArgument(11);
            if (defaultCasualties != null) {
              return new CasualtyDetails(defaultCasualties.getKilled(), defaultCasualties.getDamaged(), true);
            }
            return null;
          }
        });
    gameData = TestMapGameData.WW2V3_1941.getGameData();
  }

  private ITestDelegateBridge getDelegateBridge(final PlayerID player) {
    return GameDataTestUtil.getDelegateBridge(player, gameData);
  }

  private static String fight(final BattleDelegate battle, final Territory territory) {
    for (final Entry<BattleType, Collection<Territory>> entry : battle.getBattles().getBattles().entrySet()) {
      if (!entry.getKey().isBombingRun() && entry.getValue().contains(territory)) {
        return battle.fightBattle(territory, false, entry.getKey());
      }
    }
    throw new IllegalStateException("Could not find battle in: " + territory.getName());
  }

  @Test
  public void testAaCasualtiesLowLuckMixedRadar() {
    // moved from BattleCalculatorTest because "revised" does not have "radar"
    final PlayerID british = GameDataTestUtil.british(gameData);
    final ITestDelegateBridge bridge = getDelegateBridge(british);
    makeGameLowLuck(gameData);
    // setSelectAACasualties(data, false);
    givePlayerRadar(germans(gameData));
    // 3 bombers and 3 fighters
    final Collection<Unit> planes = bomber(gameData).create(3, british(gameData));
    planes.addAll(fighter(gameData).create(3, british(gameData)));
    final Collection<Unit> defendingAa =
        territory("Germany", gameData).getUnits().getMatches(Matches.unitIsAaForAnything());
    // don't allow rolling, 6 of each is deterministic
    bridge.setRandomSource(new ScriptedRandomSource(new int[] {ScriptedRandomSource.ERROR}));
    final DiceRoll roll =
        DiceRoll.rollAa(
            Matches.getMatches(planes,
                Matches
                    .unitIsOfTypes(UnitAttachment.get(defendingAa.iterator().next().getType()).getTargetsAA(gameData))),
            defendingAa, bridge, territory("Germany", gameData), true);
    final Collection<Unit> casualties = BattleCalculator.getAaCasualties(false, planes, planes, defendingAa,
        defendingAa, roll, bridge, null, null, null, territory("Germany", gameData), null, false, null).getKilled();
    assertEquals(casualties.size(), 2);
    // should be 1 fighter and 1 bomber
    assertEquals(Matches.countMatches(casualties, Matches.unitIsStrategicBomber()), 1);
    assertEquals(Matches.countMatches(casualties, Matches.unitIsStrategicBomber().invert()), 1);
  }

  @Test
  public void testAaCasualtiesLowLuckMixedWithRollingRadar() {
    // moved from BattleCalculatorTest because "revised" does not have "radar"
    final PlayerID british = GameDataTestUtil.british(gameData);
    final ITestDelegateBridge bridge = getDelegateBridge(british);
    makeGameLowLuck(gameData);
    // setSelectAACasualties(data, false);
    givePlayerRadar(germans(gameData));
    // 4 bombers and 4 fighters
    final Collection<Unit> planes = bomber(gameData).create(4, british(gameData));
    planes.addAll(fighter(gameData).create(4, british(gameData)));
    final Collection<Unit> defendingAa =
        territory("Germany", gameData).getUnits().getMatches(Matches.unitIsAaForAnything());
    // 1 roll, a hit
    // then a dice to select the casualty
    final ScriptedRandomSource randomSource = new ScriptedRandomSource(new int[] {0, 1});
    bridge.setRandomSource(randomSource);
    final DiceRoll roll =
        DiceRoll.rollAa(
            Matches.getMatches(planes,
                Matches
                    .unitIsOfTypes(UnitAttachment.get(defendingAa.iterator().next().getType()).getTargetsAA(gameData))),
            defendingAa, bridge, territory("Germany", gameData), true);
    // make sure we rolled once
    assertEquals(1, randomSource.getTotalRolled());
    final Collection<Unit> casualties = BattleCalculator.getAaCasualties(false, planes, planes, defendingAa,
        defendingAa, roll, bridge, null, null, null, territory("Germany", gameData), null, false, null).getKilled();
    assertEquals(casualties.size(), 3);
    // should be 1 fighter and 2 bombers
    assertEquals(Matches.countMatches(casualties, Matches.unitIsStrategicBomber()), 2);
    assertEquals(Matches.countMatches(casualties, Matches.unitIsStrategicBomber().invert()), 1);
  }

  @Test
  public void testAaCasualtiesLowLuckMixedWithRollingMissRadar() {
    // moved from BattleCalculatorTest because "revised" does not have "radar"
    final PlayerID british = GameDataTestUtil.british(gameData);
    final ITestDelegateBridge bridge = getDelegateBridge(british);
    makeGameLowLuck(gameData);
    // setSelectAACasualties(data, false);
    givePlayerRadar(germans(gameData));
    // 4 bombers and 4 fighters
    final Collection<Unit> planes = bomber(gameData).create(4, british(gameData));
    planes.addAll(fighter(gameData).create(4, british(gameData)));
    final Collection<Unit> defendingAa =
        territory("Germany", gameData).getUnits().getMatches(Matches.unitIsAaForAnything());
    // 1 roll, a miss
    // then a dice to select the casualty
    final ScriptedRandomSource randomSource =
        new ScriptedRandomSource(new int[] {5, 0, 0, 0, ScriptedRandomSource.ERROR});
    bridge.setRandomSource(randomSource);
    final DiceRoll roll =
        DiceRoll.rollAa(
            Matches.getMatches(planes,
                Matches
                    .unitIsOfTypes(UnitAttachment.get(defendingAa.iterator().next().getType()).getTargetsAA(gameData))),
            defendingAa, bridge, territory("Germany", gameData), true);
    assertEquals(roll.getHits(), 2);
    // make sure we rolled once
    assertEquals(1, randomSource.getTotalRolled());
    final Collection<Unit> casualties = BattleCalculator.getAaCasualties(false, planes, planes, defendingAa,
        defendingAa, roll, bridge, null, null, null, territory("Germany", gameData), null, false, null).getKilled();
    assertEquals(casualties.size(), 2);
    assertEquals(4, randomSource.getTotalRolled());
    // should be 1 fighter and 2 bombers
    assertEquals(Matches.countMatches(casualties, Matches.unitIsStrategicBomber()), 1);
    assertEquals(Matches.countMatches(casualties, Matches.unitIsStrategicBomber().invert()), 1);
  }

  @Test
  public void testDefendingTrasnportsAutoKilled() {
    final Territory sz13 = gameData.getMap().getTerritory("13 Sea Zone");
    final Territory sz12 = gameData.getMap().getTerritory("12 Sea Zone");
    final PlayerID british = GameDataTestUtil.british(gameData);
    final MoveDelegate moveDelegate = moveDelegate(gameData);
    final ITestDelegateBridge bridge = getDelegateBridge(british);
    bridge.setStepName("CombatMove");
    moveDelegate.setDelegateBridgeAndPlayer(bridge);
    moveDelegate.start();
    final Route sz12To13 = new Route();
    sz12To13.setStart(sz12);
    sz12To13.add(sz13);
    final String error = moveDelegate.move(sz12.getUnits().getUnits(), sz12To13);
    assertEquals(error, null);
    assertEquals(sz13.getUnits().size(), 3);
    moveDelegate.end();
    // the transport was not removed automatically
    assertEquals(sz13.getUnits().size(), 3);
    final BattleDelegate bd = battleDelegate(gameData);
    assertFalse(bd.getBattleTracker().getPendingBattleSites(false).isEmpty());
  }

  @Test
  public void testUnplacedDie() {
    final PlaceDelegate del = placeDelegate(gameData);
    del.setDelegateBridgeAndPlayer(getDelegateBridge(british(gameData)));
    del.start();
    addTo(british(gameData), transport(gameData).create(1, british(gameData)), gameData);
    del.end();
    // unplaced units die
    assertEquals(1, british(gameData).getUnits().size());
  }

  @Test
  public void testPlaceEmpty() {
    final PlaceDelegate del = placeDelegate(gameData);
    del.setDelegateBridgeAndPlayer(getDelegateBridge(british(gameData)));
    del.start();
    addTo(british(gameData), transport(gameData).create(1, british(gameData)), gameData);
    final String error = del.placeUnits(Collections.emptyList(), territory("United Kingdom", gameData),
        IAbstractPlaceDelegate.BidMode.NOT_BID);
    assertNull(error);
  }

  @Test
  public void testTechTokens() {
    // Set up the test
    final PlayerID germans = GameDataTestUtil.germans(gameData);
    final ITestDelegateBridge delegateBridge = getDelegateBridge(germans);
    delegateBridge.setStepName("germanTech");
    final TechnologyDelegate techDelegate = techDelegate(gameData);
    techDelegate.setDelegateBridgeAndPlayer(delegateBridge);
    techDelegate.start();
    final TechnologyFrontier mech = new TechnologyFrontier("", gameData);
    mech.addAdvance(TechAdvance.findAdvance(TechAdvance.TECH_PROPERTY_MECHANIZED_INFANTRY, gameData, null));
    // Add tech token
    gameData.performChange(
        ChangeFactory.changeResourcesChange(germans, gameData.getResourceList().getResource(Constants.TECH_TOKENS), 1));
    // Check to make sure it was successful
    final int initTokens = germans.getResources().getQuantity("techTokens");
    assertEquals(1, initTokens);
    // Fail the roll
    delegateBridge.setRandomSource(new ScriptedRandomSource(new int[] {3}));
    final TechResults roll = techDelegate.rollTech(1, mech, 0, null);
    // Check to make sure it failed
    assertEquals(0, roll.getHits());
    final int midTokens = germans.getResources().getQuantity("techTokens");
    assertEquals(1, midTokens);
    // Make a Successful roll
    delegateBridge.setRandomSource(new ScriptedRandomSource(new int[] {5}));
    final TechResults roll2 = techDelegate.rollTech(1, mech, 0, null);
    // Check to make sure it succeeded and all tokens were removed
    assertEquals(1, roll2.getHits());
    final int finalTokens = germans.getResources().getQuantity("techTokens");
    assertEquals(0, finalTokens);
  }

  @Test
  public void testInfantryLoadOnlyTransports() {
    final Territory gibraltar = territory("Gibraltar", gameData);
    // add a tank to gibralter
    final PlayerID british = british(gameData);
    addTo(gibraltar, infantry(gameData).create(1, british));
    final MoveDelegate moveDelegate = moveDelegate(gameData);
    final ITestDelegateBridge bridge = getDelegateBridge(british);
    bridge.setStepName("britishCombatMove");
    moveDelegate.setDelegateBridgeAndPlayer(bridge);
    moveDelegate.start();
    bridge.setRemote(dummyPlayer);
    final Territory sz9 = territory("9 Sea Zone", gameData);
    final Territory sz13 = territory("13 Sea Zone", gameData);
    final Route sz9ToSz13 = new Route(sz9, territory("12 Sea Zone", gameData), sz13);
    // move the transport to attack, this is suicide but valid
    move(sz9.getUnits().getMatches(Matches.unitIsTransport()), sz9ToSz13);
    // load the transport
    load(gibraltar.getUnits().getUnits(), new Route(gibraltar, sz13));
    moveDelegate.end();
    bridge.setStepName("britishBattle");
    final BattleDelegate battleDelegate = battleDelegate(gameData);
    battleDelegate.setDelegateBridgeAndPlayer(bridge);
    battleDelegate.start();
    assertTrue(battleDelegate.getBattles().isEmpty());
  }

  @Test
  public void testLoadedTransportAttackKillsLoadedUnits() {
    final PlayerID british = british(gameData);
    final MoveDelegate moveDelegate = moveDelegate(gameData);
    final ITestDelegateBridge bridge = getDelegateBridge(british);
    bridge.setStepName("britishCombatMove");
    when(dummyPlayer.selectAttackSubs(any())).thenReturn(true);
    bridge.setRemote(dummyPlayer);
    moveDelegate.setDelegateBridgeAndPlayer(bridge);
    moveDelegate.start();
    final Territory sz9 = territory("9 Sea Zone", gameData);
    final Territory sz7 = territory("7 Sea Zone", gameData);
    final Territory uk = territory("United Kingdom", gameData);
    final Route sz9ToSz7 = new Route(sz9, territory("8 Sea Zone", gameData), sz7);
    // move the transport to attack, this is suicide but valid
    final List<Unit> transports = sz9.getUnits().getMatches(Matches.unitIsTransport());
    move(transports, sz9ToSz7);
    // load the transport
    load(uk.getUnits().getMatches(Matches.unitIsInfantry()), new Route(uk, sz7));
    moveDelegate(gameData).end();
    final ScriptedRandomSource randomSource = new ScriptedRandomSource(new int[] {0, 1});
    bridge.setRandomSource(randomSource);
    bridge.setStepName("britishBattle");
    final BattleDelegate battleDelegate = battleDelegate(gameData);
    battleDelegate.setDelegateBridgeAndPlayer(bridge);
    assertEquals(2, TransportTracker.transporting(transports.get(0)).size());
    battleDelegate.start();
    // battle already fought
    // make sure the infantry die with the transport
    assertTrue(sz7.getUnits().toString(), sz7.getUnits().getMatches(Matches.unitOwnedBy(british)).isEmpty());
  }

  @Test
  public void testCanRetreatIntoEmptyEnemyTerritory() {
    final Territory eastPoland = territory("East Poland", gameData);
    final Territory ukraine = territory("Ukraine", gameData);
    final Territory poland = territory("Poland", gameData);
    // remove all units from east poland
    removeFrom(eastPoland, eastPoland.getUnits().getUnits());
    final MoveDelegate moveDelegate = moveDelegate(gameData);
    final ITestDelegateBridge delegateBridge = getDelegateBridge(germans(gameData));
    delegateBridge.setStepName("CombatMove");
    moveDelegate.setDelegateBridgeAndPlayer(delegateBridge);
    moveDelegate.start();
    final Territory bulgaria = territory("Bulgaria Romania", gameData);
    // attack from bulgraia
    move(bulgaria.getUnits().getUnits(), new Route(bulgaria, ukraine));
    // add an air attack from east poland
    move(poland.getUnits().getMatches(Matches.unitIsAir()), new Route(poland, eastPoland, ukraine));
    // we should not be able to retreat to east poland!
    // that territory is still owned by the enemy
    final MustFightBattle battle =
        (MustFightBattle) AbstractMoveDelegate.getBattleTracker(gameData).getPendingBattle(ukraine, false, null);
    assertFalse(battle.getAttackerRetreatTerritories().contains(eastPoland));
  }

  @Test
  public void testCanRetreatIntoBlitzedTerritory() {
    final Territory eastPoland = territory("East Poland", gameData);
    final Territory ukraine = territory("Ukraine", gameData);
    final Territory poland = territory("Poland", gameData);
    // remove all units from east poland
    removeFrom(eastPoland, eastPoland.getUnits().getUnits());
    final MoveDelegate moveDelegate = moveDelegate(gameData);
    final ITestDelegateBridge delegateBridge = getDelegateBridge(germans(gameData));
    delegateBridge.setStepName("CombatMove");
    moveDelegate.setDelegateBridgeAndPlayer(delegateBridge);
    moveDelegate.start();
    final Territory bulgaria = territory("Bulgaria Romania", gameData);
    // attack from bulgraia
    move(bulgaria.getUnits().getUnits(), new Route(bulgaria, ukraine));
    // add a blitz attack
    move(poland.getUnits().getMatches(Matches.unitCanBlitz()), new Route(poland, eastPoland, ukraine));
    // we should not be able to retreat to east poland!
    // that territory was just conquered
    final MustFightBattle battle =
        (MustFightBattle) AbstractMoveDelegate.getBattleTracker(gameData).getPendingBattle(ukraine, false, null);
    assertTrue(battle.getAttackerRetreatTerritories().contains(eastPoland));
  }

  @Test
  public void testCantBlitzFactoryOrAa() {
    // Set up territories
    final Territory poland = territory("Poland", gameData);
    final Territory eastPoland = territory("East Poland", gameData);
    final Territory ukraine = territory("Ukraine", gameData);
    // remove all units from east poland
    removeFrom(eastPoland, eastPoland.getUnits().getUnits());
    // Add a russian factory
    addTo(eastPoland, factory(gameData).create(1, russians(gameData)));
    MoveDelegate moveDelegate = moveDelegate(gameData);
    ITestDelegateBridge delegateBridge = getDelegateBridge(germans(gameData));
    delegateBridge.setStepName("CombatMove");
    moveDelegate.setDelegateBridgeAndPlayer(delegateBridge);
    moveDelegate.start();
    // add a blitz attack
    String errorResults =
        moveDelegate.move(poland.getUnits().getMatches(Matches.unitCanBlitz()), new Route(poland, eastPoland, ukraine));
    assertError(errorResults);
    /*
     * Now try with an AA
     */
    // remove all units from east poland
    removeFrom(eastPoland, eastPoland.getUnits().getUnits());
    // Add a russian factory
    addTo(eastPoland, aaGun(gameData).create(1, russians(gameData)));
    moveDelegate = moveDelegate(gameData);
    delegateBridge = getDelegateBridge(germans(gameData));
    delegateBridge.setStepName("CombatMove");
    moveDelegate.setDelegateBridgeAndPlayer(delegateBridge);
    moveDelegate.start();
    // add a blitz attack
    errorResults =
        moveDelegate.move(poland.getUnits().getMatches(Matches.unitCanBlitz()), new Route(poland, eastPoland, ukraine));
    assertError(errorResults);
  }

  @Test
  public void testMultipleAaInTerritory() {
    // Set up territories
    final Territory poland = territory("Poland", gameData);
    // Add a russian factory
    final Territory germany = territory("Germany", gameData);
    addTo(poland, aaGun(gameData).create(1, germans(gameData)));
    MoveDelegate moveDelegate = moveDelegate(gameData);
    ITestDelegateBridge delegateBridge = getDelegateBridge(germans(gameData));
    delegateBridge.setStepName("NonCombatMove");
    moveDelegate.setDelegateBridgeAndPlayer(delegateBridge);
    moveDelegate.start();
    final int preCount = germany.getUnits().getUnitCount();
    /*
     * Move one
     */
    String errorResults =
        moveDelegate.move(poland.getUnits().getMatches(Matches.unitIsAaForAnything()), new Route(poland, germany));
    assertValid(errorResults);
    assertEquals(germany.getUnits().getUnitCount(), preCount + 1);
    /*
     * Test unloading TRN
     */
    final Territory finland = territory("Finland", gameData);
    final Territory sz5 = territory("5 Sea Zone", gameData);
    addTo(finland, aaGun(gameData).create(1, germans(gameData)));
    moveDelegate = moveDelegate(gameData);
    delegateBridge = getDelegateBridge(germans(gameData));
    delegateBridge.setStepName("NonCombatMove");
    moveDelegate.setDelegateBridgeAndPlayer(delegateBridge);
    moveDelegate.start();
    // load the trn
    errorResults = moveDelegate.move(finland.getUnits().getMatches(Matches.unitIsAaForAnything()),
        new Route(finland, sz5), sz5.getUnits().getMatches(Matches.unitIsTransport()));
    assertValid(errorResults);
    // unload the trn
    errorResults = moveDelegate.move(sz5.getUnits().getMatches(Matches.unitIsAaForAnything()), new Route(sz5, germany));
    assertValid(errorResults);
    assertEquals(germany.getUnits().getUnitCount(), preCount + 2);
    /*
     * Test Building one
     */
    final UnitType aaGun = GameDataTestUtil.aaGun(gameData);
    final IntegerMap<UnitType> map = new IntegerMap<>();
    map.add(aaGun, 1);
    // Set up the test
    final PlayerID germans = GameDataTestUtil.germans(gameData);
    final PlaceDelegate placeDelegate = placeDelegate(gameData);
    delegateBridge.setStepName("Place");
    delegateBridge.setPlayerId(germans);
    placeDelegate.setDelegateBridgeAndPlayer(getDelegateBridge(germans(gameData)));
    placeDelegate.start();
    addTo(germans(gameData), aaGun(gameData).create(1, germans(gameData)), gameData);
    errorResults = placeDelegate.placeUnits(GameDataTestUtil.getUnits(map, germans), germany,
        IAbstractPlaceDelegate.BidMode.NOT_BID);
    assertValid(errorResults);
    assertEquals(germany.getUnits().getUnitCount(), preCount + 3);
  }

  @Test
  public void testMechanizedInfantry() {
    // Set up tech
    final PlayerID germans = GameDataTestUtil.germans(gameData);
    final ITestDelegateBridge delegateBridge = getDelegateBridge(germans(gameData));
    TechTracker.addAdvance(germans, delegateBridge,
        TechAdvance.findAdvance(TechAdvance.TECH_PROPERTY_MECHANIZED_INFANTRY, gameData, germans));
    // Set up the move delegate
    final MoveDelegate moveDelegate = moveDelegate(gameData);
    delegateBridge.setStepName("CombatMove");
    moveDelegate.setDelegateBridgeAndPlayer(delegateBridge);
    moveDelegate.start();
    // Set up the territories
    final Territory poland = territory("Poland", gameData);
    final Territory eastPoland = territory("East Poland", gameData);
    final Territory belorussia = territory("Belorussia", gameData);
    // Set up the unit types
    final UnitType infantryType = GameDataTestUtil.infantry(gameData);
    // Remove all units from east poland
    removeFrom(eastPoland, eastPoland.getUnits().getUnits());
    // Get total number of units in territories to start
    final int preCountIntPoland = poland.getUnits().size();
    final int preCountIntBelorussia = belorussia.getUnits().size();
    // Get units
    final Collection<Unit> moveUnits = poland.getUnits().getUnits(infantryType, 3);
    moveUnits.addAll(poland.getUnits().getMatches(Matches.unitCanBlitz()));
    // add a INVALID blitz attack
    final String errorResults = moveDelegate.move(moveUnits, new Route(poland, eastPoland, belorussia));
    assertError(errorResults);
    // Fix the number of units
    moveUnits.clear();
    moveUnits.addAll(poland.getUnits().getUnits(infantryType, 2));
    moveUnits.addAll(poland.getUnits().getMatches(Matches.unitCanBlitz()));
    // add a VALID blitz attack
    final String validResults = moveDelegate.move(moveUnits, new Route(poland, eastPoland, belorussia));
    assertValid(validResults);
    // Get number of units in territories after move (adjusted for movement)
    final int postCountIntPoland = poland.getUnits().size() + 4;
    final int postCountIntBelorussia = belorussia.getUnits().size() - 4;
    // Compare the number of units before and after
    assertEquals(preCountIntPoland, postCountIntPoland);
    assertEquals(preCountIntBelorussia, postCountIntBelorussia);
  }

  @Test
  public void testJetPower() {
    // Set up tech
    final PlayerID germans = GameDataTestUtil.germans(gameData);
    final ITestDelegateBridge delegateBridge = getDelegateBridge(germans(gameData));
    TechTracker.addAdvance(germans, delegateBridge,
        TechAdvance.findAdvance(TechAdvance.TECH_PROPERTY_JET_POWER, gameData, germans));
    // Set up the territories
    final Territory poland = territory("Poland", gameData);
    final Territory eastPoland = territory("East Poland", gameData);
    // Set up the unit types
    final UnitType fighterType = GameDataTestUtil.fighter(gameData);
    delegateBridge.setStepName("germanBattle");
    while (!gameData.getSequence().getStep().getName().equals("germanBattle")) {
      gameData.getSequence().next();
    }
    final Collection<TerritoryEffect> territoryEffects = TerritoryEffectHelper.getEffects(eastPoland);
    // With JET_POWER attacking fighter hits on 4 (0 base)
    final List<Unit> germanFighter = (List<Unit>) poland.getUnits().getUnits(fighterType, 1);
    delegateBridge.setRandomSource(new ScriptedRandomSource(new int[] {3}));
    final DiceRoll roll1 = DiceRoll.rollDice(germanFighter, false, germans, delegateBridge, new MockBattle(eastPoland),
        "", territoryEffects, null);
    assertEquals(1, roll1.getHits());
    // With JET_POWER defending fighter misses on 5 (0 base)
    delegateBridge.setRandomSource(new ScriptedRandomSource(new int[] {4}));
    final DiceRoll roll2 = DiceRoll.rollDice(germanFighter, true, germans, delegateBridge, new MockBattle(eastPoland),
        "", territoryEffects, null);
    assertEquals(0, roll2.getHits());
  }

  @Test
  public void testBidPlace() {
    final ITestDelegateBridge bridge = getDelegateBridge(british(gameData));
    bridge.setStepName("BidPlace");
    bidPlaceDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    bidPlaceDelegate(gameData).start();
    // create 20 british infantry
    addTo(british(gameData), infantry(gameData).create(20, british(gameData)), gameData);
    final Territory uk = territory("United Kingdom", gameData);
    final Collection<Unit> units = british(gameData).getUnits().getUnits();
    final PlaceableUnits placeable = bidPlaceDelegate(gameData).getPlaceableUnits(units, uk);
    assertEquals(20, placeable.getMaxUnits());
    assertNull(placeable.getErrorMessage());
    final String error = bidPlaceDelegate(gameData).placeUnits(units, uk);
    assertNull(error);
  }

  @Test
  public void testFactoryPlace() {
    // Set up game
    final PlayerID british = GameDataTestUtil.british(gameData);
    final ITestDelegateBridge delegateBridge = getDelegateBridge(british(gameData));
    // Set up the territories
    final Territory egypt = territory("Union of South Africa", gameData);
    // Set up the unit types
    final UnitType factoryType = GameDataTestUtil.factory(gameData);
    // Set up the move delegate
    final PlaceDelegate placeDelegate = placeDelegate(gameData);
    delegateBridge.setStepName("Place");
    delegateBridge.setPlayerId(british);
    placeDelegate.setDelegateBridgeAndPlayer(delegateBridge);
    placeDelegate.start();
    // Add the factory
    final IntegerMap<UnitType> map = new IntegerMap<>();
    map.add(factoryType, 1);
    addTo(british(gameData), factory(gameData).create(1, british(gameData)), gameData);
    // Place the factory
    final String response = placeDelegate.placeUnits(GameDataTestUtil.getUnits(map, british), egypt);
    assertValid(response);
    // placeUnits performPlace
    // get production and unit production values
    final TerritoryAttachment ta = TerritoryAttachment.get(egypt);
    assertEquals(ta.getUnitProduction(), ta.getProduction());
  }

  @Test
  public void testChinesePlacement() {
    /*
     * This tests that Chinese can place units in any territory, that they can
     * place in just conquered territories, and that they can place in territories
     * with up to 3 Chinese units in them.
     */
    // Set up game
    final PlayerID chinese = GameDataTestUtil.chinese(gameData);
    final ITestDelegateBridge delegateBridge = getDelegateBridge(chinese(gameData));
    delegateBridge.setPlayerId(chinese);
    delegateBridge.setStepName("CombatMove");
    final MoveDelegate moveDelegate = moveDelegate(gameData);
    moveDelegate.setDelegateBridgeAndPlayer(delegateBridge);
    moveDelegate.start();
    // Set up the territories
    final Territory yunnan = territory("Yunnan", gameData);
    final Territory kiangsu = territory("Kiangsu", gameData);
    final Territory hupeh = territory("Hupeh", gameData);
    // Set up the unit types
    final UnitType infantryType = GameDataTestUtil.infantry(gameData);
    // Remove all units
    removeFrom(kiangsu, kiangsu.getUnits().getUnits());
    // add a VALID attack
    final Collection<Unit> moveUnits = hupeh.getUnits().getUnits();
    final String validResults = moveDelegate.move(moveUnits, new Route(hupeh, kiangsu));
    assertValid(validResults);
    /*
     * Place units in just captured territory
     */
    final PlaceDelegate placeDelegate = placeDelegate(gameData);
    delegateBridge.setStepName("Place");
    placeDelegate.setDelegateBridgeAndPlayer(delegateBridge);
    placeDelegate.start();
    // Add the infantry
    IntegerMap<UnitType> map = new IntegerMap<>();
    map.add(infantryType, 3);
    addTo(chinese(gameData), infantry(gameData).create(1, chinese(gameData)), gameData);
    // Get the number of units before placing
    int preCount = kiangsu.getUnits().getUnitCount();
    // Place the infantry
    String response = placeDelegate.placeUnits(GameDataTestUtil.getUnits(map, chinese), kiangsu,
        IAbstractPlaceDelegate.BidMode.NOT_BID);
    assertValid(response);
    assertEquals(preCount + 1, kiangsu.getUnits().getUnitCount());
    /*
     * Place units in a territory with up to 3 Chinese units
     */
    // Add the infantry
    map = new IntegerMap<>();
    map.add(infantryType, 3);
    addTo(chinese(gameData), infantry(gameData).create(3, chinese(gameData)), gameData);
    // Get the number of units before placing
    preCount = yunnan.getUnits().getUnitCount();
    // Place the infantry
    response = placeDelegate.placeUnits(GameDataTestUtil.getUnits(map, chinese), yunnan,
        IAbstractPlaceDelegate.BidMode.NOT_BID);
    assertValid(response);
    final int midCount = yunnan.getUnits().getUnitCount();
    // Make sure they were all placed
    assertEquals(preCount, midCount - 3);
    /*
     * Place units in a territory with 3 or more Chinese units
     */
    map = new IntegerMap<>();
    map.add(infantryType, 1);
    addTo(chinese(gameData), infantry(gameData).create(1, chinese(gameData)), gameData);
    response = placeDelegate.placeUnits(GameDataTestUtil.getUnits(map, chinese), yunnan,
        IAbstractPlaceDelegate.BidMode.NOT_BID);
    assertError(response);
    // Make sure none were placed
    final int postCount = yunnan.getUnits().getUnitCount();
    assertEquals(midCount, postCount);
  }

  @Test
  public void testPlaceInOccupiedSeaZone() {
    // Set up game
    final PlayerID germans = GameDataTestUtil.germans(gameData);
    final ITestDelegateBridge delegateBridge = getDelegateBridge(british(gameData));
    // Clear all units from the SZ and add an enemy unit
    final Territory sz5 = territory("5 Sea Zone", gameData);
    removeFrom(sz5, sz5.getUnits().getUnits());
    addTo(sz5, destroyer(gameData).create(1, british(gameData)));
    // Set up the unit types
    final UnitType transportType = GameDataTestUtil.transport(gameData);
    // Set up the move delegate
    final PlaceDelegate placeDelegate = placeDelegate(gameData);
    delegateBridge.setStepName("Place");
    delegateBridge.setPlayerId(germans);
    placeDelegate.setDelegateBridgeAndPlayer(delegateBridge);
    placeDelegate.start();
    // Add the transport
    final IntegerMap<UnitType> map = new IntegerMap<>();
    map.add(transportType, 1);
    addTo(germans(gameData), transport(gameData).create(1, germans(gameData)), gameData);
    // Place it
    final String response = placeDelegate.placeUnits(GameDataTestUtil.getUnits(map, germans), sz5);
    assertValid(response);
  }

  @Test
  public void testMoveUnitsThroughSubs() {
    final ITestDelegateBridge bridge = getDelegateBridge(british(gameData));
    bridge.setStepName("britishNonCombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    final Territory sz6 = territory("6 Sea Zone", gameData);
    final Route route = new Route(sz6, territory("7 Sea Zone", gameData), territory("8 Sea Zone", gameData));
    final String error = moveDelegate(gameData).move(sz6.getUnits().getUnits(), route);
    assertNull(error, error);
  }

  @Test
  public void testMoveUnitsThroughTransports() {
    final ITestDelegateBridge bridge = getDelegateBridge(british(gameData));
    bridge.setStepName("britishCombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    final Territory sz12 = territory("12 Sea Zone", gameData);
    final Route route = new Route(sz12, territory("13 Sea Zone", gameData), territory("14 Sea Zone", gameData));
    final String error = moveDelegate(gameData).move(sz12.getUnits().getUnits(), route);
    assertNull(error, error);
  }

  @Test
  public void testMoveUnitsThroughTransports2() {
    final ITestDelegateBridge bridge = getDelegateBridge(british(gameData));
    bridge.setStepName("britishNonCombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    final Territory sz12 = territory("12 Sea Zone", gameData);
    final Territory sz14 = territory("14 Sea Zone", gameData);
    removeFrom(sz14, sz14.getUnits().getUnits());
    final Route route = new Route(sz12, territory("13 Sea Zone", gameData), sz14);
    final String error = moveDelegate(gameData).move(sz12.getUnits().getUnits(), route);
    assertNull(error, error);
  }

  @Test
  public void testLoadThroughSubs() {
    final ITestDelegateBridge bridge = getDelegateBridge(british(gameData));
    bridge.setStepName("britishNonCombatMove");
    final MoveDelegate moveDelegate = moveDelegate(gameData);
    moveDelegate.setDelegateBridgeAndPlayer(bridge);
    moveDelegate.start();
    final Territory sz8 = territory("8 Sea Zone", gameData);
    final Territory sz7 = territory("7 Sea Zone", gameData);
    final Territory sz6 = territory("6 Sea Zone", gameData);
    final Territory uk = territory("United Kingdom", gameData);
    // add a transport
    addTo(sz8, transport(gameData).create(1, british(gameData)));
    // move the transport where to the sub is
    assertValid(moveDelegate.move(sz8.getUnits().getUnits(), new Route(sz8, sz7)));
    // load the transport
    load(uk.getUnits().getMatches(Matches.unitIsInfantry()), new Route(uk, sz7));
    // move the transport out
    assertValid(
        moveDelegate.move(sz7.getUnits().getMatches(Matches.unitOwnedBy(british(gameData))), new Route(sz7, sz6)));
  }

  @Test
  public void testAttackUndoAndAttackAgain() {
    final MoveDelegate move = moveDelegate(gameData);
    final ITestDelegateBridge bridge = getDelegateBridge(italians(gameData));
    bridge.setStepName("CombatMove");
    move.setDelegateBridgeAndPlayer(bridge);
    move.start();
    final Territory sz14 = territory("14 Sea Zone", gameData);
    final Territory sz13 = territory("13 Sea Zone", gameData);
    final Territory sz12 = territory("12 Sea Zone", gameData);
    final Route r = new Route(sz14, sz13, sz12);
    // move the battleship
    move(sz14.getUnits().getMatches(Matches.unitHasMoreThanOneHitPointTotal()), r);
    // move everything
    move(sz14.getUnits().getMatches(Matches.unitIsNotTransport()), r);
    // undo it
    move.undoMove(1);
    // move again
    move(sz14.getUnits().getMatches(Matches.unitIsNotTransport()), r);
    final MustFightBattle mfb =
        (MustFightBattle) AbstractMoveDelegate.getBattleTracker(gameData).getPendingBattle(sz12, false, null);
    // only 3 attacking units
    // the battleship and the two cruisers
    assertEquals(3, mfb.getAttackingUnits().size());
  }

  @Test
  public void testAttackSubsOnSubs() {
    final String defender = "Germans";
    final String attacker = "British";
    final Territory attacked = territory("31 Sea Zone", gameData);
    final Territory from = territory("32 Sea Zone", gameData);
    // 1 sub attacks 1 sub
    addTo(from, submarine(gameData).create(1, british(gameData)));
    addTo(attacked, submarine(gameData).create(1, germans(gameData)));
    final ITestDelegateBridge bridge = getDelegateBridge(british(gameData));
    bridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    move(from.getUnits().getUnits(), new Route(from, attacked));
    moveDelegate(gameData).end();
    final MustFightBattle battle =
        (MustFightBattle) AbstractMoveDelegate.getBattleTracker(gameData).getPendingBattle(attacked, false, null);
    final List<String> steps = battle.determineStepStrings(true);
    assertEquals(
        Arrays.asList(attacker + SUBS_SUBMERGE, defender + SUBS_SUBMERGE, attacker + SUBS_FIRE,
            defender + SELECT_SUB_CASUALTIES, defender + SUBS_FIRE, attacker + SELECT_SUB_CASUALTIES,
            REMOVE_SNEAK_ATTACK_CASUALTIES, REMOVE_CASUALTIES, attacker + ATTACKER_WITHDRAW).toString(),
        steps.toString());
    bridge.setRemote(dummyPlayer);
    // fight, each sub should fire
    // and hit
    final ScriptedRandomSource randomSource = new ScriptedRandomSource(0, 0, ScriptedRandomSource.ERROR);
    bridge.setRandomSource(randomSource);
    battle.fight(bridge);
    assertEquals(2, randomSource.getTotalRolled());
    assertTrue(attacked.getUnits().isEmpty());
  }

  @Test
  public void testAttackSubsOnDestroyer() {
    final String defender = "Germans";
    final String attacker = "British";
    final Territory attacked = territory("31 Sea Zone", gameData);
    final Territory from = territory("32 Sea Zone", gameData);
    // 1 sub attacks 1 sub and 1 destroyer
    // defender sneak attacks, not attacker
    addTo(from, submarine(gameData).create(1, british(gameData)));
    addTo(attacked, submarine(gameData).create(1, germans(gameData)));
    addTo(attacked, destroyer(gameData).create(1, germans(gameData)));
    final ITestDelegateBridge bridge = getDelegateBridge(british(gameData));
    bridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    move(from.getUnits().getUnits(), new Route(from, attacked));
    moveDelegate(gameData).end();
    final MustFightBattle battle =
        (MustFightBattle) AbstractMoveDelegate.getBattleTracker(gameData).getPendingBattle(attacked, false, null);
    final List<String> steps = battle.determineStepStrings(true);
    assertEquals(
        Arrays.asList(defender + SUBS_SUBMERGE, defender + SUBS_FIRE, attacker + SELECT_SUB_CASUALTIES,
            REMOVE_SNEAK_ATTACK_CASUALTIES, attacker + SUBS_FIRE, defender + SELECT_SUB_CASUALTIES, defender + FIRE,
            attacker + SELECT_CASUALTIES, REMOVE_CASUALTIES, attacker + ATTACKER_WITHDRAW).toString(),
        steps.toString());
    bridge.setRemote(dummyPlayer);
    // defending subs sneak attack and hit
    // no chance to return fire
    final ScriptedRandomSource randomSource = new ScriptedRandomSource(0, ScriptedRandomSource.ERROR);
    bridge.setRandomSource(randomSource);
    battle.fight(bridge);
    assertEquals(1, randomSource.getTotalRolled());
    assertTrue(attacked.getUnits().getMatches(Matches.unitIsOwnedBy(british(gameData))).isEmpty());
    assertEquals(2, attacked.getUnits().size());
  }

  @Test
  public void testAttackDestroyerAndSubsAgainstSub() {
    final String defender = "Germans";
    final String attacker = "British";
    final Territory attacked = territory("31 Sea Zone", gameData);
    final Territory from = territory("32 Sea Zone", gameData);
    // 1 sub and 1 destroyer attack 1 sub
    // defender sneak attacks, not attacker
    addTo(from, submarine(gameData).create(1, british(gameData)));
    addTo(from, destroyer(gameData).create(1, british(gameData)));
    addTo(attacked, submarine(gameData).create(1, germans(gameData)));
    final ITestDelegateBridge bridge = getDelegateBridge(british(gameData));
    bridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    move(from.getUnits().getUnits(), new Route(from, attacked));
    moveDelegate(gameData).end();
    final MustFightBattle battle =
        (MustFightBattle) AbstractMoveDelegate.getBattleTracker(gameData).getPendingBattle(attacked, false, null);
    final List<String> steps = battle.determineStepStrings(true);
    assertEquals(
        Arrays.asList(attacker + SUBS_SUBMERGE, attacker + SUBS_FIRE, defender + SELECT_SUB_CASUALTIES,
            REMOVE_SNEAK_ATTACK_CASUALTIES, attacker + FIRE, defender + SELECT_CASUALTIES, defender + SUBS_FIRE,
            attacker + SELECT_SUB_CASUALTIES, REMOVE_CASUALTIES, attacker + ATTACKER_WITHDRAW).toString(),
        steps.toString());
    bridge.setRemote(dummyPlayer);
    // attacking subs sneak attack and hit
    // no chance to return fire
    final ScriptedRandomSource randomSource = new ScriptedRandomSource(0, ScriptedRandomSource.ERROR);
    bridge.setRandomSource(randomSource);
    battle.fight(bridge);
    assertEquals(1, randomSource.getTotalRolled());
    assertTrue(attacked.getUnits().getMatches(Matches.unitIsOwnedBy(germans(gameData))).isEmpty());
    assertEquals(2, attacked.getUnits().size());
  }

  @Test
  public void testAttackDestroyerAndSubsAgainstSubAndDestroyer() {
    final String defender = "Germans";
    final String attacker = "British";
    final Territory attacked = territory("31 Sea Zone", gameData);
    final Territory from = territory("32 Sea Zone", gameData);
    // 1 sub and 1 destroyer attack 1 sub and 1 destroyer
    // no sneak attacks
    addTo(from, submarine(gameData).create(1, british(gameData)));
    addTo(from, destroyer(gameData).create(1, british(gameData)));
    addTo(attacked, submarine(gameData).create(1, germans(gameData)));
    addTo(attacked, destroyer(gameData).create(1, germans(gameData)));
    final ITestDelegateBridge bridge = getDelegateBridge(british(gameData));
    bridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    move(from.getUnits().getUnits(), new Route(from, attacked));
    moveDelegate(gameData).end();
    final MustFightBattle battle =
        (MustFightBattle) AbstractMoveDelegate.getBattleTracker(gameData).getPendingBattle(attacked, false, null);
    final List<String> steps = battle.determineStepStrings(true);
    assertEquals(
        Arrays.asList(attacker + SUBS_FIRE, defender + SELECT_SUB_CASUALTIES, attacker + FIRE,
            defender + SELECT_CASUALTIES, defender + SUBS_FIRE, attacker + SELECT_SUB_CASUALTIES, defender + FIRE,
            attacker + SELECT_CASUALTIES, REMOVE_CASUALTIES, attacker + ATTACKER_WITHDRAW).toString(),
        steps.toString());
    when(dummyPlayer.selectCasualties(any(), any(), anyInt(), any(), any(), any(), any(), any(), any(), anyBoolean(),
        any(),
        any(), any(), any(), anyBoolean())).thenAnswer(new Answer<CasualtyDetails>() {
          @Override
          public CasualtyDetails answer(final InvocationOnMock invocation) {
            final Collection<Unit> selectFrom = invocation.getArgument(0);
            return new CasualtyDetails(Arrays.asList(selectFrom.iterator().next()), new ArrayList<>(), false);
          }
        });
    bridge.setRemote(dummyPlayer);
    // attacking subs sneak attack and hit
    // no chance to return fire
    final ScriptedRandomSource randomSource = new ScriptedRandomSource(0, 0, 0, 0, ScriptedRandomSource.ERROR);
    bridge.setRandomSource(randomSource);
    battle.fight(bridge);
    assertEquals(4, randomSource.getTotalRolled());
    assertEquals(0, attacked.getUnits().size());
  }

  @Test
  public void testLimitBombardtoNumberOfUnloaded() {
    final MoveDelegate move = moveDelegate(gameData);
    final ITestDelegateBridge bridge = getDelegateBridge(italians(gameData));

    when(dummyPlayer.selectShoreBombard(any())).thenReturn(true);
    bridge.setRemote(dummyPlayer);
    bridge.setStepName("CombatMove");
    move.setDelegateBridgeAndPlayer(bridge);
    move.start();
    final Territory sz14 = territory("14 Sea Zone", gameData);
    final Territory sz15 = territory("15 Sea Zone", gameData);
    final Territory eg = territory("Egypt", gameData);
    final Territory li = territory("Libya", gameData);
    final Territory balkans = territory("Balkans", gameData);
    // Clear all units from the attacked terr
    removeFrom(eg, eg.getUnits().getUnits());
    // Add 2 inf
    final PlayerID british = GameDataTestUtil.british(gameData);
    addTo(eg, infantry(gameData).create(2, british));
    // load the transports
    load(balkans.getUnits().getMatches(Matches.unitIsInfantry()), new Route(balkans, sz14));
    // move the fleet
    move(sz14.getUnits().getUnits(), new Route(sz14, sz15));
    // move troops from Libya
    move(li.getUnits().getMatches(Matches.unitOwnedBy(italians(gameData))), new Route(li, eg));
    // unload the transports
    move(sz15.getUnits().getMatches(Matches.unitIsLand()), new Route(sz15, eg));
    move.end();
    // start the battle phase, this will ask the user to bombard
    battleDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    BattleDelegate.doInitialize(battleDelegate(gameData).getBattleTracker(), bridge);
    battleDelegate(gameData).addBombardmentSources();
    final MustFightBattle mfb =
        (MustFightBattle) AbstractMoveDelegate.getBattleTracker(gameData).getPendingBattle(eg, false, null);
    // only 2 ships are allowed to bombard (there are 1 battleship and 2 cruisers that COULD bombard, but only 2 ships
    // may bombard total)
    assertEquals(2, mfb.getBombardingUnits().size());
    // Show that bombard casualties can return fire
    // Note- the 3 & 2 hits below show default behavior of bombarding at attack strength
    // 2= Battleship hitting a 3, 2=Cruiser hitting a 3, 15=British infantry hitting once
    bridge.setRandomSource(new ScriptedRandomSource(2, 2, 1, 5, 5, 5, 5, 5));
    battleDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    battleDelegate(gameData).start();
    // end result should be 2 italian infantry.
    assertEquals(3, eg.getUnits().size());
  }

  @Test
  public void testBombardStrengthVariable() {
    final MoveDelegate move = moveDelegate(gameData);
    final ITestDelegateBridge bridge = getDelegateBridge(italians(gameData));
    when(dummyPlayer.selectShoreBombard(any())).thenReturn(true);
    bridge.setRemote(dummyPlayer);
    bridge.setStepName("CombatMove");
    move.setDelegateBridgeAndPlayer(bridge);
    move.start();
    final Territory sz14 = territory("14 Sea Zone", gameData);
    final Territory sz15 = territory("15 Sea Zone", gameData);
    final Territory eg = territory("Egypt", gameData);
    final Territory balkans = territory("Balkans", gameData);
    // Clear all units
    removeFrom(eg, eg.getUnits().getUnits());
    removeFrom(sz14, sz14.getUnits().getUnits());
    // Add 2 inf to the attacked terr
    final PlayerID british = GameDataTestUtil.british(gameData);
    addTo(eg, infantry(gameData).create(2, british));
    // create/load the destroyers and transports
    final PlayerID italians = GameDataTestUtil.italians(gameData);
    addTo(sz14, transport(gameData).create(1, italians));
    addTo(sz14, destroyer(gameData).create(2, italians));
    // load the transports
    load(balkans.getUnits().getMatches(Matches.unitIsInfantry()), new Route(balkans, sz14));
    // move the fleet
    move(sz14.getUnits().getUnits(), new Route(sz14, sz15));
    // unload the transports
    move(sz15.getUnits().getMatches(Matches.unitIsLand()), new Route(sz15, eg));
    move.end();
    // Set the tech for DDs bombard
    // ww2v3 doesn't have this tech, so this does nothing...
    // TechAttachment.get(italians).setDestroyerBombard("true");
    UnitAttachment.get(destroyer(gameData)).setCanBombard("true");
    // Set the bombard strength for the DDs
    final Collection<Unit> dds = Matches.getMatches(sz15.getUnits().getUnits(), Matches.unitIsDestroyer());
    final Iterator<Unit> ddIter = dds.iterator();
    while (ddIter.hasNext()) {
      final Unit unit = ddIter.next();
      final UnitAttachment ua = UnitAttachment.get(unit.getType());
      ua.setBombard("3");
    }
    // start the battle phase, this will ask the user to bombard
    battleDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    BattleDelegate.doInitialize(battleDelegate(gameData).getBattleTracker(), bridge);
    battleDelegate(gameData).addBombardmentSources();
    final MustFightBattle mfb =
        (MustFightBattle) AbstractMoveDelegate.getBattleTracker(gameData).getPendingBattle(eg, false, null);
    assertNotNull(mfb);
    // Show that bombard casualties can return fire
    // destroyer bombard hit/miss on rolls of 4 & 3
    // landing inf miss
    // defending inf hit
    bridge.setRandomSource(new ScriptedRandomSource(3, 2, 6, 6, 1, 1));
    battleDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    BattleDelegate.doInitialize(battleDelegate(gameData).getBattleTracker(), bridge);
    battleDelegate(gameData).addBombardmentSources();
    fight(battleDelegate(gameData), eg);
    // 1 defending inf remaining
    assertEquals(1, eg.getUnits().size());
  }

  @Test
  public void testAmphAttackUndoAndAttackAgainBombard() {
    final MoveDelegate move = moveDelegate(gameData);
    final ITestDelegateBridge bridge = getDelegateBridge(italians(gameData));
    when(dummyPlayer.selectShoreBombard(any())).thenReturn(true);
    bridge.setRemote(dummyPlayer);
    bridge.setStepName("CombatMove");
    move.setDelegateBridgeAndPlayer(bridge);
    move.start();
    final Territory sz14 = territory("14 Sea Zone", gameData);
    final Territory sz15 = territory("15 Sea Zone", gameData);
    final Territory eg = territory("Egypt", gameData);
    final Territory li = territory("Libya", gameData);
    final Territory balkans = territory("Balkans", gameData);
    // load the transports
    load(balkans.getUnits().getMatches(Matches.unitIsInfantry()), new Route(balkans, sz14));
    // move the fleet
    move(sz14.getUnits().getUnits(), new Route(sz14, sz15));
    // move troops from Libya
    move(li.getUnits().getMatches(Matches.unitOwnedBy(italians(gameData))), new Route(li, eg));
    // unload the transports
    move(sz15.getUnits().getMatches(Matches.unitIsLand()), new Route(sz15, eg));
    // undo amphibious landing
    move.undoMove(move.getMovesMade().size() - 1);
    // move again
    move(sz15.getUnits().getMatches(Matches.unitIsLand()), new Route(sz15, eg));
    move.end();
    // start the battle phase, this will ask the user to bombard
    battleDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    BattleDelegate.doInitialize(battleDelegate(gameData).getBattleTracker(), bridge);
    battleDelegate(gameData).addBombardmentSources();
    final MustFightBattle mfb =
        (MustFightBattle) AbstractMoveDelegate.getBattleTracker(gameData).getPendingBattle(eg, false, null);
    // only 2 battleships are allowed to bombard
    assertEquals(2, mfb.getBombardingUnits().size());
  }

  @Test
  public void testCarrierWithAlliedPlanes() {
    final Territory sz8 = territory("8 Sea Zone", gameData);
    final Territory sz1 = territory("1 Sea Zone", gameData);
    addTo(sz8, carrier(gameData).create(1, british(gameData)));
    addTo(sz8, fighter(gameData).create(1, americans(gameData)));
    final Route route = new Route(sz8, sz1);
    final ITestDelegateBridge bridge = getDelegateBridge(british(gameData));
    bridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    move(sz8.getUnits().getUnits(), route);
    // make sure the fighter moved
    assertTrue(sz8.getUnits().getUnits().isEmpty());
    assertFalse(sz1.getUnits().getMatches(Matches.unitIsAir()).isEmpty());
  }

  @Test
  public void testAirCanLandWithAlliedFighters() {
    // germany owns madagascar, with 2 fighters in it
    // also 1 carrier, and 1 allied fighter in sz 40
    // the fighters should not be able to move from madagascar
    // to sz 40, since with the allied fighter, their is no room
    // on the carrier
    final Territory madagascar = territory("French Madagascar", gameData);
    final PlayerID germans = germans(gameData);
    madagascar.setOwner(germans);
    final Territory sz40 = territory("40 Sea Zone", gameData);
    addTo(sz40, carrier(gameData).create(1, germans));
    addTo(sz40, fighter(gameData).create(1, italians(gameData)));
    addTo(madagascar, fighter(gameData).create(2, germans));
    final Route route = gameData.getMap().getRoute(madagascar, sz40);
    final ITestDelegateBridge bridge = getDelegateBridge(germans);
    bridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    // don't allow kamikaze
    bridge.setRemote(dummyPlayer);
    final String error = moveDelegate(gameData).move(madagascar.getUnits().getUnits(), route);
    assertError(error);
  }

  @Test
  public void testMechInfSimple() {
    final PlayerID germans = germans(gameData);
    final Territory france = territory("France", gameData);
    final Territory germany = territory("Germany", gameData);
    final Territory poland = territory("Poland", gameData);
    TechAttachment.get(germans).setMechanizedInfantry("true");
    final ITestDelegateBridge bridge = getDelegateBridge(germans);
    bridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    final Route r = new Route(france, germany, poland);
    final List<Unit> toMove = new ArrayList<>();
    // 1 armour and 1 infantry
    toMove.addAll(france.getUnits().getMatches(Matches.unitCanBlitz()));
    toMove.add(france.getUnits().getMatches(Matches.unitIsInfantry()).get(0));
    move(toMove, r);
  }

  @Test
  public void testMechInfUnitAlreadyMovedSimple() {
    final PlayerID germans = germans(gameData);
    final Territory france = territory("France", gameData);
    final Territory germany = territory("Germany", gameData);
    TechAttachment.get(germans).setMechanizedInfantry("true");
    final ITestDelegateBridge bridge = getDelegateBridge(germans);
    bridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    // get rid of the infantry in france
    removeFrom(france, france.getUnits().getMatches(Matches.unitIsInfantry()));
    // move an infantry from germany to france
    move(germany.getUnits().getMatches(Matches.unitIsInfantry()).subList(0, 1), new Route(germany, france));
    // try to move all the units in france, the infantry should not be able to move
    final Route r = new Route(france, germany);
    final String error = moveDelegate(gameData).move(france.getUnits().getUnits(), r);
    assertFalse(error == null);
  }

  @Test
  public void testParatroopsWalkOnWater() {
    final PlayerID germans = germans(gameData);
    final Territory france = territory("France", gameData);
    TechAttachment.get(germans).setParatroopers("true");
    final Route r = gameData.getMap().getRoute(france, territory("7 Sea Zone", gameData));
    final Collection<Unit> paratroopers = france.getUnits().getMatches(Matches.unitIsAirTransportable());
    assertFalse(paratroopers.isEmpty());
    final MoveValidationResult results = MoveValidator.validateMove(paratroopers, r, germans,
        Collections.emptyList(), new HashMap<>(), false, null, gameData);
    assertFalse(results.isMoveValid());
  }

  @Test
  public void testBomberWithTankOverWaterParatroopers() {
    final PlayerID germans = germans(gameData);
    TechAttachment.get(germans).setParatroopers("true");
    final Territory sz5 = territory("5 Sea Zone", gameData);
    final Territory germany = territory("Germany", gameData);
    final Territory karelia = territory("Karelia S.S.R.", gameData);
    addTo(germany, armour(gameData).create(1, germans));
    final Route r = new Route(germany, sz5, karelia);
    final Collection<Unit> toMove = germany.getUnits().getMatches(Matches.unitCanBlitz());
    toMove.addAll(germany.getUnits().getMatches(Matches.unitIsStrategicBomber()));
    assertEquals(2, toMove.size());
    final MoveValidationResult results = MoveValidator.validateMove(toMove, r, germans, Collections.emptyList(),
        new HashMap<>(), false, null, gameData);
    assertFalse(results.isMoveValid());
  }

  @Test
  public void testBomberTankOverWater() {
    // can't transport a tank over water using a bomber
    final PlayerID germans = germans(gameData);
    final Territory sz5 = territory("5 Sea Zone", gameData);
    final Territory germany = territory("Germany", gameData);
    final Territory karelia = territory("Karelia S.S.R.", gameData);
    addTo(germany, armour(gameData).create(1, germans));
    final Route r = new Route(germany, sz5, karelia);
    final Collection<Unit> toMove = germany.getUnits().getMatches(Matches.unitCanBlitz());
    toMove.addAll(germany.getUnits().getMatches(Matches.unitIsStrategicBomber()));
    assertEquals(2, toMove.size());
    final MoveValidationResult results = MoveValidator.validateMove(toMove, r, germans, Collections.emptyList(),
        new HashMap<>(), false, null, gameData);
    assertFalse(results.isMoveValid());
  }

  @Test
  public void testMoveParatroopersAsNonPartroops() {
    // move a bomber and a paratrooper
    // one step, but as a normal movement
    final PlayerID germans = germans(gameData);
    final Territory germany = territory("Germany", gameData);
    final Territory nwe = territory("Northwestern Europe", gameData);
    final ITestDelegateBridge bridge = getDelegateBridge(germans);
    bridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    TechAttachment.get(germans).setParatroopers("true");
    List<Unit> paratrooper = germany.getUnits().getMatches(Matches.unitIsAirTransportable());
    paratrooper = paratrooper.subList(0, 1);
    final List<Unit> bomberAndParatroop = new ArrayList<>(paratrooper);
    bomberAndParatroop.addAll(germany.getUnits().getMatches(Matches.unitIsAirTransport()));
    // move to nwe, this is a valid move, and it not a partroop move
    move(bomberAndParatroop, new Route(germany, nwe));
  }

  @Test
  public void testCantMoveParatroopersThatMovedPreviously() {
    // make sure infantry can't be moved as paratroopers after moving
    final PlayerID germans = germans(gameData);
    final Territory germany = territory("Germany", gameData);
    final Territory nwe = territory("Northwestern Europe", gameData);
    final Territory poland = territory("Poland", gameData);
    final Territory eastPoland = territory("East Poland", gameData);
    final ITestDelegateBridge bridge = getDelegateBridge(germans);
    bridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    TechAttachment.get(germans).setParatroopers("true");
    final List<Unit> paratrooper = nwe.getUnits().getMatches(Matches.unitIsAirTransportable());
    move(paratrooper, new Route(nwe, germany));
    final List<Unit> bomberAndParatroop = new ArrayList<>(paratrooper);
    bomberAndParatroop.addAll(germany.getUnits().getMatches(Matches.unitIsAirTransport()));
    // move the units to east poland
    final String error = moveDelegate(gameData).move(bomberAndParatroop, new Route(germany, poland, eastPoland));
    assertError(error);
  }

  @Test
  public void testCantTransportParatroopersWithBombersThatMovedPreviously() {
    // make sure bombers can't move then pick up paratroopers
    final PlayerID germans = germans(gameData);
    final Territory germany = territory("Germany", gameData);
    final Territory bulgaria = territory("Bulgaria Romania", gameData);
    final Territory poland = territory("Poland", gameData);
    final Territory ukraine = territory("Ukraine", gameData);
    final ITestDelegateBridge bridge = getDelegateBridge(germans);
    bridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    TechAttachment.get(germans).setParatroopers("true");
    // Move the bomber first
    final List<Unit> bomber = germany.getUnits().getMatches(Matches.unitIsAirTransport());
    move(bomber, new Route(germany, poland));
    // Pick up a paratrooper
    final List<Unit> bomberAndParatroop = new ArrayList<>(bomber);
    bomberAndParatroop.addAll(poland.getUnits().getUnits(GameDataTestUtil.infantry(gameData), 1));
    // move them
    final String error = moveDelegate(gameData).move(bomberAndParatroop, new Route(poland, bulgaria, ukraine));
    assertError(error);
  }

  @Test
  public void testMoveOneParatrooperPerBomber() {
    // make sure only 1 paratroop per bomber can be moved
    final PlayerID germans = germans(gameData);
    final Territory germany = territory("Germany", gameData);
    // Territory nwe = territory("Northwestern Europe", gameData);
    final Territory poland = territory("Poland", gameData);
    final Territory eastPoland = territory("East Poland", gameData);
    final ITestDelegateBridge bridge = getDelegateBridge(germans);
    bridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    TechAttachment.get(germans).setParatroopers("true");
    final List<Unit> bomberAndParatroop = new ArrayList<>();
    bomberAndParatroop.addAll(germany.getUnits().getMatches(Matches.unitIsAirTransport()));
    // add 2 infantry
    bomberAndParatroop.addAll(germany.getUnits().getUnits(GameDataTestUtil.infantry(gameData), 2));
    // move the units to east poland
    final String error = moveDelegate(gameData).move(bomberAndParatroop, new Route(germany, poland, eastPoland));
    assertError(error);
  }

  @Test
  public void testParatroopersMoveTwice() {
    // After a battle move to put a bomber + infantry (paratroop) in a first enemy
    // territory, you can make a new move (in the same battle move round) to put
    // bomber+ infantry in a more internal enemy territory.
    final PlayerID germans = germans(gameData);
    final Territory germany = territory("Germany", gameData);
    final Territory poland = territory("Poland", gameData);
    final Territory eastPoland = territory("East Poland", gameData);
    final Territory beloRussia = territory("Belorussia", gameData);
    final ITestDelegateBridge bridge = getDelegateBridge(germans);
    bridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    TechAttachment.get(germans).setParatroopers("true");
    List<Unit> paratroopers = germany.getUnits().getMatches(Matches.unitIsAirTransportable());
    paratroopers = paratroopers.subList(0, 1);
    final List<Unit> bomberAndParatroop = new ArrayList<>(paratroopers);
    bomberAndParatroop.addAll(germany.getUnits().getMatches(Matches.unitIsAirTransport()));
    final Route route = new Route(germany, poland, eastPoland);
    final List<Unit> airTransports = germany.getUnits().getMatches(Matches.unitIsAirTransport());
    for (final Unit airTransport : airTransports) {
      for (final Unit unit : paratroopers) {
        final Change change = TransportTracker.loadTransportChange((TripleAUnit) airTransport, unit);
        bridge.addChange(change);
      }
    }
    // move the units to east poland
    // airTransports
    String error = moveDelegate(gameData).move(bomberAndParatroop, route);
    assertValid(error);
    // try to move them further, this should fail
    error = moveDelegate(gameData).move(bomberAndParatroop, new Route(eastPoland, beloRussia));
    assertError(error);
  }

  @Test
  public void testParatroopersFlyOverBlitzedTerritory() {
    // We should be able to blitz a territory, then fly over it with paratroops to battle.
    final PlayerID germans = germans(gameData);
    final Territory germany = territory("Germany", gameData);
    final Territory poland = territory("Poland", gameData);
    final Territory eastPoland = territory("East Poland", gameData);
    final Territory beloRussia = territory("Belorussia", gameData);
    // Clear East Poland
    removeFrom(eastPoland, eastPoland.getUnits().getUnits());
    // Set up test
    final ITestDelegateBridge bridge = getDelegateBridge(germans);
    bridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    TechAttachment.get(germans).setParatroopers("true");
    List<Unit> paratrooper = germany.getUnits().getMatches(Matches.unitIsAirTransportable());
    paratrooper = paratrooper.subList(0, 1);
    final List<Unit> bomberAndParatroop = new ArrayList<>(paratrooper);
    bomberAndParatroop.addAll(germany.getUnits().getMatches(Matches.unitIsAirTransport()));
    final List<Unit> tanks = poland.getUnits().getMatches(Matches.unitCanBlitz());
    move(tanks, new Route(poland, eastPoland, beloRussia));
    final List<Unit> airTransports = Matches.getMatches(bomberAndParatroop, Matches.unitIsAirTransport());
    for (final Unit airTransport : airTransports) {
      for (final Unit unit : paratrooper) {
        final Change change = TransportTracker.loadTransportChange((TripleAUnit) airTransport, unit);
        bridge.addChange(change);
      }
    }
    // Verify paratroops can overfly blitzed territory
    final String error =
        moveDelegate(gameData).move(bomberAndParatroop, new Route(germany, poland, eastPoland, beloRussia));
    assertValid(error);
  }

  @Test
  public void testDefencelessTransportsDie() {
    final PlayerID british = british(gameData);
    final ITestDelegateBridge bridge = getDelegateBridge(british);
    bridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    final Territory uk = territory("United Kingdom", gameData);
    final Territory sz5 = territory("5 Sea Zone", gameData);
    // remove the sub
    removeFrom(sz5, sz5.getUnits().getMatches(Matches.unitIsSub()));

    when(dummyPlayer.retreatQuery(any(), anyBoolean(), any(),
        any(), any())).thenAnswer(new Answer<Territory>() {
          @Override
          public Territory answer(final InvocationOnMock invocation) throws Throwable {
            throw new IllegalStateException(
                "Should not be asked to retreat:" + invocation.getArgument(4));
          }
        });
    bridge.setRemote(dummyPlayer);
    move(uk.getUnits().getMatches(Matches.unitIsAir()), gameData.getMap().getRoute(uk, sz5));
    // move units for amphib assault
    moveDelegate(gameData).end();
    bridge.setStepName("Combat");
    // cook the dice so that 1 british fighters hits, and nothing else
    // this will leave 1 transport alone in the sea zone
    bridge.setRandomSource(new ScriptedRandomSource(1, 5, 5, 5, 5, 5, 5, 5, 5));
    battleDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    battleDelegate(gameData).start();
    // make sure the transports died
    assertTrue(sz5.getUnits().getMatches(Matches.unitIsOwnedBy(germans(gameData))).isEmpty());
  }

  @Test
  public void testFighterLandsWhereCarrierCanBePlaced() {
    final PlayerID germans = germans(gameData);
    // germans have 1 carrier to place
    addTo(germans, carrier(gameData).create(1, germans), gameData);
    // start the move phase
    final ITestDelegateBridge bridge = getDelegateBridge(germans);
    bridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    bridge.setRemote(dummyPlayer);
    // the fighter should be able to move and hover in the sea zone
    // the fighter has no movement left
    final Territory neEurope = territory("Northwestern Europe", gameData);
    final Route route = new Route(neEurope, territory("Germany", gameData), territory("Poland", gameData),
        territory("Baltic States", gameData), territory("5 Sea Zone", gameData));
    // the fighter should be able to move, and hover in the sea zone until the carrier is placed
    move(neEurope.getUnits().getMatches(Matches.unitIsAir()), route);
  }

  @Test
  public void testFighterCantHoverWithNoCarrierToPlace() {
    // start the move phase
    final ITestDelegateBridge bridge = getDelegateBridge(germans(gameData));
    bridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(bridge);
    moveDelegate(gameData).start();
    bridge.setRemote(dummyPlayer);
    // the fighter should not be able to move and hover in the sea zone
    // since their are no carriers to place
    // the fighter has no movement left
    final Territory neEurope = territory("Northwestern Europe", gameData);
    final Route route = new Route(neEurope, territory("Germany", gameData), territory("Poland", gameData),
        territory("Baltic States", gameData), territory("5 Sea Zone", gameData));
    final String error = moveDelegate(gameData).move(neEurope.getUnits().getMatches(Matches.unitIsAir()), route);
    assertNotNull(error);
  }

  @Test
  public void testRepair() {
    final Territory germany = territory("Germany", gameData);
    final Unit factory = germany.getUnits().getMatches(Matches.unitCanBeDamaged()).get(0);
    final PurchaseDelegate del = purchaseDelegate(gameData);
    del.setDelegateBridgeAndPlayer(getDelegateBridge(germans(gameData)));
    del.start();
    // Set up player
    final PlayerID germans = GameDataTestUtil.germans(gameData);
    final int initPUs = germans.getResources().getQuantity("PUs");
    // damage a factory
    IntegerMap<Unit> startHits = new IntegerMap<>();
    startHits.put(factory, 1);
    gameData.performChange(ChangeFactory.bombingUnitDamage(startHits));
    assertEquals(((TripleAUnit) factory).getUnitDamage(), 1);
    RepairRule repair = germans(gameData).getRepairFrontier().getRules().get(0);
    IntegerMap<RepairRule> repairs = new IntegerMap<>();
    repairs.put(repair, 1);
    String error = del.purchaseRepair(Collections.singletonMap(
        Matches.getMatches(germany.getUnits().getUnits(), Matches.unitCanBeDamaged()).iterator().next(), repairs));
    assertValid(error);
    assertEquals(((TripleAUnit) factory).getUnitDamage(), 0);
    // Find cost
    final int midPUs = germans.getResources().getQuantity("PUs");
    assertEquals(initPUs, midPUs + 1);
    /*
     * INCREASED_FACTORY_PRODUCTION repairs
     */
    // Set up INCREASED_FACTORY_PRODUCTION
    final ITestDelegateBridge delegateBridge = getDelegateBridge(germans(gameData));
    TechTracker.addAdvance(germans, delegateBridge,
        TechAdvance.findAdvance(TechAdvance.TECH_PROPERTY_INCREASED_FACTORY_PRODUCTION, gameData, germans));
    // damage a factory
    startHits = new IntegerMap<>();
    startHits.put(factory, 2);
    gameData.performChange(ChangeFactory.bombingUnitDamage(startHits));
    assertEquals(((TripleAUnit) factory).getUnitDamage(), 2);
    repair = germans(gameData).getRepairFrontier().getRules().get(0);
    repairs = new IntegerMap<>();
    repairs.put(repair, 2);
    error = del.purchaseRepair(Collections.singletonMap(
        Matches.getMatches(germany.getUnits().getUnits(), Matches.unitCanBeDamaged()).iterator().next(), repairs));
    assertValid(error);
    assertEquals(((TripleAUnit) factory).getUnitDamage(), 0);
    // Find cost
    final int finalPUs = germans.getResources().getQuantity("PUs");
    assertEquals(midPUs, finalPUs + 1);
  }

  @Test
  public void testRepairMoreThanDamaged() {
    final Territory germany = territory("Germany", gameData);
    final Unit factory = germany.getUnits().getMatches(Matches.unitCanBeDamaged()).get(0);
    final PurchaseDelegate del = purchaseDelegate(gameData);
    del.setDelegateBridgeAndPlayer(getDelegateBridge(germans(gameData)));
    del.start();
    // dame a factory
    final IntegerMap<Unit> startHits = new IntegerMap<>();
    startHits.put(factory, 1);
    gameData.performChange(ChangeFactory.bombingUnitDamage(startHits));
    assertEquals(((TripleAUnit) factory).getUnitDamage(), 1);
    final RepairRule repair = germans(gameData).getRepairFrontier().getRules().get(0);
    final IntegerMap<RepairRule> repairs = new IntegerMap<>();
    // we have 1 damaged marker, but trying to repair 2
    repairs.put(repair, 2);
    final String error = del.purchaseRepair(Collections.singletonMap(
        Matches.getMatches(germany.getUnits().getUnits(), Matches.unitCanBeDamaged()).iterator().next(), repairs));
    // it is no longer an error, we just math max 0 it
    assertValid(error);
    assertEquals(((TripleAUnit) factory).getUnitDamage(), 0);
  }

  @Test
  public void testOccupiedTerrOfAttachment() {
    // Set up test
    final PlayerID british = GameDataTestUtil.british(gameData);
    final ITestDelegateBridge delegateBridge = getDelegateBridge(british(gameData));
    // Set up the move delegate
    final MoveDelegate moveDelegate = moveDelegate(gameData);
    delegateBridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(delegateBridge);
    moveDelegate(gameData).start();
    // Set up the territories
    final Territory hupeh = territory("Hupeh", gameData);
    final Territory kiangsu = territory("Kiangsu", gameData);
    // Remove all units
    removeFrom(kiangsu, kiangsu.getUnits().getUnits());
    removeFrom(hupeh, hupeh.getUnits().getUnits());
    // Set up the unit types
    addTo(hupeh, infantry(gameData).create(1, british));
    // Get units
    final Collection<Unit> moveUnits = hupeh.getUnits().getUnits();
    // Get Owner prior to battle
    final String preOwner = kiangsu.getOwner().getName();
    assertEquals(preOwner, Constants.PLAYER_NAME_JAPANESE);
    // add a VALID attack
    final String validResults = moveDelegate.move(moveUnits, new Route(hupeh, kiangsu));
    assertValid(validResults);
    // Ensure owner after attack doesn't match attacker
    final String postOwner = kiangsu.getOwner().getName();
    assertNotSame(postOwner, Constants.PLAYER_NAME_BRITISH);
    // Check that original owner is now owner
    assertEquals(postOwner, Constants.PLAYER_NAME_CHINESE);
  }

  @Test
  public void testOccupiedTerrOfAttachmentWithCapital() {
    // Set up test
    final PlayerID british = GameDataTestUtil.british(gameData);
    final ITestDelegateBridge delegateBridge = getDelegateBridge(british(gameData));
    // Set up the move delegate
    final MoveDelegate moveDelegate = moveDelegate(gameData);
    delegateBridge.setStepName("CombatMove");
    moveDelegate(gameData).setDelegateBridgeAndPlayer(delegateBridge);
    moveDelegate(gameData).start();
    // Set up the territories
    final Territory hupeh = territory("Hupeh", gameData);
    final Territory kiangsu = territory("Kiangsu", gameData);
    final Territory mongolia = territory("Mongolia", gameData);
    // Remove original capital
    final TerritoryAttachment taMongolia = TerritoryAttachment.get(mongolia);
    final TerritoryAttachment taKiangsu = TerritoryAttachment.get(kiangsu);
    try {
      final String noVal = null;
      taMongolia.setCapital(noVal);
      // Set as NEW capital
      taKiangsu.setCapital(Constants.PLAYER_NAME_CHINESE);
    } catch (final GameParseException e) {
      ClientLogger.logError(e);
    }
    // Remove all units
    removeFrom(kiangsu, kiangsu.getUnits().getUnits());
    removeFrom(hupeh, hupeh.getUnits().getUnits());
    // Set up the unit types
    addTo(hupeh, infantry(gameData).create(1, british));
    // Get units
    final Collection<Unit> moveUnits = hupeh.getUnits().getUnits();
    // Get Owner prior to battle
    final String preOwner = kiangsu.getOwner().getName();
    assertEquals(preOwner, Constants.PLAYER_NAME_JAPANESE);
    // add a VALID attack
    final String validResults = moveDelegate.move(moveUnits, new Route(hupeh, kiangsu));
    assertValid(validResults);
    // Ensure owner after attack doesn't match attacker
    final String postOwner = kiangsu.getOwner().getName();
    assertNotSame(postOwner, Constants.PLAYER_NAME_BRITISH);
    // Check that original owner is now owner
    assertEquals(postOwner, Constants.PLAYER_NAME_CHINESE);
  }

  @Test
  public void testTwoStepBlitz() {
    final ITestDelegateBridge delegateBridge = getDelegateBridge(british(gameData));
    // Set up the territories
    final Territory libya = territory("Libya", gameData);
    final Territory egypt = territory("Egypt", gameData);
    final Territory morrocco = territory("Morocco Algeria", gameData);
    removeFrom(libya, libya.getUnits().getUnits());
    // Set up the move delegate
    final MoveDelegate moveDelegate = moveDelegate(gameData);
    delegateBridge.setStepName("CombatMove");
    moveDelegate.setDelegateBridgeAndPlayer(delegateBridge);
    moveDelegate.start();
    // blitz in two steps
    final Collection<Unit> armour = egypt.getUnits().getMatches(Matches.unitCanBlitz());
    move(armour, new Route(egypt, libya));
    assertEquals(libya.getOwner(), british(gameData));
    move(armour, new Route(libya, morrocco));
  }

  public void assertValid(final String string) {
    assertNull(string, string);
  }

  public void assertError(final String string) {
    assertNotNull(string, string);
  }
}
