package games.strategy.engine.random;
import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.Window;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.net.SocketException;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import games.strategy.debug.ClientLogger;
import games.strategy.ui.SwingAction;
/**
* Its a bit messy, but the threads are a pain to deal with We want to be able
* to call this from any thread, and have a dialog that doesnt close until the
* dice roll finishes. If there is an error we wait until we get a good roll
* before returning.
*/
public class PBEMDiceRoller implements IRandomSource {
private final String m_gameUUID;
private final IRemoteDiceServer m_remoteDiceServer;
private static Frame s_focusWindow;
/**
* If the game has multiple frames, allows the ui to
* set what frame should be the parent of the dice rolling window
* if set to null, or not set, we try to guess by finding the currently
* focused window (or a visble window if none are focused).
*/
public static void setFocusWindow(final Frame w) {
s_focusWindow = w;
}
public PBEMDiceRoller(final IRemoteDiceServer diceServer, final String gameUUID) {
m_remoteDiceServer = diceServer;
m_gameUUID = gameUUID;
}
/**
* Do a test roll, leaving the dialog open after the roll is done.
*/
public void test() {
// TODO: do a test based on data.getDiceSides()
final HttpDiceRollerDialog dialog =
new HttpDiceRollerDialog(getFocusedFrame(), 6, 1, "Test", m_remoteDiceServer, "test-roll");
dialog.setTest();
dialog.roll();
}
@Override
public int[] getRandom(final int max, final int count, final String annotation) throws IllegalStateException {
if (!SwingUtilities.isEventDispatchThread()) {
final AtomicReference<int[]> result = new AtomicReference<>();
SwingAction.invokeAndWait(() -> result.set(getRandom(max, count, annotation)));
return result.get();
}
final HttpDiceRollerDialog dialog =
new HttpDiceRollerDialog(getFocusedFrame(), max, count, annotation, m_remoteDiceServer, m_gameUUID);
dialog.roll();
return dialog.getDiceRoll();
}
private static Frame getFocusedFrame() {
if (s_focusWindow != null) {
return s_focusWindow;
}
final Frame[] frames = Frame.getFrames();
Frame rVal = null;
for (final Frame frame : frames) {
// find the window with focus, failing that, get something that is
// visible
if (frame.isFocused()) {
rVal = frame;
} else if (rVal == null && frame.isVisible()) {
rVal = frame;
}
}
return rVal;
}
@Override
public int getRandom(final int max, final String annotation) throws IllegalStateException {
return getRandom(max, 1, annotation)[0];
}
}
/**
* The dialog that will show while the dice are rolling.
*/
class HttpDiceRollerDialog extends JDialog {
private static final long serialVersionUID = -4802403913826489223L;
private final JButton m_exitButton = new JButton("Exit");
private final JButton m_reRollButton = new JButton("Roll Again");
private final JButton m_okButton = new JButton("OK");
private final JTextArea m_text = new JTextArea();
private int[] m_diceRoll;
private final int m_count;
private final int m_sides;
private final String m_subjectMessage;
private final String m_gameID;
private final IRemoteDiceServer m_diceServer;
private final String m_gameUUID;
private final Object m_lock = new Object();
public boolean m_test = false;
private final JPanel m_buttons = new JPanel();
private Window m_owner;
/**
* @param owner
* owner frame.
* @param sides
* the number of sides on the dice
* @param count
* the number of dice rolled
* @param subjectMessage
* the subject for the email the dice roller will send (if it sends emails)
* @param diceServer
* the dice server implementation
* @param gameUUID
* the TripleA game UUID or null
*/
public HttpDiceRollerDialog(final Frame owner, final int sides, final int count, final String subjectMessage,
final IRemoteDiceServer diceServer, final String gameUUID) {
super(owner, "Dice roller", true);
m_owner = owner;
m_sides = sides;
m_count = count;
m_subjectMessage = subjectMessage;
m_gameID = diceServer.getGameId() == null ? "" : diceServer.getGameId();
m_diceServer = diceServer;
m_gameUUID = gameUUID;
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
m_exitButton.addActionListener(e -> System.exit(-1));
m_exitButton.setEnabled(false);
m_reRollButton.addActionListener(e -> rollInternal());
m_okButton.addActionListener(e -> closeAndReturn());
m_reRollButton.setEnabled(false);
getContentPane().setLayout(new BorderLayout());
m_buttons.add(m_exitButton);
m_buttons.add(m_reRollButton);
getContentPane().add(m_buttons, BorderLayout.SOUTH);
getContentPane().add(new JScrollPane(m_text));
m_text.setEditable(false);
setSize(400, 300);
// games.strategy.ui.Util
games.strategy.ui.Util.center(this);
}
/**
* There are three differences when we are testing, 1 dont close the window
* when we are done 2 remove the exit button 3 add a close button.
*/
public void setTest() {
m_test = true;
m_buttons.removeAll();
m_buttons.add(m_okButton);
m_buttons.add(m_reRollButton);
}
public void appendText(final String aString) {
m_text.setText(m_text.getText() + aString);
}
public void notifyError() {
SwingUtilities.invokeLater(() -> {
m_exitButton.setEnabled(true);
m_reRollButton.setEnabled(true);
});
}
public int[] getDiceRoll() {
return m_diceRoll;
}
// should only be called if we are not visible
// can be called from any thread
// wont return until the roll is done.
public void roll() throws IllegalStateException {
// if we are not the event thread, then start again in the event thread
// pausing this thread until we are done
if (!SwingUtilities.isEventDispatchThread()) {
synchronized (m_lock) {
SwingUtilities.invokeLater(() -> roll());
try {
m_lock.wait();
} catch (final InterruptedException e) {
ClientLogger.logQuietly(e);
}
}
return;
}
rollInternal();
setVisible(true);
}
// should be called from the event thread
private void rollInternal() throws IllegalStateException {
if (!SwingUtilities.isEventDispatchThread()) {
throw new IllegalStateException("Wrong thread");
}
m_reRollButton.setEnabled(false);
m_exitButton.setEnabled(false);
final Thread t = new Thread("Triplea, roll in seperate thread") {
@Override
public void run() {
rollInSeperateThread();
}
};
t.start();
}
private void closeAndReturn() {
// releast any threads waiting on the lock
if (m_lock != null) {
synchronized (m_lock) {
m_lock.notifyAll();
}
}
SwingUtilities.invokeLater(() -> {
setVisible(false);
m_owner.toFront();
m_owner = null;
dispose();
});
}
/**
* should be called from a thread other than the event thread after we are
* open (or at least in the process of opening) will close the window and
* notify any waiting threads when completed successfully.
* Before contacting Irony Dice Server, check if email has a reasonable
* valid syntax.
*/
private void rollInSeperateThread() throws IllegalStateException {
if (SwingUtilities.isEventDispatchThread()) {
throw new IllegalStateException("Wrong thread");
}
while (!isVisible()) {
Thread.yield();
}
appendText(m_subjectMessage + "\n");
appendText("Contacting " + m_diceServer.getDisplayName() + "\n");
String text = null;
try {
text = m_diceServer.postRequest(m_sides, m_count, m_subjectMessage, m_gameID, m_gameUUID);
if (text.length() == 0) {
appendText("Nothing could be read from dice server\n");
appendText("Please check your firewall settings");
notifyError();
}
if (!m_test) {
appendText("Contacted :" + text + "\n");
}
m_diceRoll = m_diceServer.getDice(text, m_count);
appendText("Success!");
if (!m_test) {
closeAndReturn();
}
} catch (final SocketException ex) { // an error in networking
appendText("Connection failure:" + ex.getMessage() + "\n"
+ "Please ensure your Internet connection is working, and try again.");
notifyError();
} catch (final InvocationTargetException e) {
appendText("\nError:" + e.getMessage() + "\n\n");
appendText("Text from dice server:\n" + text + "\n");
notifyError();
} catch (final IOException ex) {
try {
appendText("An error has occured!\n");
appendText("Possible reasons the error could have happened:\n");
appendText(" 1: An invalid e-mail address\n");
appendText(" 2: Firewall could be blocking TripleA from connecting to the Dice Server\n");
appendText(" 3: The e-mail address does not exist\n");
appendText(" 4: An unknown error, please see the error console and consult the forums for help\n");
appendText(" Visit https://forums.triplea-game.org for extra help\n");
if (text != null) {
appendText("Text from dice server:\n" + text + "\n");
}
final StringWriter writer = new StringWriter();
ex.printStackTrace(new PrintWriter(writer));
writer.close();
appendText(writer.toString());
} catch (final IOException ex1) {
ex1.printStackTrace();
}
notifyError();
}
}
}