package tools.map.xml.creator;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import games.strategy.ui.SwingAction;
import games.strategy.ui.Util;
import tools.map.xml.creator.TerritoryDefinitionDialog.DEFINITION;
public final class CanalDefinitionsPanel extends ImageScrollPanePanel {
private static final String MSG_TITLE_AUTO_FILL_RESULT = "Auto-Fill Result";
// TODO: consider rework HTML strings for XML file creation and parsing
static final String HTML_CANAL_KEY_POSTFIX = ": ";
static final String HTML_CANAL_KEY_PREFIX = "<br/> - ";
private static final double PI_HALF = Math.PI / 2;
private static final String NEW_CANAL_OPTION = "<new Canal>";
private Set<String> selectedLandTerritories = new TreeSet<>();
private Set<String> selectedWaterTerritories = new TreeSet<>();
private Optional<String> currentCanalName = Optional.empty();
private CanalDefinitionsPanel() {}
public static void layout(final MapXmlCreator mapXmlCreator) {
setMapXmlCreator(mapXmlCreator);
final CanalDefinitionsPanel panel = new CanalDefinitionsPanel();
panel.layout(mapXmlCreator.getStepActionPanel());
mapXmlCreator.setAutoFillAction(SwingAction.of(e -> {
final int prevCanalCount = MapXmlHelper.getCanalDefinitionsMap().size();
if (prevCanalCount > 0) {
if (JOptionPane.YES_OPTION != MapXmlUIHelper.showYesNoOptionDialog("Auto-Fill Warning",
"All current canal definitions will be deleted.\rDo you want to continue with Auto-Fill?",
JOptionPane.WARNING_MESSAGE)) {
return;
}
MapXmlHelper.clearCanalDefinitions();
}
panel.clearSelection();
final Map<String, Set<String>> landWaterTerritoyConnections = MapXmlData.getLandWaterTerritoryConnections();
MapXmlHelper.validateAndAddCanalDefinitions(landWaterTerritoyConnections);
final boolean noNewCanalsBuild = MapXmlHelper.getCanalDefinitionsMap().isEmpty();
if (noNewCanalsBuild) {
JOptionPane.showMessageDialog(null, "No canals have been build!", MSG_TITLE_AUTO_FILL_RESULT,
JOptionPane.PLAIN_MESSAGE);
} else {
JOptionPane.showMessageDialog(null, MapXmlHelper.getHtmlStringFromCanalDefinitions(),
MSG_TITLE_AUTO_FILL_RESULT,
JOptionPane.PLAIN_MESSAGE);
}
if (prevCanalCount > 0 || !noNewCanalsBuild) {
panel.repaint();
}
}));
}
@Override
protected void paintCenterSpecifics(final Graphics g, final String centerName, final FontMetrics fontMetrics,
final Point item, final int textStartX) {
if (selectedLandTerritories.contains(centerName) || selectedWaterTerritories.contains(centerName)) {
final Rectangle2D stringBounds = fontMetrics.getStringBounds(centerName, g);
g.setColor(Color.yellow);
g.fillRect(Math.max(0, textStartX - 2), Math.max(0, item.y - 6), (int) stringBounds.getWidth() + 4,
(int) stringBounds.getHeight());
g.setColor(Color.red);
g.drawString(centerName, Math.max(0, textStartX), item.y + 5);
}
g.setColor(Color.red);
}
@Override
protected void paintPreparation(final Map<String, Point> centers) {}
@Override
protected void paintOwnSpecifics(final Graphics g, final Map<String, Point> centers) {
final Graphics2D g2d = (Graphics2D) g;
final FontMetrics fontMetrics = g.getFontMetrics();
for (final Entry<String, CanalTerritoriesTuple> canalDef : MapXmlHelper.getCanalDefinitionsMap()
.entrySet()) {
final Set<String> terrSet1 = canalDef.getValue().getWaterTerritories();
final Set<String> remainingTerrs = new TreeSet<>(terrSet1);
paintOwnSpecificsToWaterTerritories(centers, g2d, fontMetrics, terrSet1, remainingTerrs, canalDef.getKey());
paintOwnSpecificsToLandTerritories(g, centers, canalDef);
}
}
public void paintOwnSpecificsToLandTerritories(final Graphics g, final Map<String, Point> centers,
final Entry<String, CanalTerritoriesTuple> canalDef) {
g.setColor(Color.GREEN);
final Set<String> terrLandSet = canalDef.getValue().getLandTerritories();
final Set<String> terrLandRemainingSet = new TreeSet<>(terrLandSet);
for (final String terrLand : terrLandSet) {
final Point centerLandTerr = centers.get(terrLand);
terrLandRemainingSet.remove(terrLand);
for (final String terrLandRemaining : terrLandRemainingSet) {
final Point centerLandRemainingTerr = centers.get(terrLandRemaining);
g.drawLine(centerLandTerr.x, centerLandTerr.y, centerLandRemainingTerr.x, centerLandRemainingTerr.y);
}
}
}
public void paintOwnSpecificsToWaterTerritories(final Map<String, Point> centers, final Graphics2D g2d,
final FontMetrics fontMetrics, final Set<String> terrSet1, final Set<String> remainingTerrs,
final String canalName) {
g2d.setColor(Color.BLUE);
for (final String terr1 : terrSet1) {
final Point center1 = centers.get(terr1);
remainingTerrs.remove(terr1);
for (final String terr2 : remainingTerrs) {
final Point center2 = centers.get(terr2);
g2d.drawLine(center1.x, center1.y, center2.x, center2.y);
final Rectangle2D stringBounds = fontMetrics.getStringBounds(canalName, g2d);
final int dX = center2.x - center1.x;
final int dY = center2.y - center1.y;
final Point lineCenter = new Point(center1.x + dX / 2, center1.y + dY / 2);
final double centerDistance = center2.distance(center1);
if (centerDistance > stringBounds.getWidth()) {
drawRotate(g2d, lineCenter.x, lineCenter.y, Math.atan2(dY, dX), canalName,
(int) (stringBounds.getWidth()) / -2);
} else {
g2d.drawString(canalName, lineCenter.x, lineCenter.y);
}
}
}
}
public static void drawRotate(final Graphics2D g2d, final double x, final double y, double radianAngle,
final String text, final int textX) {
g2d.translate((float) x, (float) y);
if (radianAngle > PI_HALF) {
radianAngle -= Math.PI;
} else if (radianAngle < -PI_HALF) {
radianAngle += Math.PI;
}
g2d.rotate(radianAngle);
g2d.drawString(text, textX, -2);
g2d.rotate(-radianAngle);
g2d.translate(-(float) x, -(float) y);
}
@Override
protected void mouseClickedOnImage(final Map<String, Point> centers, final MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {
mouseRightClickedOnImage();
return;
}
final Optional<String> newTerrNameOptional = Util.findTerritoryName(e.getPoint(), polygons);
if (!newTerrNameOptional.isPresent()) {
return;
}
final String newTerrName = newTerrNameOptional.get();
Boolean newTerrIsWater = MapXmlHelper.getTerritoryDefintionsMap().get(newTerrName).get(DEFINITION.IS_WATER);
if (newTerrIsWater == null) {
newTerrIsWater = false;
}
final Set<String> newTerrNeighborsDiffType = getNeighborsByType(newTerrName, !newTerrIsWater);
final List<String> terrCanals = getCanalsLinkedToTerritory(newTerrName, newTerrIsWater);
if (!evaluateSelectedTerritoryToCurrentCanal(newTerrName, newTerrIsWater, newTerrNeighborsDiffType, terrCanals)) {
return;
}
if (!handleSelectedTerritoryToCurrentCanal(newTerrName, newTerrIsWater, newTerrNeighborsDiffType)) {
return;
}
repaint();
}
/**
* @param newTerrName - territory name.
* @param newTerrIsWater - IS_WATER property of newTerrName territory
* @param newTerrNeighborsDiffType - list of neighbor territories with/without (defined by newTerrIsWater) property
* IS_WATER
* @return whether handling was successful or not
*/
private boolean handleSelectedTerritoryToCurrentCanal(final String newTerrName, final boolean newTerrIsWater,
final Set<String> newTerrNeighborsDiffType) {
if (currentCanalName.isPresent()) {
setSelectedTerritoriesFromTerritory();
final Set<String> selectedTerrsSameType;
if (newTerrIsWater) {
selectedTerrsSameType = selectedWaterTerritories;
} else {
selectedTerrsSameType = selectedLandTerritories;
}
if (selectedTerrsSameType.size() > 0) {
final Set<String> commonNeighborsDiffType =
getCommonNeighborsOfType(selectedTerrsSameType, !newTerrIsWater);
commonNeighborsDiffType.retainAll(newTerrNeighborsDiffType);
if (commonNeighborsDiffType.size() < 2) {
JOptionPane.showMessageDialog(null,
getMessageTextOnToFewSuitableNeighbors(newTerrIsWater, selectedTerrsSameType.size()), "Input Error",
JOptionPane.ERROR_MESSAGE);
return false;
}
}
if (selectedTerrsSameType.contains(newTerrName)) {
selectedTerrsSameType.remove(newTerrName);
} else {
selectedTerrsSameType.add(newTerrName);
}
}
return true;
}
private void setSelectedTerritoriesFromTerritory() {
CanalTerritoriesTuple canalTerrs = MapXmlHelper.getCanalDefinitionsMap().get(currentCanalName.get());
if (canalTerrs == null) {
canalTerrs = new CanalTerritoriesTuple();
MapXmlHelper.putCanalDefinitions(currentCanalName.get(), canalTerrs);
}
selectedWaterTerritories = canalTerrs.getWaterTerritories();
selectedLandTerritories = canalTerrs.getLandTerritories();
}
/**
* The method evaluated the current selected territory with the current canal name and provides some message feedback
* in case of problems (either by an error message or by the possibility to refine the selection purpose).
*
* @param imagePanel - JPanel
* @param newTerrName - territory name
* @param newTerrIsWater - IS_WATER property of newTerrName territory
* @param newTerrNeighborsDiffType - list of neighbor territories with/without (defined by newTerrIsWater) property
* IS_WATER
* @param terrCanals - list of canals the newTerrName territory is linked to
* @return evaluation result
*/
private boolean evaluateSelectedTerritoryToCurrentCanal(final String newTerrName, final boolean newTerrIsWater,
final Set<String> newTerrNeighborsDiffType, final List<String> terrCanals) {
if (currentCanalName.isPresent()) {
if (newTerrNeighborsDiffType.size() < 2) {
JOptionPane.showMessageDialog(null, "The selected " + (newTerrIsWater ? "water" : "land")
+ " territory is connected to less than 2 " + (!newTerrIsWater ? "water" : "land")
+ " territories!", "Input Error", JOptionPane.ERROR_MESSAGE);
return false;
}
if (!terrCanals.isEmpty()) {
terrCanals.add(NEW_CANAL_OPTION);
currentCanalName = Optional.ofNullable((String) JOptionPane.showInputDialog(null,
"Which canal should be selected for territory '" + newTerrName + "?", "Choose Canal",
JOptionPane.QUESTION_MESSAGE,
null, terrCanals.toArray(new String[terrCanals.size()]), // Array of choices
terrCanals.get(0))); // Initial choice
}
if (terrCanals.isEmpty() || NEW_CANAL_OPTION.equals(currentCanalName.orElse(""))) {
final String suggestedCanalName = getSuggestedCanalName();
currentCanalName = Optional.ofNullable(JOptionPane.showInputDialog(null,
"Which canal should be selected for territory '" + newTerrName + "?", suggestedCanalName));
while (MapXmlHelper.getCanalDefinitionsMap().keySet().contains(currentCanalName.get())) {
JOptionPane.showMessageDialog(null, "The canal name " + currentCanalName + " is already in use!",
"Input Error", JOptionPane.ERROR_MESSAGE);
currentCanalName = Optional.ofNullable(JOptionPane.showInputDialog(null,
"Which canal should be selected for territory '" + newTerrName + "?", currentCanalName));
}
} else if (currentCanalName.isPresent()) {
final CanalTerritoriesTuple canalTerrs =
MapXmlHelper.getCanalDefinitionsMap().get(currentCanalName.get());
selectedWaterTerritories = canalTerrs.getWaterTerritories();
selectedLandTerritories = canalTerrs.getLandTerritories();
repaint();
return false;
}
if (!currentCanalName.isPresent()) {
return false;
}
}
return true;
}
private void mouseRightClickedOnImage() {
if (currentCanalName.isPresent()
&& (selectedLandTerritories.size() < 2 || selectedWaterTerritories.size() < 2)) {
if (JOptionPane.YES_OPTION != MapXmlUIHelper.showYesNoOptionDialog("Canal incomplete",
"Canal '" + currentCanalName + "' is incomplete. A canal needs at least 2 land and 2 water territories.\r"
+ "Do you want to continue to deselect the canal?",
JOptionPane.WARNING_MESSAGE)) {
return;
}
MapXmlHelper.getCanalDefinitionsMap().remove(currentCanalName.get());
}
currentCanalName = Optional.empty();
if (!selectedLandTerritories.isEmpty() || !selectedWaterTerritories.isEmpty()) {
clearSelection();
SwingUtilities.invokeLater(() -> getImagePanel().repaint());
}
}
/**
* @param newTerrName - base territory.
* @param newTerrIsWater - IS_WATER property of base territory
* @param terrCanals - list of canals the base territory is linked to
*/
private List<String> getCanalsLinkedToTerritory(final String newTerrName, final boolean newTerrIsWater) {
final List<String> terrCanals = new ArrayList<>();
if (newTerrIsWater) {
for (final Entry<String, CanalTerritoriesTuple> canalDef : MapXmlHelper.getCanalDefinitionsMap()
.entrySet()) {
if (canalDef.getValue().getWaterTerritories().contains(newTerrName)) {
terrCanals.add(canalDef.getKey());
}
}
} else {
for (final Entry<String, CanalTerritoriesTuple> canalDef : MapXmlHelper.getCanalDefinitionsMap()
.entrySet()) {
if (canalDef.getValue().getLandTerritories().contains(newTerrName)) {
terrCanals.add(canalDef.getKey());
}
}
}
return terrCanals;
}
private String getMessageTextOnToFewSuitableNeighbors(final boolean newTerrIsWater,
final int selectedTerrsSameTypeCount) {
return "The selected " + (newTerrIsWater ? "water" : "land")
+ " territory is connected to less than 2 common " + (!newTerrIsWater ? "water" : "land")
+ " territories with the other " + (newTerrIsWater ? "water" : "land") + " territor"
+ (selectedTerrsSameTypeCount == 1 ? "y" : "ies")
+ "!\rRight click to deselect current canal '" + currentCanalName + "'.";
}
/**
* @return suggested canal name.
*/
private String getSuggestedCanalName() {
String suggestedCanalName;
int counter = MapXmlHelper.getCanalDefinitionsMap().size();
do {
suggestedCanalName = "Canal" + counter;
++counter;
} while (MapXmlHelper.getCanalDefinitionsMap().keySet().contains(suggestedCanalName));
return suggestedCanalName;
}
protected void clearSelection() {
currentCanalName = Optional.empty();
selectedLandTerritories = new TreeSet<>();
selectedWaterTerritories = new TreeSet<>();
}
/**
*
* @param newTerrName - territory name.
* @param waterNeighbors - whether IS_WATER property is supposed to be present or not
* @return list of neighbor territories with/without (defined by waterNeighbors) property IS_WATER
*/
protected Set<String> getNeighborsByType(final String newTerrName, final boolean waterNeighbors) {
final Set<String> neighborsByType = Sets.newLinkedHashSet();
final Set<String> neighbors = MapXmlHelper.getTerritoryConnectionsMap().get(newTerrName);
for (final Entry<String, Set<String>> terrConnEntry : MapXmlHelper.getTerritoryConnectionsMap().entrySet()) {
if (MapXmlHelper.getTerritoryDefintionsMap().get(terrConnEntry.getKey())
.get(DEFINITION.IS_WATER) == waterNeighbors
&& terrConnEntry.getValue().contains(newTerrName)) {
neighborsByType.add(terrConnEntry.getKey());
}
}
if (neighbors != null) {
for (final String neighbor : neighbors) {
if (MapXmlHelper.getTerritoryDefintionsMap().get(neighbor).get(DEFINITION.IS_WATER) == waterNeighbors) {
neighborsByType.add(neighbor);
}
}
}
return neighborsByType;
}
private Set<String> getCommonNeighborsOfType(final Set<String> terrList, final boolean typeIsWater) {
final Set<String> commonNeighborsOfType = new TreeSet<>();
final Map<String, Collection<String>> neighborsMap = getNeighborsMapWaterDefinitionBeing(terrList, typeIsWater);
commonNeighborsOfType.addAll(neighborsMap.values().iterator().next());
if (commonNeighborsOfType.size() >= 2) {
for (final Collection<String> waterNeighors : neighborsMap.values()) {
commonNeighborsOfType.retainAll(waterNeighors);
if (commonNeighborsOfType.size() < 2) {
break;
}
}
}
return commonNeighborsOfType;
}
/**
* @param terrList - list of territories for which neighbors should be evaluated.
* @param typeIsWater - filter criteria of neighbors by their property IS_WATER
* @return map of terrList list territory entry -> neighbor territories with/without (defined by typeIsWater) IS_WATER
* property
*/
private Map<String, Collection<String>> getNeighborsMapWaterDefinitionBeing(final Set<String> terrList,
final boolean typeIsWater) {
final Map<String, Collection<String>> neighborsMap = Maps.newHashMap();
for (final String terr : terrList) {
neighborsMap.put(terr, new ArrayList<>());
}
for (final Entry<String, Set<String>> terrConnEntry : MapXmlHelper.getTerritoryConnectionsMap().entrySet()) {
final String terr1 = terrConnEntry.getKey();
if (terrList.contains(terr1)) {
for (final String terr2 : terrConnEntry.getValue()) {
if (MapXmlHelper.getTerritoryDefintionsMap().get(terr2).get(DEFINITION.IS_WATER) == typeIsWater) {
neighborsMap.get(terr1).add(terr2);
}
}
} else {
if (MapXmlHelper.getTerritoryDefintionsMap().get(terr1).get(DEFINITION.IS_WATER) == typeIsWater) {
final SortedSet<String> selectedTerritoriesCopy = new TreeSet<>(terrList);
selectedTerritoriesCopy.retainAll(terrConnEntry.getValue());
for (final String terr2 : selectedTerritoriesCopy) {
neighborsMap.get(terr2).add(terr1);
}
}
}
}
return neighborsMap;
}
}