package games.strategy.triplea.ui;
import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.MouseEvent;
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.Map.Entry;
import java.util.Vector;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.TechnologyFrontier;
import games.strategy.triplea.Constants;
import games.strategy.triplea.attachments.PlayerAttachment;
import games.strategy.triplea.delegate.TechAdvance;
import games.strategy.triplea.delegate.TechTracker;
import games.strategy.triplea.delegate.dataObjects.TechRoll;
import games.strategy.ui.ScrollableTextField;
import games.strategy.ui.ScrollableTextFieldListener;
import games.strategy.ui.SwingAction;
import games.strategy.util.IntegerMap;
import games.strategy.util.Util;
public class TechPanel extends ActionPanel {
private static final long serialVersionUID = -6477919141575138007L;
private final JLabel m_actionLabel = new JLabel();
private TechRoll m_techRoll;
private int m_currTokens = 0;
private int m_quantity;
private IntegerMap<PlayerID> m_whoPaysHowMuch = null;
/** Creates new TechPanel. */
public TechPanel(final GameData data, final MapPanel map) {
super(data, map);
}
@Override
public void display(final PlayerID id) {
super.display(id);
SwingUtilities.invokeLater(() -> {
removeAll();
m_actionLabel.setText(id.getName() + " Tech Roll");
add(m_actionLabel);
if (isWW2V3TechModel()) {
add(new JButton(getTechTokenAction));
add(new JButton(JustRollTech));
} else {
add(new JButton(getTechRollsAction));
add(new JButton(DontBother));
}
});
}
@Override
public String toString() {
return "TechPanel";
}
public TechRoll waitForTech() {
if (getAvailableTechs().isEmpty()) {
return null;
}
waitForRelease();
if (m_techRoll == null) {
return null;
}
if (m_techRoll.getRolls() == 0) {
return null;
}
return m_techRoll;
}
private List<TechAdvance> getAvailableTechs() {
final Collection<TechAdvance> currentAdvances = TechTracker.getCurrentTechAdvances(getCurrentPlayer(), getData());
final Collection<TechAdvance> allAdvances = TechAdvance.getTechAdvances(getData(), getCurrentPlayer());
return Util.difference(allAdvances, currentAdvances);
}
private List<TechnologyFrontier> getAvailableCategories() {
final Collection<TechnologyFrontier> currentAdvances =
TechTracker.getFullyResearchedPlayerTechCategories(getData(), getCurrentPlayer());
final Collection<TechnologyFrontier> allAdvances =
TechAdvance.getPlayerTechCategories(getData(), getCurrentPlayer());
return Util.difference(allAdvances, currentAdvances);
}
private final Action getTechRollsAction = SwingAction.of("Roll Tech...", e -> {
TechAdvance advance = null;
if (isWW2V2() || (isSelectableTechRoll() && !isWW2V3TechModel())) {
final List<TechAdvance> available = getAvailableTechs();
if (available.isEmpty()) {
JOptionPane.showMessageDialog(TechPanel.this, "No more available tech advances");
return;
}
final JList<TechAdvance> list = new JList<>(new Vector<>(available));
final JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.add(list, BorderLayout.CENTER);
panel.add(new JLabel("Select the tech you want to roll for"), BorderLayout.NORTH);
list.setSelectedIndex(0);
final int choice = JOptionPane.showConfirmDialog(JOptionPane.getFrameForComponent(TechPanel.this), panel,
"Select advance", JOptionPane.PLAIN_MESSAGE);
if (choice != JOptionPane.OK_OPTION) {
return;
}
advance = list.getSelectedValue();
}
final int PUs = getCurrentPlayer().getResources().getQuantity(Constants.PUS);
final String message = "Roll Tech";
final TechRollPanel techRollPanel = new TechRollPanel(PUs, getCurrentPlayer());
final int choice = JOptionPane.showConfirmDialog(getTopLevelAncestor(), techRollPanel, message,
JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null);
if (choice != JOptionPane.OK_OPTION) {
return;
}
final int quantity = techRollPanel.getValue();
if (advance == null) {
m_techRoll = new TechRoll(null, quantity);
} else {
try {
getData().acquireReadLock();
final TechnologyFrontier front = new TechnologyFrontier("", getData());
front.addAdvance(advance);
m_techRoll = new TechRoll(front, quantity);
} finally {
getData().releaseReadLock();
}
}
release();
});
private final Action DontBother = SwingAction.of("Done", e -> {
m_techRoll = null;
release();
});
private final Action getTechTokenAction = SwingAction.of("Buy Tech Tokens...", e -> {
final PlayerID currentPlayer = getCurrentPlayer();
m_currTokens = currentPlayer.getResources().getQuantity(Constants.TECH_TOKENS);
// Notify user if there are no more techs to acheive
final List<TechnologyFrontier> techCategories = getAvailableCategories();
if (techCategories.isEmpty()) {
JOptionPane.showMessageDialog(TechPanel.this, "No more available tech advances");
return;
}
final JList<TechnologyFrontier> list = new JList<TechnologyFrontier>(new Vector<>(techCategories)) {
private static final long serialVersionUID = 35094445315520702L;
@Override
public String getToolTipText(final MouseEvent e) {
final int index = locationToIndex(e.getPoint());
if (-1 < index) {
return getTechListToolTipText(getModel().getElementAt(index));
} else {
return null;
}
}
};
final JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.add(list, BorderLayout.CENTER);
panel.add(new JLabel("Select which tech chart you want to roll for"), BorderLayout.NORTH);
list.setSelectedIndex(0);
JOptionPane.showMessageDialog(JOptionPane.getFrameForComponent(TechPanel.this), panel, "Select chart",
JOptionPane.PLAIN_MESSAGE);
final TechnologyFrontier category = list.getSelectedValue();
final int PUs = currentPlayer.getResources().getQuantity(Constants.PUS);
final String message = "Purchase Tech Tokens";
// see if anyone will help us to pay
Collection<PlayerID> helpPay;
final PlayerAttachment pa = PlayerAttachment.get(currentPlayer);
if (pa != null) {
helpPay = pa.getHelpPayTechCost();
} else {
helpPay = null;
}
final TechTokenPanel techTokenPanel = new TechTokenPanel(PUs, m_currTokens, currentPlayer, helpPay);
final int choice = JOptionPane.showConfirmDialog(getTopLevelAncestor(), techTokenPanel, message,
JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null);
if (choice != JOptionPane.OK_OPTION) {
return;
}
m_quantity = techTokenPanel.getValue();
m_whoPaysHowMuch = techTokenPanel.getWhoPaysHowMuch();
m_currTokens += m_quantity;
m_techRoll = new TechRoll(category, m_currTokens, m_quantity, m_whoPaysHowMuch);
m_techRoll.setNewTokens(m_quantity);
release();
});
private final Action JustRollTech = SwingAction.of("Done/Roll Current Tokens", e -> {
m_currTokens = getCurrentPlayer().getResources().getQuantity(Constants.TECH_TOKENS);
// If this player has tokens, roll them.
if (m_currTokens > 0) {
final List<TechnologyFrontier> techCategories = getAvailableCategories();
if (techCategories.isEmpty()) {
return;
}
final JList<TechnologyFrontier> list = new JList<TechnologyFrontier>(new Vector<>(techCategories)) {
private static final long serialVersionUID = -8415987764855418565L;
@Override
public String getToolTipText(final MouseEvent e) {
final int index = locationToIndex(e.getPoint());
if (-1 < index) {
return getTechListToolTipText(getModel().getElementAt(index));
} else {
return null;
}
}
};
final JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.add(list, BorderLayout.CENTER);
panel.add(new JLabel("Select which tech chart you want to roll for"), BorderLayout.NORTH);
list.setSelectedIndex(0);
JOptionPane.showMessageDialog(JOptionPane.getFrameForComponent(TechPanel.this), panel, "Select chart",
JOptionPane.PLAIN_MESSAGE);
final TechnologyFrontier category = list.getSelectedValue();
m_techRoll = new TechRoll(category, m_currTokens);
} else {
m_techRoll = null;
}
release();
});
private static String getTechListToolTipText(final TechnologyFrontier techCategory) {
final List<TechAdvance> techList = techCategory.getTechs();
if (techList.size() <= 1) {
return null;
}
final Collection<TechAdvance> listedAlready = new HashSet<>();
final StringBuilder strTechCategory = new StringBuilder("Available Techs: ");
final Iterator<TechAdvance> iterTechList = techList.iterator();
while (iterTechList.hasNext()) {
final TechAdvance advance = iterTechList.next();
if (listedAlready.contains(advance)) {
continue;
} else {
listedAlready.add(advance);
}
final int freq = Collections.frequency(techList, advance);
strTechCategory.append(advance.getName()).append(freq > 1 ? " (" + freq + "/" + techList.size() + ")" : "");
if (iterTechList.hasNext()) {
strTechCategory.append(", ");
}
}
return strTechCategory.toString();
}
}
class TechRollPanel extends JPanel {
private static final long serialVersionUID = -3794742986339086059L;
int m_PUs;
PlayerID m_player;
JLabel m_left = new JLabel();
ScrollableTextField m_textField;
TechRollPanel(final int PUs, final PlayerID player) {
setLayout(new GridBagLayout());
m_PUs = PUs;
m_player = player;
final JLabel title = new JLabel("Select the number of tech rolls:");
title.setBorder(new javax.swing.border.EmptyBorder(5, 5, 5, 5));
m_textField = new ScrollableTextField(0, PUs / TechTracker.getTechCost(player));
final ScrollableTextFieldListener m_listener =
stf -> setLabel(m_PUs - (TechTracker.getTechCost(m_player) * m_textField.getValue()));
m_textField.addChangeListener(m_listener);
final JLabel costLabel = new JLabel("x" + TechTracker.getTechCost(m_player));
setLabel(PUs);
final int space = 0;
add(title, new GridBagConstraints(0, 0, 3, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.NONE,
new Insets(5, 5, space, space), 0, 0));
add(m_textField, new GridBagConstraints(0, 1, 1, 1, 0.5, 1, GridBagConstraints.EAST, GridBagConstraints.NONE,
new Insets(8, 10, space, space), 0, 0));
add(costLabel, new GridBagConstraints(1, 1, 1, 1, 0.5, 1, GridBagConstraints.WEST, GridBagConstraints.NONE,
new Insets(8, 5, space, 2), 0, 0));
add(m_left, new GridBagConstraints(0, 2, 3, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.NONE,
new Insets(10, 5, space, space), 0, 0));
}
private void setLabel(final int PUs) {
m_left.setText("Left to spend:" + PUs);
}
public int getValue() {
return m_textField.getValue();
}
}
class TechTokenPanel extends JPanel {
private static final long serialVersionUID = 332026624893335993L;
int m_totalPUs;
int m_playerPUs;
final ScrollableTextField m_playerPUField;
PlayerID m_player;
JLabel m_left = new JLabel();
JLabel m_right = new JLabel();
JLabel m_totalCost = new JLabel();
ScrollableTextField m_textField;
HashMap<PlayerID, ScrollableTextField> m_whoPaysTextFields = null;
TechTokenPanel(final int PUs, final int currTokens, final PlayerID player, final Collection<PlayerID> helpPay) {
m_playerPUs = PUs;
m_totalPUs = PUs;
if (helpPay != null && !helpPay.isEmpty()) {
helpPay.remove(player);
for (final PlayerID p : helpPay) {
m_totalPUs += p.getResources().getQuantity(Constants.PUS);
}
}
m_playerPUField = new ScrollableTextField(0, m_totalPUs);
m_playerPUField.setEnabled(false);
setLayout(new GridBagLayout());
m_player = player;
final JLabel title = new JLabel("Select the number of tech tokens to purchase:");
title.setBorder(new javax.swing.border.EmptyBorder(5, 5, 5, 5));
final int techCost = TechTracker.getTechCost(m_player);
m_textField = new ScrollableTextField(0, m_totalPUs / techCost);
final ScrollableTextFieldListener m_listener = stf -> {
setLabel(TechTracker.getTechCost(m_player) * m_textField.getValue());
setWidgetActivation();
};
m_textField.addChangeListener(m_listener);
final JLabel costLabel = new JLabel("x" + techCost + " cost per token");
setLabel(0);
setTokens(currTokens);
final int space = 0;
add(title, new GridBagConstraints(0, 0, 3, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.NONE,
new Insets(5, 5, space, space), 0, 0));
add(m_textField, new GridBagConstraints(0, 1, 1, 1, 0.5, 1, GridBagConstraints.EAST, GridBagConstraints.NONE,
new Insets(8, 10, space, space), 0, 0));
add(costLabel, new GridBagConstraints(1, 1, 1, 1, 0.5, 1, GridBagConstraints.WEST, GridBagConstraints.NONE,
new Insets(8, 5, space, 2), 0, 0));
add(m_left, new GridBagConstraints(0, 2, 3, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.NONE,
new Insets(10, 5, space, space), 0, 0));
add(m_right, new GridBagConstraints(0, 2, 3, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.NONE,
new Insets(10, 130, space, space), 0, 0));
add(m_totalCost, new GridBagConstraints(0, 3, 3, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.NONE,
new Insets(10, 5, space, space), 0, 0));
if (helpPay != null && !helpPay.isEmpty()) {
if (m_whoPaysTextFields == null) {
m_whoPaysTextFields = new HashMap<>();
}
helpPay.remove(player);
int row = 4;
add(new JLabel("Nations Paying How Much:"), new GridBagConstraints(0, row, 1, 1, 0.5, 1, GridBagConstraints.EAST,
GridBagConstraints.NONE, new Insets(30, 6, 6, 6), 0, 0));
row++;
add(new JLabel(player.getName()), new GridBagConstraints(0, row, 1, 1, 0.5, 1, GridBagConstraints.CENTER,
GridBagConstraints.NONE, new Insets(6, 6, 6, 6), 0, 0));
add(m_playerPUField, new GridBagConstraints(1, row, 1, 1, 0.5, 1, GridBagConstraints.CENTER,
GridBagConstraints.NONE, new Insets(6, 6, 6, 6), 0, 0));
add(new JLabel("PUs"), new GridBagConstraints(2, row, 1, 1, 0.5, 1, GridBagConstraints.CENTER,
GridBagConstraints.NONE, new Insets(6, 6, 6, 6), 0, 0));
row++;
for (final PlayerID p : helpPay) {
final int helperPUs = p.getResources().getQuantity(Constants.PUS);
if (helperPUs > 0) {
final ScrollableTextField whoPaysTextField = new ScrollableTextField(0, helperPUs);
whoPaysTextField.addChangeListener(setWidgetAction());
m_whoPaysTextFields.put(p, whoPaysTextField);
// TODO: force players to pay if it goes above the cost m_player can afford.
add(new JLabel(p.getName()), new GridBagConstraints(0, row, 1, 1, 0.5, 1, GridBagConstraints.CENTER,
GridBagConstraints.NONE, new Insets(6, 6, 6, 6), 0, 0));
add(whoPaysTextField, new GridBagConstraints(1, row, 1, 1, 0.5, 1, GridBagConstraints.CENTER,
GridBagConstraints.NONE, new Insets(6, 6, 6, 6), 0, 0));
add(new JLabel("PUs"), new GridBagConstraints(2, row, 1, 1, 0.5, 1, GridBagConstraints.CENTER,
GridBagConstraints.NONE, new Insets(6, 6, 6, 6), 0, 0));
row++;
}
}
}
}
private void setWidgetActivation() {
if (m_whoPaysTextFields == null || m_whoPaysTextFields.isEmpty()) {
return;
}
final int cost = TechTracker.getTechCost(m_player) * m_textField.getValue();
int totalPaidByOthers = 0;
for (final Entry<PlayerID, ScrollableTextField> entry : m_whoPaysTextFields.entrySet()) {
totalPaidByOthers += Math.max(0, entry.getValue().getValue());
}
final int totalPaidByPlayer = Math.max(0, cost - totalPaidByOthers);
int amountOver = -1 * (m_playerPUs - totalPaidByPlayer);
final Iterator<Entry<PlayerID, ScrollableTextField>> otherPayers = m_whoPaysTextFields.entrySet().iterator();
while (amountOver > 0 && otherPayers.hasNext()) {
final Entry<PlayerID, ScrollableTextField> entry = otherPayers.next();
int current = entry.getValue().getValue();
final int max = entry.getValue().getMax();
if (current < max) {
final int canAdd = Math.min(max - current, amountOver);
amountOver -= canAdd;
current += canAdd;
entry.getValue().setValue(current);
}
}
// now check if we are negative
totalPaidByOthers = 0;
for (final Entry<PlayerID, ScrollableTextField> entry : m_whoPaysTextFields.entrySet()) {
totalPaidByOthers += Math.max(0, entry.getValue().getValue());
}
int amountUnder = -1 * (cost - totalPaidByOthers);
final Iterator<Entry<PlayerID, ScrollableTextField>> otherPayers2 = m_whoPaysTextFields.entrySet().iterator();
while (amountUnder > 0 && otherPayers2.hasNext()) {
final Entry<PlayerID, ScrollableTextField> entry = otherPayers2.next();
int current = entry.getValue().getValue();
if (current > 0) {
final int canSubtract = Math.min(current, amountUnder);
amountUnder -= canSubtract;
current -= canSubtract;
entry.getValue().setValue(current);
}
}
m_playerPUField.setValue(Math.max(0, Math.min(m_playerPUs, totalPaidByPlayer)));
}
private void setLabel(final int cost) {
m_left.setText("Left to Spend: " + (m_totalPUs - cost));
m_totalCost.setText("Total Cost: " + cost);
}
private void setTokens(final int tokens) {
m_right.setText("Current token count: " + tokens);
}
private ScrollableTextFieldListener setWidgetAction() {
return stf -> setWidgetActivation();
}
public int getValue() {
return m_textField.getValue();
}
public IntegerMap<PlayerID> getWhoPaysHowMuch() {
final int techCost = TechTracker.getTechCost(m_player);
final int numberOfTechRolls = getValue();
final int totalCost = numberOfTechRolls * techCost;
final IntegerMap<PlayerID> whoPaysHowMuch = new IntegerMap<>();
if (m_whoPaysTextFields == null || m_whoPaysTextFields.isEmpty()) {
whoPaysHowMuch.put(m_player, totalCost);
} else {
int runningTotal = 0;
for (final Entry<PlayerID, ScrollableTextField> entry : m_whoPaysTextFields.entrySet()) {
final int value = entry.getValue().getValue();
whoPaysHowMuch.put(entry.getKey(), value);
runningTotal += value;
}
if (!m_whoPaysTextFields.containsKey(m_player)) {
whoPaysHowMuch.put(m_player, Math.max(0, totalCost - runningTotal));
}
}
return whoPaysHowMuch;
}
}