package games.strategy.engine.random;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.ProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import games.strategy.debug.ClientLogger;
import games.strategy.engine.ClientContext;
import games.strategy.engine.ClientFileSystemHelper;
import games.strategy.engine.framework.startup.ui.editors.DiceServerEditor;
import games.strategy.engine.framework.startup.ui.editors.EditorPanel;
import games.strategy.engine.framework.startup.ui.editors.IBean;
import games.strategy.engine.framework.system.HttpProxy;
import games.strategy.util.Util;
/**
* A pbem dice roller that reads its configuration from a properties file.
*/
public class PropertiesDiceRoller implements IRemoteDiceServer {
private static final long serialVersionUID = 6481409417543119539L;
/**
* Loads the property dice rollers from the properties file.
*
* @return the collection of available dice rollers
*/
public static Collection<PropertiesDiceRoller> loadFromFile() {
final List<PropertiesDiceRoller> rollers = new ArrayList<>();
final File f = new File(ClientFileSystemHelper.getRootFolder(), "dice_servers");
if (!f.exists()) {
throw new IllegalStateException("No dice server folder:" + f);
}
final java.util.List<Properties> propFiles = new ArrayList<>();
final File[] files = f.listFiles();
for (final File file : files) {
if (!file.isDirectory() && file.getName().endsWith(".properties")) {
try {
final Properties props = new Properties();
try (final FileInputStream fin = new FileInputStream(file)) {
props.load(fin);
propFiles.add(props);
}
} catch (final IOException e) {
System.out.println("error reading file:" + file);
ClientLogger.logQuietly(e);
}
}
}
Collections.sort(propFiles, (o1, o2) -> {
final int n1 = Integer.parseInt(o1.getProperty("order"));
final int n2 = Integer.parseInt(o2.getProperty("order"));
return n1 - n2;
});
for (final Properties prop : propFiles) {
rollers.add(new PropertiesDiceRoller(prop));
}
return rollers;
}
private final Properties m_props;
private String m_toAddress;
private String m_ccAddress;
private String m_gameId;
public PropertiesDiceRoller(final Properties props) {
m_props = props;
}
@Override
public String getDisplayName() {
return m_props.getProperty("name");
}
@Override
public EditorPanel getEditor() {
return new DiceServerEditor(this);
}
@Override
public boolean sameType(final IBean other) {
return other instanceof PropertiesDiceRoller && getDisplayName().equals(other.getDisplayName());
}
@Override
public boolean sendsEmail() {
final String property = m_props.getProperty("send.email");
if (property == null) {
return true;
}
return Boolean.valueOf(property);
}
@Override
public String postRequest(final int max, final int numDice, final String subjectMessage, String gameID,
final String gameUUID) throws IOException {
if (gameID.trim().length() == 0) {
gameID = "TripleA";
}
String message = gameID + ":" + subjectMessage;
final int maxLength = Integer.valueOf(m_props.getProperty("message.maxlength"));
if (message.length() > maxLength) {
message = message.substring(0, maxLength - 1);
}
try (CloseableHttpClient httpClient =
HttpClientBuilder.create().setRedirectStrategy(new AdvancedRedirectStrategy()).build()) {
final HttpPost httpPost = new HttpPost(m_props.getProperty("path"));
final List<NameValuePair> params = new ArrayList<>(8);
params.add(new BasicNameValuePair("numdice", "" + numDice));
params.add(new BasicNameValuePair("numsides", "" + max));
params.add(new BasicNameValuePair("modroll", "No"));
params.add(new BasicNameValuePair("numroll", "" + 1));
params.add(new BasicNameValuePair("subject", message));
params.add(new BasicNameValuePair("roller", getToAddress()));
params.add(new BasicNameValuePair("gm", getCcAddress()));
params.add(new BasicNameValuePair("send", "true"));
httpPost.setEntity(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8));
httpPost.addHeader("User-Agent", "triplea/" + ClientContext.engineVersion());
// this is to allow a dice server to allow the user to request the emails for the game
// rather than sending out email for each roll
httpPost.addHeader("X-Triplea-Game-UUID", gameUUID);
final String host = m_props.getProperty("host");
final int port = Integer.parseInt(m_props.getProperty("port", "80"));
final HttpHost hostConfig = new HttpHost(host, port);
HttpProxy.addProxy(httpPost);
try (CloseableHttpResponse response = httpClient.execute(hostConfig, httpPost);) {
final HttpEntity entity = response.getEntity();
return Util.getStringFromInputStream(entity.getContent());
}
}
}
@Override
public String getInfoText() {
return m_props.getProperty("infotext");
}
/**
* @throws IOException
* if there was an error parsing the string.
*/
@Override
public int[] getDice(final String string, final int count) throws IOException, InvocationTargetException {
final String errorStartString = m_props.getProperty("error.start");
final String errorEndString = m_props.getProperty("error.end");
// if the error strings are defined
if (errorStartString != null && errorStartString.length() > 0 && errorEndString != null
&& errorEndString.length() > 0) {
final int startIndex = string.indexOf(errorStartString);
if (startIndex >= 0) {
final int endIndex = string.indexOf(errorEndString, (startIndex + errorStartString.length()));
if (endIndex > 0) {
final String error = string.substring(startIndex + errorStartString.length(), endIndex);
throw new InvocationTargetException(null, error);
}
}
}
String rollStartString;
String rollEndString;
if (count == 1) {
rollStartString = m_props.getProperty("roll.single.start");
rollEndString = m_props.getProperty("roll.single.end");
} else {
rollStartString = m_props.getProperty("roll.multiple.start");
rollEndString = m_props.getProperty("roll.multiple.end");
}
int startIndex = string.indexOf(rollStartString);
if (startIndex == -1) {
throw new IOException("Cound not find start index, text returned is:" + string);
}
startIndex += rollStartString.length();
final int endIndex = string.indexOf(rollEndString, startIndex);
if (endIndex == -1) {
throw new IOException("Cound not find end index");
}
final StringTokenizer tokenizer = new StringTokenizer(string.substring(startIndex, endIndex), " ,", false);
final int[] rVal = new int[count];
for (int i = 0; i < count; i++) {
try {
// -1 since we are 0 based
rVal[i] = Integer.parseInt(tokenizer.nextToken()) - 1;
} catch (final NumberFormatException ex) {
ClientLogger.logQuietly("Number format parsing: " + string, ex);
throw new IOException(ex.getMessage());
}
}
return rVal;
}
@Override
public String getToAddress() {
return m_toAddress;
}
@Override
public void setToAddress(final String toAddress) {
m_toAddress = toAddress;
}
@Override
public String getCcAddress() {
return m_ccAddress;
}
@Override
public void setCcAddress(final String ccAddress) {
m_ccAddress = ccAddress;
}
@Override
public boolean supportsGameId() {
final String gameid = m_props.getProperty("gameid");
return "true".equals(gameid);
}
@Override
public void setGameId(final String gameId) {
m_gameId = gameId;
}
@Override
public String getGameId() {
return m_gameId;
}
@Override
public String getHelpText() {
return getInfoText();
}
private static class AdvancedRedirectStrategy extends LaxRedirectStrategy {
@Override
public HttpUriRequest getRedirect(
final HttpRequest request,
final HttpResponse response,
final HttpContext context) throws ProtocolException {
final URI uri = getLocationURI(request, response, context);
final String method = request.getRequestLine().getMethod();
if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) {
return new HttpHead(uri);
} else if (method.equalsIgnoreCase(HttpGet.METHOD_NAME)) {
return new HttpGet(uri);
} else {
final int status = response.getStatusLine().getStatusCode();
if (status == HttpStatus.SC_TEMPORARY_REDIRECT || status == HttpStatus.SC_MOVED_PERMANENTLY
|| status == HttpStatus.SC_MOVED_TEMPORARILY) {
return RequestBuilder.copy(request).setUri(uri).build();
} else {
return new HttpGet(uri);
}
}
}
}
}