package games.strategy.triplea.ui.menubar;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.WindowConstants;
import javax.swing.tree.DefaultMutableTreeNode;
import games.strategy.debug.ClientLogger;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.ProductionRule;
import games.strategy.engine.data.Resource;
import games.strategy.engine.data.UnitType;
import games.strategy.engine.data.export.GameDataExporter;
import games.strategy.engine.framework.GameDataUtils;
import games.strategy.engine.history.HistoryNode;
import games.strategy.engine.history.Round;
import games.strategy.engine.history.Step;
import games.strategy.engine.stats.IStat;
import games.strategy.triplea.attachments.UnitAttachment;
import games.strategy.triplea.delegate.EndRoundDelegate;
import games.strategy.triplea.printgenerator.SetupFrame;
import games.strategy.triplea.ui.ExtendedStats;
import games.strategy.triplea.ui.IUIContext;
import games.strategy.triplea.ui.TripleAFrame;
import games.strategy.triplea.ui.export.ScreenshotExporter;
import games.strategy.triplea.ui.history.HistoryPanel;
import games.strategy.triplea.util.PlayerOrderComparator;
import games.strategy.ui.SwingAction;
import games.strategy.util.IllegalCharacterRemover;
import games.strategy.util.LocalizeHTML;
public class ExportMenu {
private final TripleAFrame frame;
private final GameData gameData;
private final IUIContext iuiContext;
public ExportMenu(final TripleAMenuBar menuBar, final TripleAFrame frame) {
this.frame = frame;
gameData = frame.getGame().getData();
iuiContext = frame.getUIContext();
final JMenu menuGame = new JMenu("Export");
menuGame.setMnemonic(KeyEvent.VK_E);
menuBar.add(menuGame);
addExportXML(menuGame);
addExportStats(menuGame);
addExportStatsFull(menuGame);
addExportSetupCharts(menuGame);
addExportUnitStats(menuGame);
addSaveScreenshot(menuGame);
}
// TODO: create a second menu option for parsing current attachments
private void addExportXML(final JMenu parentMenu) {
final Action exportXML = SwingAction.of("Export game.xml File (Beta)", e -> exportXMLFile());
parentMenu.add(exportXML).setMnemonic(KeyEvent.VK_X);
}
private void exportXMLFile() {
final JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
final File rootDir = new File(System.getProperties().getProperty("user.dir"));
final DateFormat formatDate = new SimpleDateFormat("yyyy_MM_dd");
int round = 0;
try {
gameData.acquireReadLock();
round = gameData.getSequence().getRound();
} finally {
gameData.releaseReadLock();
}
String defaultFileName = "xml_" + formatDate.format(new Date()) + "_" + gameData.getGameName() + "_round_" + round;
defaultFileName = IllegalCharacterRemover.removeIllegalCharacter(defaultFileName);
defaultFileName = defaultFileName + ".xml";
chooser.setSelectedFile(new File(rootDir, defaultFileName));
if (chooser.showSaveDialog(frame) != JOptionPane.OK_OPTION) {
return;
}
final String xmlFile;
try {
gameData.acquireReadLock();
final GameDataExporter exporter = new games.strategy.engine.data.export.GameDataExporter(gameData);
xmlFile = exporter.getXML();
} finally {
gameData.releaseReadLock();
}
try {
try (final FileWriter writer = new FileWriter(chooser.getSelectedFile());) {
writer.write(xmlFile);
}
} catch (final IOException e1) {
ClientLogger.logQuietly(e1);
}
}
private void addSaveScreenshot(final JMenu parentMenu) {
final AbstractAction abstractAction = SwingAction.of("Export Map Snapshot", e -> {
// get current history node. if we are in history view, get the selected node.
final HistoryPanel historyPanel = frame.getHistoryPanel();
final HistoryNode curNode;
if (historyPanel == null) {
curNode = gameData.getHistory().getLastNode();
} else {
curNode = historyPanel.getCurrentNode();
}
ScreenshotExporter.exportScreenshot(frame, gameData, curNode);
});
parentMenu.add(abstractAction).setMnemonic(KeyEvent.VK_E);
}
private void addExportStatsFull(final JMenu parentMenu) {
final Action showDiceStats = SwingAction.of("Export Full Game Stats", e -> createAndSaveStats(true));
parentMenu.add(showDiceStats).setMnemonic(KeyEvent.VK_F);
}
private void addExportStats(final JMenu parentMenu) {
final Action showDiceStats = SwingAction.of("Export Short Game Stats", e -> createAndSaveStats(false));
parentMenu.add(showDiceStats).setMnemonic(KeyEvent.VK_S);
}
private void createAndSaveStats(final boolean showPhaseStats) {
final ExtendedStats statPanel = new ExtendedStats(gameData, iuiContext);
final JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
final File rootDir = new File(System.getProperties().getProperty("user.dir"));
final DateFormat formatDate = new SimpleDateFormat("yyyy_MM_dd");
int currentRound = 0;
try {
gameData.acquireReadLock();
currentRound = gameData.getSequence().getRound();
} finally {
gameData.releaseReadLock();
}
String defaultFileName = "stats_" + formatDate.format(new Date()) + "_" + gameData.getGameName() + "_round_"
+ currentRound + (showPhaseStats ? "_full" : "_short");
defaultFileName = IllegalCharacterRemover.removeIllegalCharacter(defaultFileName);
defaultFileName = defaultFileName + ".csv";
chooser.setSelectedFile(new File(rootDir, defaultFileName));
if (chooser.showSaveDialog(frame) != JOptionPane.OK_OPTION) {
return;
}
final StringBuilder text = new StringBuilder(1000);
GameData clone;
try {
gameData.acquireReadLock();
clone = GameDataUtils.cloneGameData(gameData);
final IStat[] stats = statPanel.getStats();
// extended stats covers stuff that doesn't show up in the game stats menu bar, like custom resources or tech
// tokens or # techs, etc.
final IStat[] statsExtended = statPanel.getStatsExtended(gameData);
final String[] alliances = statPanel.getAlliances().toArray(new String[statPanel.getAlliances().size()]);
final PlayerID[] players = statPanel.getPlayers().toArray(new PlayerID[statPanel.getPlayers().size()]);
// its important here to translate the player objects into our game data
// the players for the stat panel are only relevant with respect to
// the game data they belong to
for (int i = 0; i < players.length; i++) {
players[i] = clone.getPlayerList().getPlayerID(players[i].getName());
}
text.append(defaultFileName + ",");
text.append("\n");
text.append("TripleA Engine Version: ,");
text.append(games.strategy.engine.ClientContext.engineVersion() + ",");
text.append("\n");
text.append("Game Name: ,");
text.append(gameData.getGameName() + ",");
text.append("\n");
text.append("Game Version: ,");
text.append(gameData.getGameVersion() + ",");
text.append("\n");
text.append("\n");
text.append("Current Round: ,");
text.append(currentRound + ",");
text.append("\n");
text.append("Number of Players: ,");
text.append(statPanel.getPlayers().size() + ",");
text.append("\n");
text.append("Number of Alliances: ,");
text.append(statPanel.getAlliances().size() + ",");
text.append("\n");
text.append("\n");
text.append("Turn Order: ,");
text.append("\n");
final List<PlayerID> playerOrderList = new ArrayList<>();
playerOrderList.addAll(gameData.getPlayerList().getPlayers());
Collections.sort(playerOrderList, new PlayerOrderComparator(gameData));
final Set<PlayerID> playerOrderSetNoDuplicates = new LinkedHashSet<>(playerOrderList);
final Iterator<PlayerID> playerOrderIterator = playerOrderSetNoDuplicates.iterator();
while (playerOrderIterator.hasNext()) {
final PlayerID currentPlayerID = playerOrderIterator.next();
text.append(currentPlayerID.getName()).append(",");
final Iterator<String> allianceName =
gameData.getAllianceTracker().getAlliancesPlayerIsIn(currentPlayerID).iterator();
while (allianceName.hasNext()) {
text.append(allianceName.next()).append(",");
}
text.append("\n");
}
text.append("\n");
text.append("Winners: ,");
final EndRoundDelegate delegateEndRound = (EndRoundDelegate) gameData.getDelegateList().getDelegate("endRound");
if (delegateEndRound != null && delegateEndRound.getWinners() != null) {
for (final PlayerID p : delegateEndRound.getWinners()) {
text.append(p.getName()).append(",");
}
} else {
text.append("none yet; game not over,");
}
text.append("\n");
text.append("\n");
text.append("Resource Chart: ,");
text.append("\n");
final Iterator<Resource> resourceIterator = gameData.getResourceList().getResources().iterator();
while (resourceIterator.hasNext()) {
text.append(resourceIterator.next().getName() + ",");
text.append("\n");
}
// if short, we won't both showing production and unit info
if (showPhaseStats) {
text.append("\n");
text.append("Production Rules: ,");
text.append("\n");
text.append("Name,Result,Quantity,Cost,Resource,\n");
final Iterator<ProductionRule> purchaseOptionsIterator =
gameData.getProductionRuleList().getProductionRules().iterator();
while (purchaseOptionsIterator.hasNext()) {
final ProductionRule pr = purchaseOptionsIterator.next();
String costString = pr.toStringCosts().replaceAll("; ", ",");
costString = costString.replaceAll(" ", ",");
text.append(pr.getName()).append(",").append(pr.getResults().keySet().iterator().next().getName()).append(",")
.append(pr.getResults().getInt(pr.getResults().keySet().iterator().next())).append(",").append(costString)
.append(",");
text.append("\n");
}
text.append("\n");
text.append("Unit Types: ,");
text.append("\n");
text.append("Name,Listed Abilities\n");
final Iterator<UnitType> allUnitsIterator = gameData.getUnitTypeList().iterator();
while (allUnitsIterator.hasNext()) {
final UnitAttachment ua = UnitAttachment.get(allUnitsIterator.next());
if (ua == null) {
continue;
}
String toModify = ua.allUnitStatsForExporter();
toModify = toModify.replaceFirst("UnitType called ", "").replaceFirst(" with:", "")
.replaceAll("games.strategy.engine.data.", "").replaceAll("\n", ";").replaceAll(",", ";");
toModify = toModify.replaceAll(" ", ",");
toModify = toModify.replaceAll(", ", ",").replaceAll(" ,", ",");
text.append(toModify);
text.append("\n");
}
}
text.append("\n");
text.append((showPhaseStats ? "Full Stats (includes each phase that had activity),"
: "Short Stats (only shows first phase with activity per player per round),"));
text.append("\n");
text.append("Turn Stats: ,");
text.append("\n");
text.append("Round,Player Turn,Phase Name,");
for (final IStat stat : stats) {
for (final PlayerID player : players) {
text.append(stat.getName()).append(" ");
text.append(player.getName());
text.append(",");
}
for (final String alliance : alliances) {
text.append(stat.getName()).append(" ");
text.append(alliance);
text.append(",");
}
}
for (final IStat element : statsExtended) {
for (final PlayerID player : players) {
text.append(element.getName()).append(" ");
text.append(player.getName());
text.append(",");
}
for (final String alliance : alliances) {
text.append(element.getName()).append(" ");
text.append(alliance);
text.append(",");
}
}
text.append("\n");
clone.getHistory().gotoNode(clone.getHistory().getLastNode());
@SuppressWarnings("unchecked")
final Enumeration<HistoryNode> nodes =
((DefaultMutableTreeNode) clone.getHistory().getRoot()).preorderEnumeration();
PlayerID currentPlayer = null;
int round = 0;
while (nodes.hasMoreElements()) {
// we want to export on change of turn
final HistoryNode element = nodes.nextElement();
if (element instanceof Round) {
round++;
}
if (!(element instanceof Step)) {
continue;
}
final Step step = (Step) element;
if (step.getPlayerID() == null || step.getPlayerID().isNull()) {
continue;
}
// this is to stop from having multiple entries for each players turn.
if (!showPhaseStats) {
if (step.getPlayerID() == currentPlayer) {
continue;
}
}
currentPlayer = step.getPlayerID();
clone.getHistory().gotoNode(element);
final String playerName = step.getPlayerID() == null ? "" : step.getPlayerID().getName() + ": ";
String stepName = step.getStepName();
// copied directly from TripleAPlayer, will probably have to be updated in the future if more delegates are made
if (stepName.endsWith("Bid")) {
stepName = "Bid";
} else if (stepName.endsWith("Tech")) {
stepName = "Tech";
} else if (stepName.endsWith("TechActivation")) {
stepName = "TechActivation";
} else if (stepName.endsWith("Purchase")) {
stepName = "Purchase";
} else if (stepName.endsWith("NonCombatMove")) {
stepName = "NonCombatMove";
} else if (stepName.endsWith("Move")) {
stepName = "Move";
} else if (stepName.endsWith("Battle")) {
stepName = "Battle";
} else if (stepName.endsWith("BidPlace")) {
stepName = "BidPlace";
} else if (stepName.endsWith("Place")) {
stepName = "Place";
} else if (stepName.endsWith("Politics")) {
stepName = "Politics";
} else if (stepName.endsWith("EndTurn")) {
stepName = "EndTurn";
} else {
stepName = "";
}
text.append(round).append(",").append(playerName).append(",").append(stepName).append(",");
for (final IStat stat : stats) {
for (final PlayerID player : players) {
text.append(stat.getFormatter().format(stat.getValue(player, clone)));
text.append(",");
}
for (final String alliance : alliances) {
text.append(stat.getFormatter().format(stat.getValue(alliance, clone)));
text.append(",");
}
}
for (final IStat element2 : statsExtended) {
for (final PlayerID player : players) {
text.append(element2.getFormatter().format(element2.getValue(player, clone)));
text.append(",");
}
for (final String alliance : alliances) {
text.append(element2.getFormatter().format(element2.getValue(alliance, clone)));
text.append(",");
}
}
text.append("\n");
}
} finally {
gameData.releaseReadLock();
}
try (final FileWriter writer = new FileWriter(chooser.getSelectedFile())) {
writer.write(text.toString());
} catch (final IOException e1) {
ClientLogger.logQuietly(e1);
}
}
private void addExportUnitStats(final JMenu parentMenu) {
final JMenuItem menuFileExport = new JMenuItem(SwingAction.of("Export Unit Charts", e -> {
final JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
final File rootDir = new File(System.getProperties().getProperty("user.dir"));
String defaultFileName = gameData.getGameName() + "_unit_stats";
defaultFileName = IllegalCharacterRemover.removeIllegalCharacter(defaultFileName);
defaultFileName = defaultFileName + ".html";
chooser.setSelectedFile(new File(rootDir, defaultFileName));
if (chooser.showSaveDialog(frame) != JOptionPane.OK_OPTION) {
return;
}
try (final FileWriter writer = new FileWriter(chooser.getSelectedFile())) {
writer.write(
HelpMenu.getUnitStatsTable(gameData, iuiContext).replaceAll("<p>", "<p>\r\n").replaceAll("</p>", "</p>\r\n")
.replaceAll("</tr>", "</tr>\r\n").replaceAll(LocalizeHTML.PATTERN_HTML_IMG_TAG, ""));
} catch (final IOException e1) {
ClientLogger.logQuietly(e1);
}
}));
menuFileExport.setMnemonic(KeyEvent.VK_U);
parentMenu.add(menuFileExport);
}
private void addExportSetupCharts(final JMenu parentMenu) {
final JMenuItem menuFileExport = new JMenuItem(SwingAction.of("Export Setup Charts", e -> {
final JFrame frame = new JFrame("Export Setup Charts");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
GameData clonedGameData;
gameData.acquireReadLock();
try {
clonedGameData = GameDataUtils.cloneGameData(gameData);
} finally {
gameData.releaseReadLock();
}
final JComponent newContentPane = new SetupFrame(clonedGameData);
// content panes must be opaque
newContentPane.setOpaque(true);
frame.setContentPane(newContentPane);
// Display the window.
frame.pack();
frame.setLocationRelativeTo(frame);
frame.setVisible(true);
iuiContext.addShutdownWindow(frame);
}));
menuFileExport.setMnemonic(KeyEvent.VK_C);
parentMenu.add(menuFileExport);
}
}