package games.strategy.triplea.delegate;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import games.strategy.engine.data.Change;
import games.strategy.engine.data.CompositeChange;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.Route;
import games.strategy.engine.data.Territory;
import games.strategy.engine.data.Unit;
import games.strategy.engine.delegate.IDelegateBridge;
import games.strategy.triplea.attachments.UnitAttachment;
import games.strategy.triplea.delegate.dataObjects.MoveDescription;
import games.strategy.triplea.player.ITripleAPlayer;
import games.strategy.triplea.ui.MovePanel;
import games.strategy.util.CompositeMatchAnd;
import games.strategy.util.Match;
import games.strategy.util.Util;
/**
* Contains all the data to describe a move and to undo it.
*/
public class UndoableMove extends AbstractUndoableMove {
private static final long serialVersionUID = 8490182214651531358L;
private String m_reasonCantUndo;
private String m_description;
// this move is dependent on these moves
// these moves cant be undone until this one has been
private final Set<UndoableMove> m_iDependOn = new HashSet<>();
// these moves depend on me
// we cant be undone until this is empty
private final Set<UndoableMove> m_dependOnMe = new HashSet<>();
// list of countries we took over
private final Set<Territory> m_conquered = new HashSet<>();
// transports loaded by this move
private final Set<Unit> m_loaded = new HashSet<>();
// transports unloaded by this move
private final Set<Unit> m_unloaded = new HashSet<>();
private final Route m_route;
public void addToConquered(final Territory t) {
m_conquered.add(t);
}
public Route getRoute() {
return m_route;
}
public boolean getcanUndo() {
return m_reasonCantUndo == null && m_dependOnMe.isEmpty();
}
public String getReasonCantUndo() {
if (m_reasonCantUndo != null) {
return m_reasonCantUndo;
} else if (!m_dependOnMe.isEmpty()) {
return "Move " + (m_dependOnMe.iterator().next().getIndex() + 1) + " must be undone first";
} else {
throw new IllegalStateException("no reason");
}
}
public void setCantUndo(final String reason) {
m_reasonCantUndo = reason;
}
public String getDescription() {
return m_description;
}
public void setDescription(final String description) {
m_description = description;
}
public UndoableMove(final Collection<Unit> units, final Route route) {
super(new CompositeChange(), units);
m_route = route;
}
public void load(final Unit transport) {
m_loaded.add(transport);
}
public void unload(final Unit transport) {
m_unloaded.add(transport);
}
@Override
protected void undoSpecific(final IDelegateBridge bridge) {
final GameData data = bridge.getData();
final BattleTracker battleTracker = DelegateFinder.battleDelegate(data).getBattleTracker();
battleTracker.undoBattle(m_route, m_units, bridge.getPlayerID(), bridge);
// clean up dependencies
final Iterator<UndoableMove> iter1 = m_iDependOn.iterator();
while (iter1.hasNext()) {
final UndoableMove other = iter1.next();
other.m_dependOnMe.remove(this);
}
// if we are moving out of a battle zone, mark it
// this can happen for air units moving out of a battle zone
for (final IBattle battle : battleTracker.getPendingBattles(m_route.getStart(), null)) {
if (battle == null || battle.isOver()) {
continue;
}
for (final Unit unit : m_units) {
final Route routeUnitUsedToMove =
DelegateFinder.moveDelegate(data).getRouteUsedToMoveInto(unit, m_route.getStart());
if (!battle.getBattleType().isBombingRun()) {
// route units used to move will be null in the case
// where an enemy sub is submerged in the territory, and another unit
// moved in to attack it, but some of the units in the original
// territory are moved out. Undoing this last move, the route used to move
// into the battle zone will be null
if (routeUnitUsedToMove != null) {
final Change change = battle.addAttackChange(routeUnitUsedToMove, Collections.singleton(unit), null);
bridge.addChange(change);
}
} else {
HashMap<Unit, HashSet<Unit>> targets = null;
Unit target = null;
if (routeUnitUsedToMove != null && routeUnitUsedToMove.getEnd() != null) {
final Territory end = routeUnitUsedToMove.getEnd();
final Collection<Unit> enemyTargetsTotal = end.getUnits()
.getMatches(new CompositeMatchAnd<>(Matches.enemyUnit(bridge.getPlayerID(), data),
Matches.UnitIsAtMaxDamageOrNotCanBeDamaged(end).invert(),
Matches.unitIsBeingTransported().invert()));
final Collection<Unit> enemyTargets = Match.getMatches(enemyTargetsTotal,
Matches.unitIsOfTypes(UnitAttachment.getAllowedBombingTargetsIntersection(
Match.getMatches(Collections.singleton(unit), Matches.UnitIsStrategicBomber), data)));
if (enemyTargets.size() > 1
&& games.strategy.triplea.Properties.getDamageFromBombingDoneToUnitsInsteadOfTerritories(data)
&& !games.strategy.triplea.Properties.getRaidsMayBePreceededByAirBattles(data)) {
while (target == null) {
target = ((ITripleAPlayer) bridge.getRemotePlayer(bridge.getPlayerID())).whatShouldBomberBomb(end,
enemyTargets, Collections.singletonList(unit));
}
} else if (!enemyTargets.isEmpty()) {
target = enemyTargets.iterator().next();
}
if (target != null) {
targets = new HashMap<>();
targets.put(target, new HashSet<>(Collections.singleton(unit)));
}
}
final Change change = battle.addAttackChange(routeUnitUsedToMove, Collections.singleton(unit), targets);
bridge.addChange(change);
}
}
}
// Clear any temporary dependents
MovePanel.clearDependents(m_units);
}
/**
* Update the dependencies.
*
* @param undoableMoves
* list of moves that should be undone
*/
public void initializeDependencies(final List<UndoableMove> undoableMoves) {
for (final UndoableMove other : undoableMoves) {
if (other == null) {
System.err.println(undoableMoves);
throw new IllegalStateException("other should not be null");
}
if (// if the other move has moves that depend on this
!Util.intersection(other.getUnits(), this.getUnits()).isEmpty()
// if the other move has transports that we are loading
|| !Util.intersection(other.m_units, this.m_loaded).isEmpty()
// or we are moving through a previously conqueured territory
// we should be able to take this out later
// we need to add logic for this move to take over the same territories
// when the other move is undone
|| !Util.intersection(other.m_conquered, m_route.getAllTerritories()).isEmpty()
// or we are unloading transports that have moved in another turn
|| !Util.intersection(other.m_units, this.m_unloaded).isEmpty()
|| !Util.intersection(other.m_unloaded, this.m_unloaded).isEmpty()) {
m_iDependOn.add(other);
other.m_dependOnMe.add(this);
}
}
}
// for use with airborne moving
public void addDependency(final List<UndoableMove> undoableMoves) {
for (final UndoableMove other : undoableMoves) {
addDependency(other);
}
}
// for use with airborne moving
public void addDependency(final UndoableMove undoableMove) {
m_iDependOn.add(undoableMove);
undoableMove.m_dependOnMe.add(this);
}
public boolean wasTransportUnloaded(final Unit transport) {
return m_unloaded.contains(transport);
}
public boolean wasTransportLoaded(final Unit transport) {
return m_loaded.contains(transport);
}
@Override
public String toString() {
return "UndoableMove index;" + m_index + " description:" + m_description;
}
@Override
public final String getMoveLabel() {
return m_route.getStart() + " -> " + m_route.getEnd();
}
@Override
public final Territory getEnd() {
return m_route.getEnd();
}
public final Territory getStart() {
return m_route.getStart();
}
@Override
protected final MoveDescription getDescriptionObject() {
return new MoveDescription(m_units, m_route);
}
}