package games.strategy.engine.history;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import games.strategy.engine.data.Change;
import games.strategy.engine.data.CompositeChange;
import games.strategy.engine.data.GameData;
import games.strategy.triplea.ui.history.HistoryPanel;
/**
* A history of the game.
* Stored as a tree, the data is organized as
* Root
* - Round
* - Step
* - Event
* - Child
* Round - the current round in the game, eg 1, 2, 3
* Step - the current step, eg Britian Combat Move
* Event - an event that happened in the game, eg Russia buys 8 inf.
*/
public class History extends DefaultTreeModel {
private static final long serialVersionUID = -1769876896869L;
private final HistoryWriter m_writer = new HistoryWriter(this);
private final List<Change> m_changes = new ArrayList<>();
private final GameData m_data;
private HistoryNode m_currentNode;
private void assertCorrectThread() {
if (m_data.areChangesOnlyInSwingEventThread() && !SwingUtilities.isEventDispatchThread()) {
throw new IllegalStateException("Wrong thread");
}
}
public History(final GameData data) {
super(new RootHistoryNode("Game History"));
m_data = data;
}
public HistoryWriter getHistoryWriter() {
return m_writer;
}
HistoryPanel m_panel = null;
public void setTreePanel(final HistoryPanel panel) {
m_panel = panel;
}
public void goToEnd() {
if (m_panel != null) {
m_panel.goToEnd();
}
}
public HistoryNode getLastNode() {
assertCorrectThread();
return getLastChildInternal((HistoryNode) getRoot());
}
private HistoryNode getLastChildInternal(final HistoryNode node) {
if (node.getChildCount() == 0) {
return node;
}
return getLastChildInternal((HistoryNode) node.getLastChild());
}
private int getLastChange(final HistoryNode node) {
int rVal;
if (node == getRoot()) {
rVal = 0;
} else if (node instanceof Event) {
rVal = ((Event) node).getChangeEndIndex();
} else if (node instanceof EventChild) {
rVal = ((Event) node.getParent()).getChangeEndIndex();
} else if (node instanceof IndexedHistoryNode) {
rVal = ((IndexedHistoryNode) node).getChangeStartIndex();
} else {
rVal = 0;
}
if (rVal == -1) {
return m_changes.size();
}
return rVal;
}
public Change getDelta(final HistoryNode start, final HistoryNode end) {
assertCorrectThread();
final int firstChange = getLastChange(start);
final int lastChange = getLastChange(end);
if (firstChange == lastChange) {
return null;
}
final List<Change> changes =
m_changes.subList(Math.min(firstChange, lastChange), Math.max(firstChange, lastChange));
final Change compositeChange = new CompositeChange(changes);
if (lastChange >= firstChange) {
return compositeChange;
} else {
return compositeChange.invert();
}
}
public synchronized void gotoNode(final HistoryNode node) {
assertCorrectThread();
getGameData().acquireWriteLock();
try {
if (m_currentNode == null) {
m_currentNode = getLastNode();
}
final Change dataChange = getDelta(m_currentNode, node);
m_currentNode = node;
if (dataChange != null) {
m_data.performChange(dataChange);
}
} finally {
getGameData().releaseWriteLock();
}
}
public synchronized void removeAllHistoryAfterNode(final HistoryNode removeAfterNode) {
gotoNode(removeAfterNode);
assertCorrectThread();
getGameData().acquireWriteLock();
try {
final int lastChange = getLastChange(removeAfterNode);
while (m_changes.size() > lastChange) {
m_changes.remove(lastChange);
}
final List<HistoryNode> nodesToRemove = new ArrayList<>();
final Enumeration<?> enumeration = ((DefaultMutableTreeNode) this.getRoot()).preorderEnumeration();
enumeration.nextElement();
boolean startRemoving = false;
while (enumeration.hasMoreElements()) {
final HistoryNode node = (HistoryNode) enumeration.nextElement();
if (node instanceof IndexedHistoryNode) {
final int index = ((IndexedHistoryNode) node).getChangeStartIndex();
if (index >= lastChange) {
startRemoving = true;
}
if (startRemoving) {
nodesToRemove.add(node);
}
}
}
while (!nodesToRemove.isEmpty()) {
this.removeNodeFromParent(nodesToRemove.remove(0));
}
} finally {
getGameData().releaseWriteLock();
}
}
synchronized void changeAdded(final Change aChange) {
m_changes.add(aChange);
if (m_currentNode == null) {
return;
}
if (m_currentNode == getLastNode()) {
m_data.performChange(aChange);
}
}
private Object writeReplace() {
return new SerializedHistory(this, m_data, m_changes);
}
List<Change> getChanges() {
return m_changes;
}
GameData getGameData() {
return m_data;
}
}
/**
* DefaultTreeModel is not serializable across jdk versions
* Instead we use an instance of this class to store our data.
*/
class SerializedHistory implements Serializable {
private static final long serialVersionUID = -5808427923253751651L;
private final List<SerializationWriter> m_Writers = new ArrayList<>();
private final GameData m_data;
public SerializedHistory(final History history, final GameData data, final List<Change> changes) {
m_data = data;
int changeIndex = 0;
final Enumeration<?> enumeration = ((DefaultMutableTreeNode) history.getRoot()).preorderEnumeration();
enumeration.nextElement();
while (enumeration.hasMoreElements()) {
final HistoryNode node = (HistoryNode) enumeration.nextElement();
// write the changes to the start of the node
if (node instanceof IndexedHistoryNode) {
while (changeIndex < ((IndexedHistoryNode) node).getChangeStartIndex()) {
m_Writers.add(new ChangeSerializationWriter(changes.get(changeIndex)));
changeIndex++;
}
}
// write the node itself
m_Writers.add(node.getWriter());
}
// write out remaining changes
while (changeIndex < changes.size()) {
m_Writers.add(new ChangeSerializationWriter(changes.get(changeIndex)));
changeIndex++;
}
}
public Object readResolve() {
final History rVal = new History(m_data);
final HistoryWriter historyWriter = rVal.getHistoryWriter();
for (final SerializationWriter element : m_Writers) {
element.write(historyWriter);
}
return rVal;
}
}
class RootHistoryNode extends HistoryNode {
private static final long serialVersionUID = 625147613043836829L;
public RootHistoryNode(final String title) {
super(title);
}
@Override
public SerializationWriter getWriter() {
throw new IllegalStateException("Not implemented");
}
}
class ChangeSerializationWriter implements SerializationWriter {
private static final long serialVersionUID = -3802807345707883606L;
private final Change aChange;
public ChangeSerializationWriter(final Change change) {
aChange = change;
}
@Override
public void write(final HistoryWriter writer) {
writer.addChange(aChange);
}
}