package games.strategy.triplea.ui;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Image;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import games.strategy.engine.data.Change;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.Territory;
import games.strategy.engine.data.Unit;
import games.strategy.engine.data.UnitType;
import games.strategy.engine.data.events.GameDataChangeListener;
import games.strategy.engine.stats.AbstractStat;
import games.strategy.engine.stats.IStat;
import games.strategy.triplea.Constants;
import games.strategy.triplea.Properties;
import games.strategy.triplea.attachments.PlayerAttachment;
import games.strategy.triplea.attachments.TerritoryAttachment;
import games.strategy.triplea.delegate.BattleCalculator;
import games.strategy.triplea.delegate.Matches;
import games.strategy.triplea.delegate.TechAdvance;
import games.strategy.triplea.delegate.TechTracker;
import games.strategy.util.IntegerMap;
import games.strategy.util.Match;
public class StatPanel extends AbstractStatPanel {
private static final long serialVersionUID = 4340684166664492498L;
private final StatTableModel m_dataModel;
private final TechTableModel m_techModel;
protected IStat[] m_stats;
private JTable m_statsTable;
private Image m_statsImage = null;
protected final Map<PlayerID, ImageIcon> m_mapPlayerImage = new HashMap<>();
protected IUIContext m_uiContext;
/** Creates a new instance of StatPanel. */
public StatPanel(final GameData data, final IUIContext uiContext2) {
super(data);
m_uiContext = uiContext2;
m_dataModel = new StatTableModel();
m_techModel = new TechTableModel();
fillPlayerIcons();
initLayout();
}
@Override
protected void initLayout() {
final boolean hasTech = !TechAdvance.getTechAdvances(m_data, null).isEmpty();
// do no include a grid box for tech if there is no tech
setLayout(new GridLayout((hasTech ? 2 : 1), 1));
m_statsTable = new JTable(m_dataModel) {
private static final long serialVersionUID = -5516554955307630864L;
@Override
public void print(final Graphics g) {
if (m_statsImage != null) {
g.drawImage(m_statsImage, 0, 0, null, null);
}
super.print(g);
}
};
m_statsTable.getTableHeader().setReorderingAllowed(false);
m_statsTable.getColumnModel().getColumn(0).setPreferredWidth(175);
JScrollPane scroll = new JScrollPane(m_statsTable);
add(scroll);
// if no technologies, do not show the tech table
if (!hasTech) {
return;
}
final JTable m_techTable = new JTable(m_techModel);
m_techTable.getTableHeader().setReorderingAllowed(false);
m_techTable.getColumnModel().getColumn(0).setPreferredWidth(500);
// setupIconHeaders(m_techTable);
// show icons for players:
final TableCellRenderer componentRenderer = new JComponentTableCellRenderer();
for (int i = 1; i < m_techTable.getColumnCount(); i++) {
final TableColumn column = m_techTable.getColumnModel().getColumn(i);
column.setHeaderRenderer(componentRenderer);
final String player = m_techTable.getColumnName(i);
final JLabel value = new JLabel("", getIcon(player), SwingConstants.CENTER);
value.setToolTipText(player);
column.setHeaderValue(value);
}
scroll = new JScrollPane(m_techTable);
add(scroll);
}
@Override
public void setGameData(final GameData data) {
m_data = data;
m_dataModel.setGameData(data);
m_techModel.setGameData(data);
m_dataModel.gameDataChanged(null);
m_techModel.gameDataChanged(null);
}
public void setStatsBgImage(final Image image) {
m_statsImage = image;
}
public JTable getStatsTable() {
return m_statsTable;
}
/**
* Gets the small flag for a given PlayerID.
*
* @param player
* the player to get the flag for
* @return ImageIcon small flag
*/
protected ImageIcon getIcon(final PlayerID player) {
ImageIcon icon = m_mapPlayerImage.get(player);
if (icon == null && m_uiContext != null) {
final Image img = m_uiContext.getFlagImageFactory().getSmallFlag(player);
icon = new ImageIcon(img);
icon.setDescription(player.getName());
m_mapPlayerImage.put(player, icon);
}
return icon;
}
protected ImageIcon getIcon(final String playerName) {
final PlayerID player = this.m_data.getPlayerList().getPlayerID(playerName);
if (player == null) {
return null;
}
return getIcon(player);
}
protected void fillPlayerIcons() {
for (final PlayerID p : m_data.getPlayerList().getPlayers()) {
getIcon(p);
}
}
class JComponentTableCellRenderer implements TableCellRenderer {
@Override
public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected,
final boolean hasFocus, final int row, final int column) {
return (JComponent) value;
}
}
/**
* Custom table model.
* This model is thread safe.
*/
class StatTableModel extends AbstractTableModel implements GameDataChangeListener {
private static final long serialVersionUID = -6156153062049822444L;
/* Flag to indicate whether data needs to be recalculated */
private boolean m_isDirty = true;
/* Column Header Names */
/* Underlying data for the table */
private String[][] m_collectedData;
public StatTableModel() {
setStatCollums();
m_data.addDataChangeListener(this);
m_isDirty = true;
}
public void setStatCollums() {
m_stats = new IStat[] {new PUStat(), new ProductionStat(), new UnitsStat(), new TUVStat()};
if (Match.someMatch(m_data.getMap().getTerritories(), Matches.TerritoryIsVictoryCity)) {
final List<IStat> stats = new ArrayList<>(Arrays.asList(m_stats));
stats.add(new VictoryCityStat());
m_stats = stats.toArray(new IStat[stats.size()]);
}
// only add the vps in pacific
if (m_data.getProperties().get(Constants.PACIFIC_THEATER, false)) {
final List<IStat> stats = new ArrayList<>(Arrays.asList(m_stats));
stats.add(new VPStat());
m_stats = stats.toArray(new IStat[stats.size()]);
}
}
private synchronized void loadData() {
m_data.acquireReadLock();
try {
final List<PlayerID> players = getPlayers();
final Collection<String> alliances = getAlliances();
m_collectedData = new String[players.size() + alliances.size()][m_stats.length + 1];
int row = 0;
for (final PlayerID player : players) {
m_collectedData[row][0] = player.getName();
for (int i = 0; i < m_stats.length; i++) {
m_collectedData[row][i + 1] = m_stats[i].getFormatter().format(m_stats[i].getValue(player, m_data));
}
row++;
}
final Iterator<String> allianceIterator = alliances.iterator();
while (allianceIterator.hasNext()) {
final String alliance = allianceIterator.next();
m_collectedData[row][0] = alliance;
for (int i = 0; i < m_stats.length; i++) {
m_collectedData[row][i + 1] = m_stats[i].getFormatter().format(m_stats[i].getValue(alliance, m_data));
}
row++;
}
} finally {
m_data.releaseReadLock();
}
}
@Override
public void gameDataChanged(final Change aChange) {
synchronized (this) {
m_isDirty = true;
}
SwingUtilities.invokeLater(() -> repaint());
}
/*
* Recalcs the underlying data in a lazy manner Limitation: This is not
* a threadsafe implementation
*/
@Override
public synchronized Object getValueAt(final int row, final int col) {
if (m_isDirty) {
loadData();
m_isDirty = false;
}
return m_collectedData[row][col];
}
// Trivial implementations of required methods
@Override
public String getColumnName(final int col) {
if (col == 0) {
return "Player";
}
return m_stats[col - 1].getName();
}
@Override
public int getColumnCount() {
return m_stats.length + 1;
}
@Override
public synchronized int getRowCount() {
if (!m_isDirty) {
return m_collectedData.length;
} else {
// no need to recalculate all the stats just to get the row count
// getting the row count is a fairly frequent operation, and will
// happen even if we are not displayed!
m_data.acquireReadLock();
try {
return m_data.getPlayerList().size() + getAlliances().size();
} finally {
m_data.releaseReadLock();
}
}
}
public synchronized void setGameData(final GameData data) {
synchronized (this) {
m_data.removeDataChangeListener(this);
m_data = data;
m_data.addDataChangeListener(this);
m_isDirty = true;
}
repaint();
}
}
class TechTableModel extends AbstractTableModel implements GameDataChangeListener {
private static final long serialVersionUID = -4612476336419396081L;
/* Flag to indicate whether data needs to be recalculated */
private boolean isDirty = true;
/* Column Header Names */
/* Row Header Names */
private String[] colList;
/* Underlying data for the table */
private String[][] data;
/* Convenience mapping of country names -> col */
private Map<String, Integer> colMap = null;
/* Convenience mapping of technology names -> row */
private Map<String, Integer> rowMap = null;
public TechTableModel() {
m_data.addDataChangeListener(this);
initColList();
/* Load the country -> col mapping */
colMap = new HashMap<>();
for (int i = 0; i < colList.length; i++) {
colMap.put(colList[i], Integer.valueOf(i + 1));
}
/*
* .size()+1 added to stop index out of bounds errors when using an
* Italian player.
*/
boolean useTech = false;
try {
m_data.acquireReadLock();
if (m_data.getResourceList().getResource(Constants.TECH_TOKENS) != null) {
useTech = true;
data = new String[TechAdvance.getTechAdvances(m_data).size() + 1][colList.length + 2];
} else {
data = new String[TechAdvance.getTechAdvances(m_data).size()][colList.length + 1];
}
} finally {
m_data.releaseReadLock();
}
/* Load the technology -> row mapping */
rowMap = new HashMap<>();
final Iterator<TechAdvance> iter = TechAdvance.getTechAdvances(m_data, null).iterator();
int row = 0;
if (useTech) {
rowMap.put("Tokens", Integer.valueOf(row));
data[row][0] = "Tokens";
row++;
}
while (iter.hasNext()) {
final TechAdvance tech = iter.next();
rowMap.put((tech).getName(), Integer.valueOf(row));
data[row][0] = tech.getName();
row++;
}
clearAdvances();
}
private void clearAdvances() {
/* Initialize the table with the tech names */
for (int i = 0; i < data.length; i++) {
for (int j = 1; j <= colList.length; j++) {
data[i][j] = "";
}
}
}
private void initColList() {
final java.util.List<PlayerID> players = new ArrayList<>(m_data.getPlayerList().getPlayers());
colList = new String[players.size()];
for (int i = 0; i < players.size(); i++) {
colList[i] = players.get(i).getName();
}
Arrays.sort(colList, 0, players.size());
}
public void update() {
clearAdvances();
// copy so aquire/release read lock are on the same object!
final GameData gameData = m_data;
gameData.acquireReadLock();
try {
for (final PlayerID pid : gameData.getPlayerList().getPlayers()) {
if (colMap.get(pid.getName()) == null) {
throw new IllegalStateException("Unexpected player in GameData.getPlayerList()" + pid.getName());
}
final int col = colMap.get(pid.getName()).intValue();
int row = 0;
// boolean useTokens = false;
if (m_data.getResourceList().getResource(Constants.TECH_TOKENS) != null) {
// useTokens = true;
final int tokens = pid.getResources().getQuantity(Constants.TECH_TOKENS);
data[row][col] = Integer.toString(tokens);
}
final Iterator<TechAdvance> advancesAll = TechAdvance.getTechAdvances(m_data).iterator();
final List<TechAdvance> has = TechAdvance.getTechAdvances(m_data, pid);
while (advancesAll.hasNext()) {
final TechAdvance advance = advancesAll.next();
// if(!pid.getTechnologyFrontierList().getAdvances().contains(advance)){
if (!has.contains(advance)) {
row = rowMap.get(advance.getName()).intValue();
data[row][col] = "-";
}
}
final Iterator<TechAdvance> advances = TechTracker.getCurrentTechAdvances(pid, m_data).iterator();
while (advances.hasNext()) {
final TechAdvance advance = advances.next();
row = rowMap.get(advance.getName()).intValue();
// System.err.println("(" + row + ", " + col + ")");
data[row][col] = "X";
// data[row][col] = colList[col].substring(0, 1);
}
}
} finally {
gameData.releaseReadLock();
}
}
@Override
public String getColumnName(final int col) {
if (col == 0) {
return "Technology";
}
// return colList[col - 1].substring(0, 1);
return colList[col - 1];
}
/*
* Recalcs the underlying data in a lazy manner Limitation: This is not
* a threadsafe implementation
*/
@Override
public Object getValueAt(final int row, final int col) {
if (isDirty) {
update();
isDirty = false;
}
return data[row][col];
}
// Trivial implementations of required methods
@Override
public int getColumnCount() {
return colList.length + 1;
}
@Override
public int getRowCount() {
return data.length;
}
@Override
public void gameDataChanged(final Change aChange) {
isDirty = true;
SwingUtilities.invokeLater(() -> repaint());
}
public void setGameData(final GameData data) {
m_data.removeDataChangeListener(this);
m_data = data;
m_data.addDataChangeListener(this);
isDirty = true;
}
}
class ProductionStat extends AbstractStat {
@Override
public String getName() {
return "Production";
}
@Override
public double getValue(final PlayerID player, final GameData data) {
int rVal = 0;
for (final Territory place : data.getMap().getTerritories()) {
/*
* Match will Check if terr is a Land Convoy Route and check ownership of neighboring Sea Zone, or if contested
*/
if (place.getOwner().equals(player) && Matches.territoryCanCollectIncomeFrom(player, data).match(place)) {
rVal += TerritoryAttachment.getProduction(place);
}
}
rVal *= Properties.getPU_Multiplier(data);
return rVal;
}
}
class PUStat extends ResourceStat {
public PUStat() {
super(getResourcePUs(m_data));
}
}
class UnitsStat extends AbstractStat {
@Override
public String getName() {
return "Units";
}
@Override
public double getValue(final PlayerID player, final GameData data) {
int rVal = 0;
final Match<Unit> ownedBy = Matches.unitIsOwnedBy(player);
for (final Territory place : data.getMap().getTerritories()) {
rVal += place.getUnits().countMatches(ownedBy);
}
return rVal;
}
}
class TUVStat extends AbstractStat {
@Override
public String getName() {
return "TUV";
}
@Override
public double getValue(final PlayerID player, final GameData data) {
final IntegerMap<UnitType> costs = BattleCalculator.getCostsForTUV(player, data);
final Match<Unit> unitIsOwnedBy = Matches.unitIsOwnedBy(player);
int rVal = 0;
for (final Territory place : data.getMap().getTerritories()) {
final Collection<Unit> owned = place.getUnits().getMatches(unitIsOwnedBy);
rVal += BattleCalculator.getTUV(owned, costs);
}
return rVal;
}
}
class VictoryCityStat extends AbstractStat {
@Override
public String getName() {
return "VC";
}
@Override
public double getValue(final PlayerID player, final GameData data) {
int rVal = 0;
for (final Territory place : data.getMap().getTerritories()) {
if (!place.getOwner().equals(player)) {
continue;
}
final TerritoryAttachment ta = TerritoryAttachment.get(place);
if (ta == null) {
continue;
}
if (ta.getVictoryCity() != 0) {
rVal = rVal + ta.getVictoryCity();
}
}
return rVal;
}
}
class VPStat extends AbstractStat {
@Override
public String getName() {
return "VPs";
}
@Override
public double getValue(final PlayerID player, final GameData data) {
final PlayerAttachment pa = PlayerAttachment.get(player);
if (pa != null) {
return pa.getVps();
}
return 0;
}
}
}