content: package aima.gui.fx.views;
  
  import aima.core.search.csp.Assignment;
  import aima.core.search.csp.CSP;
  import aima.core.search.csp.Constraint;
  import aima.core.search.csp.Variable;
  import aima.core.util.math.geom.shapes.Point2D;
  import javafx.geometry.VPos;
  import javafx.scene.layout.Pane;
  import javafx.scene.layout.StackPane;
  import javafx.scene.paint.Color;
  import javafx.scene.shape.Circle;
  import javafx.scene.shape.Line;
  import javafx.scene.text.Text;
  
  import java.util.Hashtable;
  import java.util.List;
  
  /**
   * Controller class which provides functionality to visualize a binary CSP state in a pane.
   *
   * @author Ruediger Lunde
   */
  public class CspViewCtrl<VAR extends Variable, VAL> {
      protected Pane pane;
      protected CSP<VAR, VAL> csp;
      protected Assignment<VAR, VAL> assignment;
  
      /**
       * Maintains logical 2D-coordinates for the variables of the CSP.
       */
      protected Hashtable<VAR, int[]> positionMapping = new Hashtable<>();
      /**
       * Maps domain values to colors.
       */
      protected Hashtable<VAL, Color> colorMapping = new Hashtable<>();
  
      public CspViewCtrl(StackPane viewRoot) {
          pane = new Pane();
          viewRoot.getChildren().add(pane);
          viewRoot.setStyle("-fx-background-color: white");
          pane.setMinWidth(0.0);
          pane.widthProperty().addListener((obs, o, n) -> update());
          pane.heightProperty().addListener((obs, o, n) -> update());
      }
  
      public void clearMappings() {
          positionMapping.clear();
          colorMapping.clear();
      }
  
      /**
       * Defines a logical 2D-position for a variable. If no position is given for
       * a certain variable, the viewer selects a position on a grid.
       */
      public void setPositionMapping(VAR var, int x, int y) {
          positionMapping.put(var, new int[]{x, y});
      }
  
      /**
       * Defines a color for a domain value. It is used to visualize the current
       * assignment. If no color mapping is found, the node coloring feature is
       * disabled for the corresponding variable.
       *
       * @param value
       * @param color
       */
      public void setColorMapping(VAL value, Color color) {
          colorMapping.put(value, color);
      }
  
  
      public void initialize(CSP<VAR, VAL> csp) {
          this.csp = csp;
          this.assignment = new Assignment<>();
          update();
      }
  
      public void update(CSP<VAR, VAL> csp, Assignment<VAR, VAL> assignment) {
          if (csp != null)
              this.csp = csp;
          if (assignment != null)
              this.assignment = assignment;
          update();
      }
  
      protected void update() {
          adjustTransform();
          pane.getChildren().clear();
          if (csp != null) {
              csp.getConstraints().forEach(this::visualize);
              csp.getVariables().forEach(this::visualize);
          }
      }
  
      protected void visualize(VAR var) {
          Point2D pos = getPosition(var);
          String label = var.getName();
          VAL value = null;
          Color fillColor = null;
          if (assignment != null)
              value = assignment.getValue(var);
          if (value != null) {
              label += " = " + value;
              fillColor = colorMapping.get(value);
          }
          Circle circle = new Circle(pos.getX(), pos.getY(), 20);
          circle.setStroke(Color.BLACK);
          circle.setFill(fillColor != null ? fillColor : Color.WHITE);
          Text t1 = new Text(pos.getX() + 25, pos.getY(), label);
          t1.setTextOrigin(VPos.CENTER);
          Text t2 = new Text(pos.getX(), pos.getY() + 40, csp.getDomain(var).toString());
          pane.getChildren().addAll(circle, t1, t2);
      }
  
      protected void visualize(Constraint<VAR, VAL> constraint) {
          List<VAR> scope = constraint.getScope();
          if (scope.size() == 2) { // we show only binary constraints...
              Point2D pos0 = getPosition(scope.get(0));
              Point2D pos1 = getPosition(scope.get(1));
              Line line = new Line(pos0.getX(), pos0.getY(), pos1.getX(), pos1.getY());
              pane.getChildren().add(line);
              //g2.drawLine(pos0[0] + 20, pos0[1] + 20, pos1[0] + 20, pos1[1] + 20);
          }
      }
  
      /**
       * Computes transforms (translations and scaling) and applies them to the environment state view. Those transforms
       * map logical positions to screen positions in the viewer pane. The purpose is to show the graphical state
       * representation as large as possible.
       *
       * @return The scale value.
       */
      private double adjustTransform() {
          double xMin = Double.POSITIVE_INFINITY;
          double xMax = Double.NEGATIVE_INFINITY;
          double yMin = Double.POSITIVE_INFINITY;
          double yMax = Double.NEGATIVE_INFINITY;
          if (csp != null)
              for (VAR var : csp.getVariables()) {
                  Point2D point = getPosition(var);
                  xMin = Math.min(xMin, point.getX());
                  xMax = Math.max(xMax, point.getX());
                  yMin = Math.min(yMin, point.getY());
                  yMax = Math.max(yMax, point.getY());
              }
          double scale = Math.min(pane.getWidth() / (xMax - xMin + 300),
                  pane.getHeight() / (yMax - yMin + 150));
  
          pane.setTranslateX((scale * (pane.getWidth() - xMin - xMax) / 2.0));
          pane.setTranslateY((scale * (pane.getHeight() - yMin - yMax) / 2.0));
          pane.setScaleX(scale);
          pane.setScaleY(scale);
          return scale;
      }
  
      /**
       * Provides a 2D-position for each variable. If no mapping is given, a simple grid position is computed.
       */
      protected Point2D getPosition(VAR var) {
          int[] pos = positionMapping.get(var);
          if (pos != null)
              return new Point2D(pos[0], pos[1]);
          else {
              int vIndex = csp.indexOf(var);
              int rows = Math.max((int) (pane.getHeight() / 100), 1);
              int x = (vIndex / rows) * 160 + 40;
              int y = (vIndex % rows) * 100 + 40;
              return new Point2D(x, y);
          }
      }
  }
  