/*
 * Licensed Materials - Property of Perforce Software, Inc. 
 * © Copyright Perforce Software, Inc. 2014, 2021 
 * © Copyright IBM Corp. 2009, 2014
 * © Copyright ILOG 1996, 2009
 * All Rights Reserved.
 *
 * Note to U.S. Government Users Restricted Rights:
 * The Software and Documentation were developed at private expense and
 * are "Commercial Items" as that term is defined at 48 CFR 2.101,
 * consisting of "Commercial Computer Software" and
 * "Commercial Computer Software Documentation", as such terms are
 * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
 * as applicable.
 */

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.SystemColor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

import ilog.views.IlvApplyObject;
import ilog.views.IlvDirection;
import ilog.views.IlvGraphic;
import ilog.views.IlvGraphicEnumeration;
import ilog.views.IlvHandlesSelection;
import ilog.views.IlvManager;
import ilog.views.IlvManagerView;
import ilog.views.IlvPoint;
import ilog.views.IlvRect;
import ilog.views.IlvTransformer;
import ilog.views.event.ManagerContentChangedEvent;
import ilog.views.event.ManagerContentChangedListener;
import ilog.views.event.ManagerSelectionChangedEvent;
import ilog.views.event.ManagerSelectionListener;
import ilog.views.event.TransformerChangedEvent;
import ilog.views.event.TransformerListener;
import ilog.views.graphic.IlvEllipse;
import ilog.views.graphic.IlvIcon;
import ilog.views.graphic.IlvLine;
import ilog.views.graphic.IlvRectangle;
import ilog.views.graphic.IlvZoomableLabel;
import ilog.views.graphlayout.labellayout.IlvDefaultLabelingModel;
import ilog.views.graphlayout.labellayout.IlvLabelLayout;
import ilog.views.graphlayout.labellayout.IlvLabelLayoutReport;
import ilog.views.graphlayout.labellayout.LabelLayoutEvent;
import ilog.views.graphlayout.labellayout.LabelLayoutEventListener;
import ilog.views.graphlayout.labellayout.annealing.IlvAnnealingLabelLayout;
import ilog.views.graphlayout.labellayout.annealing.IlvAnnealingPointLabelDescriptor;
import ilog.views.graphlayout.labellayout.random.IlvRandomLabelLayout;
import ilog.views.interactor.IlvSelectInteractor;
import ilog.views.interactor.IlvZoomViewInteractor;
import ilog.views.io.IlvReadFileException;
import ilog.views.swing.IlvJManagerViewControlBar;
import ilog.views.swing.IlvJScrollManagerView;
import ilog.views.util.IlvProductUtil;
import ilog.views.util.swing.IlvSwingUtil;

/**
 * The main class PointLabelLayoutApp.
 */
public class PointLabelLayoutApp extends JRootPane
    implements ManagerContentChangedListener, ManagerSelectionListener, TransformerListener {
  
  {
    // This sample uses JViews Diagrammer features. When deploying an
    // application that includes this code, you need to be in possession
    // of a JViews Diagrammer Deployment license.
    IlvProductUtil.DeploymentLicenseRequired(IlvProductUtil.JViews_Diagrammer_Deployment);
  }

  /** The manager */
  IlvManager manager = new IlvManager();

  /** The labeling model for the manager */
  IlvDefaultLabelingModel labelingModel;

  /** The view of the manager */
  IlvManagerView mgrview = new PointLabelingManagerView(manager);

  /** An instance of the Simulates Annealing Label Layout algorithm */
  IlvAnnealingLabelLayout layout = new IlvAnnealingLabelLayout();

  /** An instance of the Random Label Layout algorithm */
  IlvRandomLabelLayout randomLayout = new IlvRandomLabelLayout();

  /** A label layout event listener */
  PointLabelingListener layoutListener = new PointLabelingListener(this);

  /** A text field to display messages */
  JTextField msgLine = new JTextField();

  /** The preferred direction of all labels */
  int preferredDirection = IlvDirection.Right;

  /** Offset distances */
  static final double minDistToOtherObstacles = 5;
  double prefDistFromOwnObstacle = 1;

  /** Text fields for the offset parameters */
  JTextField minOffset = new JTextField("" + minDistToOtherObstacles);
  JTextField prefOffset = new JTextField("" + prefDistFromOwnObstacle);

  /**
   * Whether we want to see the label bounding box or not.
   */
  boolean noLabelBackground = true;

  /**
   * In this demo, the labels are assigned to circles (IlvEllipse) or rectangles
   * (IlvRectangle) representing cities. The labels should be placed close to
   * the cities. Which label belongs to which city is in this sample randomly
   * decided, and stored in the assignment table, such that we can show the
   * assignment by the auxiliry links when necessary.
   */
  Hashtable<Object, IlvGraphic> assignmentTable;
  Object[] obstacles;
  Object[] labels;
  int[] labelsPerObstacle;

  /** The assignment lines */
  IlvLine[] assignmentLines;
  boolean labelAssignmentVisible = false;

  /**
   * A counter to block the event handling of the auxiliary lines. Otherise, the
   * updating of the auxiliary lines on movements would cause cycles of events.
   */
  int blockEventsCounter = 0;

  /**
   * Initializes the application.
   */
  public void init() {
    showMessage("layout sample initialization...");

    JPanel panel;
  
    // register this application as listener to the manager
    manager.addManagerContentChangedListener(this);

    // register this application as selection listener to the manager
    manager.addManagerSelectionListener(this);

    // create the scroll manager view
    IlvJScrollManagerView scrollManView = new IlvJScrollManagerView(mgrview);

    // Some settings on the manager view and on the scroll manager view
    mgrview.setAntialiasing(true);
    mgrview.setKeepingAspectRatio(true);
    mgrview.setBackground(Color.white);
    mgrview.setForeground(SystemColor.windowText);
    Color xc = SystemColor.windowText;
    xc = new Color(255 - xc.getRed(), 255 - xc.getGreen(), 255 - xc.getBlue());
    mgrview.setDefaultXORColor(xc);
    mgrview.setDefaultGhostColor(SystemColor.windowText);
    mgrview.setSize(new Dimension(488, 357));
    mgrview.addTransformerListener(this);

    // Settings parameters for selection handles
    IlvHandlesSelection.defaultHandleColor = Color.black;
    IlvHandlesSelection.defaultHandleBackgroundColor = Color.white;
    IlvHandlesSelection.defaultHandleShape = IlvHandlesSelection.SQUARE_SHAPE;

    // create a new labeling model
    // We do this after adding the app as transformer listener, because
    // the default labeling model will register itself as transformer listener
    // as well when using nonzoomable labels. The aux lines mechanism of this
    // app requires that the app receives the event at last.
    labelingModel = new IlvDefaultLabelingModel(manager);

    // attach the manager to the layout instances
    layout.attach(labelingModel);
    randomLayout.attach(labelingModel);
    layout.setObstacleOffset(minDistToOtherObstacles);
    layout.setAutoUpdate(false);

    // install the layout iteration listener on the layout instance
    layout.addLabelLayoutEventListener(layoutListener);

    // set the layout manager
    getContentPane().setLayout(new BorderLayout(0, 0));

    // fit so far all together
    getContentPane().add("Center", scrollManView);
    getContentPane().add("North", panel = new JPanel());
    panel.setLayout(new FlowLayout(FlowLayout.LEFT));

    // create the standard control bar
    IlvJManagerViewControlBar controlBar = new IlvJManagerViewControlBar();
    controlBar.setView(mgrview);
    panel.add(controlBar);

    // modify the interactors such that the demo looks better
    ((IlvSelectInteractor) controlBar.getSelectInteractor()).setOpaqueMove(true);
    ((IlvZoomViewInteractor) controlBar.getZoomViewInteractor()).setPermanent(true);

    // set the initial interactor
    mgrview.setInteractor(controlBar.getSelectInteractor());

    // create the graph selector
    panel.add(new JLabel("Select sample"));
    JComboBox<String> chooser;
    panel.add(chooser = new JComboBox<String>());
    chooser.setToolTipText("Select a sample");
    chooser.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent event) {
        if (event.getStateChange() == ItemEvent.DESELECTED)
          return;
        try {
          // disable autoupdate until we did at least one layout
          layout.setAutoUpdate(false);
          // just to be sure: store where the aux lines are visible and
          // hide the aux lines
          boolean f = labelAssignmentVisible;
          hideLabelAssignment();
          // refill the manager from file
          manager.deleteAll(false);
          readManager((String) event.getItem());
          // restore whether the aux lines were visible
          labelAssignmentVisible = f;
          // postprocessing after reading a file
          afterRead();
        } catch (Exception e) {
          // e.printStackTrace();
          showMessage(e.getMessage());
        }
      }
    });
    chooser.addItem("small1");
    chooser.addItem("small2");
    chooser.addItem("medium");
    chooser.addItem("large");
    chooser.addItem("huge");

    // create the panel on bottom
    JPanel bottomPanel = new JPanel();
    bottomPanel.setLayout(new BorderLayout());
    getContentPane().add("South", bottomPanel);

    // create the layout buttons
    JButton b;
    JPanel layoutPanel = new JPanel();
    bottomPanel.add("North", layoutPanel);
    layoutPanel.add(b = new JButton("Random"));
    b.setToolTipText("Randomize the label positions");
    b.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent evt) {
        preprocess();
        layout(randomLayout, manager);
      }
    });

    layoutPanel.add(b = new JButton("Layout"));
    b.setToolTipText("Perform a label layout");
    b.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent evt) {
        try {
          float value;
          // validate the min offset
          value = Float.valueOf(minOffset.getText().trim()).floatValue();
          setMinimumOffset(value);
          // validate the preferred offset
          value = Float.valueOf(prefOffset.getText().trim()).floatValue();
          setPreferredOffset(value);
        } catch (Exception ex) {
          // ex.printStackTrace();
          showMessage("Illegal offset: " + ex.getMessage());
          return;
        }
        // now the layout
        preprocess();
        layout(layout, manager);
        // from now on, the labels follow the obstacles
        layout.setAutoUpdate(true);
      }
    });

    // create the layout parameters panel
    JPanel layoutParameters = new JPanel();
    layoutParameters.setLayout(new BorderLayout());
    bottomPanel.add("Center", layoutParameters);

    JPanel layoutParameters1 = new JPanel();
    layoutParameters1.setLayout(new GridLayout(2, 1));
    JPanel layoutParameters2 = new JPanel();
    layoutParameters2.setLayout(new GridLayout(2, 2));

    // pref direction parameter
    JPanel auxPanel = new JPanel();
    auxPanel.setLayout(new BorderLayout(0, 0));
    layoutParameters1.add(auxPanel);
    JComboBox<String> prefDirectionChooser = new JComboBox<String>();
    prefDirectionChooser.setToolTipText("Select the preferred direction for all labels");
    auxPanel.add("West", new JLabel("Preferred Direction:"));
    auxPanel.add("Center", prefDirectionChooser);
    prefDirectionChooser.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent event) {
        if (event.getStateChange() == ItemEvent.DESELECTED)
          return;
        int oldDir = preferredDirection;
        if ((String) event.getItem() == "top") {
          preferredDirection = IlvDirection.Top;
        }
        if ((String) event.getItem() == "bottom") {
          preferredDirection = IlvDirection.Bottom;
        }
        if ((String) event.getItem() == "left") {
          preferredDirection = IlvDirection.Left;
        }
        if ((String) event.getItem() == "right") {
          preferredDirection = IlvDirection.Right;
        }
        if (oldDir != preferredDirection)
          setLayoutParameters();
      }
    });
    prefDirectionChooser.addItem("top");
    prefDirectionChooser.addItem("bottom");
    prefDirectionChooser.addItem("left");
    prefDirectionChooser.addItem("right");
    prefDirectionChooser.setSelectedIndex(3);

    // quadtree
    auxPanel = new JPanel();
    auxPanel.setLayout(new BorderLayout(0, 0));
    layoutParameters1.add(auxPanel);
    JCheckBox useQuadtree = new JCheckBox("Quadtree", true);
    useQuadtree.setToolTipText("Enable or disable the quadtree as speedup mechanism");
    auxPanel.add("West", useQuadtree);
    useQuadtree.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED)
          layout.setUseQuadtree(true);
        else if (e.getStateChange() == ItemEvent.DESELECTED)
          layout.setUseQuadtree(false);
      }
    });

    // label box
    JCheckBox showLabelBox = new JCheckBox("Label Box", false);
    showLabelBox.setToolTipText("Show or hide the bounding box of the labels");
    auxPanel.add("Center", showLabelBox);
    showLabelBox.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED)
          noLabelBackground = false;
        else if (e.getStateChange() == ItemEvent.DESELECTED)
          noLabelBackground = true;
        setNoLabelBackground(noLabelBackground);
        // corner may have changed
        if (labelAssignmentVisible)
          showLabelAssignment();
        manager.reDraw();
      }
    });

    // auxiliary lines
    JCheckBox showAuxLines = new JCheckBox("Auxiliary Lines", false);
    showAuxLines.setToolTipText("Show or hide the auxiliary lines of the labels");
    auxPanel.add("East", showAuxLines);
    showAuxLines.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED)
          showLabelAssignment();
        else if (e.getStateChange() == ItemEvent.DESELECTED)
          hideLabelAssignment();
      }
    });

    // minimum offset to other obstacles
    layoutParameters2.add(new JLabel("Min. Offset:"));
    layoutParameters2.add(minOffset);
    minOffset.setToolTipText("Set the minimum offset to unrelated obstacles");
    minOffset.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        try {
          JTextField textfield = (JTextField) e.getSource();
          float value = Float.valueOf(textfield.getText().trim()).floatValue();
          setMinimumOffset(value);
        } catch (Exception ex) {
          showMessage("Illegal minimum offset: " + ex.getMessage());
        }
      }
    });

    // preferred offset to own obstacle
    layoutParameters2.add(new JLabel("Pref. Offset:"));
    layoutParameters2.add(prefOffset);
    prefOffset.setToolTipText("Set the preferred offset between label and related obstacle");
    prefOffset.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        try {
          JTextField textfield = (JTextField) e.getSource();
          float value = Float.valueOf(textfield.getText().trim()).floatValue();
          setPreferredOffset(value);
        } catch (Exception ex) {
          showMessage("Illegal preferred offset: " + ex.getMessage());
        }
      }
    });

    layoutParameters.add("West", layoutParameters1);
    layoutParameters.add("East", layoutParameters2);

    // add the message line to the bottom panel
    bottomPanel.add("South", msgLine);
    msgLine.setEditable(false);

    // load a sample manager
    try {
      manager.deleteAll(false);
      readManager("small1");
      afterRead();
    } catch (Exception e) {
      // e.printStackTrace();
      showMessage(e.getMessage());
    }
  }

  /**
   * Allows you to run the demo as a standalone application.
   */
  public static void main(String[] arg) {
    // Sun recommends that to put the entire GUI initialization into the
    // AWT thread
    SwingUtilities.invokeLater(new Runnable() {
      Override
      public void run() {
        PointLabelLayoutApp app = new PointLabelLayoutApp();
        app.init();

        JFrame frame = new JFrame("Label Layout Demo");
        // EXIT_ON_CLOSE is not defined in jdk1.2, hence this way:
        frame.setDefaultCloseOperation(3 /* EXIT_ON_CLOSE, >= jdk1.2.2 */);
        frame.setSize(600, 600);
        frame.getContentPane().add(app);
        frame.setVisible(true);
      }
    });
  }

  /**
   * Read the grapher.
   */
  private void readManager(String fileNameBase) throws IOException, IlvReadFileException {
    showMessage("Reading " + fileNameBase + ".ivl ...");
    manager.read(IlvSwingUtil.getRelativeURL(this, "data/" + fileNameBase + ".ivl"));
    showMessage("Reading " + fileNameBase + ".ivl done.");
  }

  /**
   * Performs the layout of the graph.
   */
  private void layout(IlvLabelLayout layout, IlvManager manager) {
    // the layout report instance; this is an object in which
    // the layout algorithm stores information about its behavior
    IlvLabelLayoutReport layoutReport = null;

    showMessage("layout started...");

    // hide the label assignment lines because they should not be obstacles
    boolean labelAssignmentWasVisible = labelAssignmentVisible;
    hideLabelAssignment();

    // initialize the iteration listener
    layoutListener.initialize();

    try {
      // perform the layout and get the layout report
      layoutReport = layout.performLayout(false, false);

      // print the code from the layout report
      showMessage(layoutReport.codeToString(layoutReport.getCode()) + "." + " Time: "
          + (0.001f * layoutReport.getLayoutTime()) + " sec." + " Overlap: " + getOverlapAsString());
    } catch (Exception e) {
      // e.printStackTrace();
      showMessage(e.getMessage());
    } finally {
      if (layoutReport != null && layoutReport.getCode() != IlvLabelLayoutReport.NOT_NEEDED) {
        if (labelAssignmentWasVisible)
          showLabelAssignment();
        mgrview.repaint();
      }
    }
  }

  /**
   * Displays a message.
   */
  void showMessage(String message) {
    // the message is displayed in the message line
    msgLine.setText(message);
    msgLine.paintImmediately(0, 0, msgLine.getWidth(), msgLine.getHeight());
  }

  /**
   * Some special handling after read. Here we determine dynamically which label
   * belongs to which rectangle or ellipse obstacle (representing for instance a
   * city). Polyline obstacles have no labels in this sample.
   */
  void afterRead() {
    int numObstacles = labelingModel.getObstaclesCount();
    int numLabels = labelingModel.getLabelsCount();
    obstacles = new Object[numObstacles];
    labels = new Object[numLabels];
    labelsPerObstacle = new int[numObstacles];
    assignmentLines = new IlvLine[numLabels];

    // initialize the table of obstacles
    Enumeration<?> e = labelingModel.getObstacles();
    int numUsableObstacles = 0;
    int i = 0;
    while (e.hasMoreElements()) {
      obstacles[i] = e.nextElement();
      if (obstacles[i] instanceof IlvIcon || obstacles[i] instanceof IlvEllipse || obstacles[i] instanceof IlvRectangle)
        numUsableObstacles++;
      labelsPerObstacle[i] = 0;
      i++;
    }

    int maxLabelsPerObstacle;
    if (numLabels <= numUsableObstacles)
      maxLabelsPerObstacle = 1;
    else if (numLabels <= 3 * numUsableObstacles)
      maxLabelsPerObstacle = 3;
    else
      maxLabelsPerObstacle = 10000;

    // initialize the table of labels
    e = labelingModel.getLabels();
    i = 0;
    while (e.hasMoreElements()) {
      labels[i] = e.nextElement();
      assignmentLines[i] = new IlvLine(0, 0, 0, 0);
      i++;
    }

    // assign to each label any obstacle randomly. But no obstacle should get
    // more than maxLabelsPerObstacle labels.

    Random rand = new Random(0);

    assignmentTable = new Hashtable<Object, IlvGraphic>();
    e = labelingModel.getLabels();
    while (e.hasMoreElements()) {
      Object label = e.nextElement();
      while (label != null) {
        i = rand.nextInt();
        if (i < 0)
          i = -i;
        i = (i % numObstacles);
        if ((obstacles[i] instanceof IlvIcon || obstacles[i] instanceof IlvEllipse
            || obstacles[i] instanceof IlvRectangle) && labelsPerObstacle[i] < maxLabelsPerObstacle) {
          assignmentTable.put(label, (IlvGraphic) obstacles[i]);
          labelsPerObstacle[i]++;
          label = null;
        }
      }
    }

    // if necessary, make the labels transparent
    if (noLabelBackground)
      setNoLabelBackground(true);

    setLayoutParameters();

    if (labelAssignmentVisible)
      showLabelAssignment();
    mgrview.fitTransformerToContent();
    manager.reDraw();
  }

  /**
   * Set the layout parameters for simulated annealing.
   */
  void setLayoutParameters() {
    // now set the label descriptors.
    try {
      Object label;
      Object obstacle;
      int shape;
      for (int i = 0; i < labels.length; i++) {
        label = labels[i];
        obstacle = assignmentTable.get(label);
        if (obstacle instanceof IlvEllipse)
          shape = IlvAnnealingPointLabelDescriptor.ELLIPTIC;
        else
          shape = IlvAnnealingPointLabelDescriptor.RECTANGULAR;
        layout.setLabelDescriptor(label, new IlvAnnealingPointLabelDescriptor(label, obstacle, null, shape, 0, 0,
            prefDistFromOwnObstacle, prefDistFromOwnObstacle, preferredDirection));
      }
      layout.setAllowedTime(240000);
    } catch (Exception ex) {
      // ex.printStackTrace();
      showMessage("Illegal label descriptor: " + ex.getMessage());
    }
  }

  /**
   * Set the minimum offset between label and obstacle. We also take 20% of this
   * value as the minimum offset between pairs of labels.
   */
  void setMinimumOffset(double value) {
    if (value > 100.0)
      value = 100.0;
    if (value < 0.0)
      value = 0.0;

    // write again to be sure the right value is shown
    minOffset.setText("" + value);
    // set the new value of the parameter
    layout.setObstacleOffset(value);
    // also, increase the offset between the labels a little bit
    layout.setLabelOffset(0.2 * value);
  }

  /**
   * Set the preferred offset for all labels.
   */
  void setPreferredOffset(double value) {
    if (value > 30.0)
      value = 30.0;
    if (value < 0.0)
      value = 0.0;

    // write again to be sure the right value is shown
    prefOffset.setText("" + value);

    // set the new value of the parameter
    double oldval = prefDistFromOwnObstacle;
    prefDistFromOwnObstacle = value;
    // need to update the layout parameters only if something changed
    if (oldval != value)
      setLayoutParameters();
  }

  /**
   * Update the label assignment for all labels. This means, actually it update
   * the auxiliary line that shows the label assignment.
   */
  void updateLabelAssignment() {
    for (int i = 0; i < labels.length; i++) {
      IlvLine line = assignmentLines[i];
      IlvZoomableLabel label = (IlvZoomableLabel) labels[i];
      Object obstacle = assignmentTable.get(label);
      IlvRect lr = labelingModel.boundingBox(label);
      IlvRect or = labelingModel.boundingBox(obstacle);
      updateLabelAssignment(line, lr, or);
    }
  }

  /**
   * Update the auxiliary line showing label assignment for one label.
   */
  void updateLabelAssignment(IlvLine line, IlvRect labelRect, IlvRect obstacleRect) {
    double ox = obstacleRect.x + 0.5 * obstacleRect.width;
    double oy = obstacleRect.y + 0.5 * obstacleRect.height;
    double bestX, bestY;

    if (noLabelBackground) {
      // for transparent labels, show the aux line from label center
      bestX = labelRect.x + 0.5 * labelRect.width;
      bestY = labelRect.y + 0.5 * labelRect.height;
    } else {
      // for nonstransparent labels, show the aux line from the closest
      // corner of the label to (ox, oy)

      double bestDistSquare;
      double distSquare;
      double x, y;

      bestX = labelRect.x;
      bestY = labelRect.y;
      bestDistSquare = (bestX - ox) * (bestX - ox) + (bestY - oy) * (bestY - oy);

      x = labelRect.x + labelRect.width;
      y = labelRect.y;
      distSquare = (x - ox) * (x - ox) + (y - oy) * (y - oy);
      if (distSquare < bestDistSquare) {
        bestX = x;
        bestY = y;
        bestDistSquare = distSquare;
      }

      x = labelRect.x + labelRect.width;
      y = labelRect.y + labelRect.height;
      distSquare = (x - ox) * (x - ox) + (y - oy) * (y - oy);
      if (distSquare < bestDistSquare) {
        bestX = x;
        bestY = y;
        bestDistSquare = distSquare;
      }

      x = labelRect.x;
      y = labelRect.y + labelRect.height;
      distSquare = (x - ox) * (x - ox) + (y - oy) * (y - oy);
      if (distSquare < bestDistSquare) {
        bestX = x;
        bestY = y;
        bestDistSquare = distSquare;
      }
    }

    // set coordinates and color of the line
    line.setFrom(new IlvPoint(ox, oy));
    line.setTo(new IlvPoint(bestX, bestY));
    line.setForeground(Color.red);
  }

  /**
   * Show the label assignment.
   */
  void showLabelAssignment() {
    blockEventsCounter++;
    try {
      manager.setContentsAdjusting(true);

      // always update the label assignment when the lines are hidden
      if (labelAssignmentVisible) {
        hideLabelAssignment();
      }

      updateLabelAssignment();

      if (!labelAssignmentVisible) {
        for (int i = 0; i < labels.length; i++) {
          manager.addObject(assignmentLines[i], 0, false);
          manager.setSelectable(assignmentLines[i], false);
        }
        labelAssignmentVisible = true;
      }

      manager.setContentsAdjusting(false);
      manager.reDraw();
    } finally {
      blockEventsCounter--;
    }
  }

  /**
   * Hide the label assignment.
   */
  void hideLabelAssignment() {
    if (labelAssignmentVisible) {
      blockEventsCounter++;
      try {
        manager.setContentsAdjusting(true);
        for (int i = 0; i < labels.length; i++)
          if (assignmentLines[i].getGraphicBag() != null)
            manager.removeObject(assignmentLines[i], false);
        manager.setContentsAdjusting(false);
        manager.reDraw();
        labelAssignmentVisible = false;
      } finally {
        blockEventsCounter--;
      }
    }
  }

  /**
   * Show or hide the background of all labels.
   */
  void setNoLabelBackground(boolean flag) {
    IlvZoomableLabel label;
    IlvApplyObject apply;
    if (flag)
      apply = new IlvApplyObject() {
        Override
        public void apply(IlvGraphic obj, Object arg) {
          IlvZoomableLabel l = (IlvZoomableLabel) obj;
          l.setBorderOn(false);
          l.setBackgroundOn(false);
        }
      };
    else
      apply = new IlvApplyObject() {
        Override
        public void apply(IlvGraphic obj, Object arg) {
          IlvZoomableLabel l = (IlvZoomableLabel) obj;
          l.setBorderOn(true);
          l.setBackgroundOn(true);
        }
      };

    for (int i = 0; i < labels.length; i++) {
      label = (IlvZoomableLabel) labels[i];
      manager.applyToObject(label, apply, null, true);
    }
  }

  /**
   * Calculate the overlap amount.
   */
  String getOverlapAsString() {
    double overlap = 0.0;
    double maxOverlap = 0.0;
    int i, j;
    IlvRect r1, r2;
    IlvPoint[] pts;
    double w;
    Object ownerObstacle;

    // calculate the actual overlap value, and the hypothetical maximum
    // overlap. The hypothetical maximum overlap is used to print some
    // percentage value.

    for (i = 0; i < labels.length; i++) {
      r1 = labelingModel.boundingBox(labels[i]);
      for (j = i + 1; j < labels.length; j++) {
        r2 = labelingModel.boundingBox(labels[j]);
        overlap += labelingModel.getLabelOverlap(labels[i], r1, labels[j], r2, 0.0);
        maxOverlap += 0.5 * r1.width * r1.height;
        maxOverlap += 0.5 * r2.width * r2.height;
      }
    }

    for (i = 0; i < labels.length; i++) {
      r1 = labelingModel.boundingBox(labels[i]);
      ownerObstacle = assignmentTable.get(labels[i]);
      for (j = 0; j < obstacles.length; j++) {
        if (obstacles[j] != ownerObstacle) {
          if (labelingModel.isPolylineObstacle(obstacles[j])) {
            r2 = labelingModel.boundingBox(obstacles[j]);
            pts = labelingModel.getPolylinePoints(obstacles[j]);
            w = labelingModel.getPolylineWidth(obstacles[j]);
            overlap += labelingModel.getPolylineObstacleOverlap(labels[i], r1, obstacles[j], pts, w, 0.0);
            maxOverlap += 0.5 * r1.width * r1.height;
            maxOverlap += 0.5 * r2.width * r2.height;
          } else {
            r2 = labelingModel.boundingBox(obstacles[j]);
            overlap += labelingModel.getObstacleOverlap(labels[i], r1, obstacles[j], r2, 0.0);
            maxOverlap += 0.5 * r1.width * r1.height;
            maxOverlap += 0.5 * r2.width * r2.height;
          }
        }
      }
    }

    // the percentage of overlap
    double perc = 100 * (overlap / maxOverlap);

    // calculate the absolute overlap, with 2 digits after dot.
    overlap = ((int) (overlap * 100)) / 100.0;

    // calculate the logarithmic overlap percentage (with 2 digits after dot)
    // The logarithmic percentage is here more useful because it represents
    // better how much the overlaps disturb the user.
    perc = 100 / Math.log(101) * Math.log(perc + 1);
    float logPerc = ((int) (100 * perc)) / 100.0f;

    return ("" + overlap + " (Log. Perc.: " + logPerc + "%)");
  }

  /**
   * Preprocess graph: Add some local parameters for demonstration purpose.
   * Here, it tells the random layout about the layout region.
   */
  void preprocess() {
    IlvGraphic obj;

    // calculate the layout region
    IlvGraphicEnumeration selectedObjects = manager.getObjects();
    double minX = Double.MAX_VALUE;
    double maxX = -Double.MAX_VALUE;
    double minY = Double.MAX_VALUE;
    double maxY = -Double.MAX_VALUE;

    while (selectedObjects.hasMoreElements()) {
      obj = selectedObjects.nextElement();
      if (labelingModel.isObstacle(obj)) {
        IlvRect r = labelingModel.boundingBox(obj);
        if (r.x < minX)
          minX = r.x;
        if (r.x + r.width > maxX)
          maxX = r.x + r.width;
        if (r.y < minY)
          minY = r.y;
        if (r.y + r.height > maxY)
          maxY = r.y + r.height;
      }
    }

    randomLayout.setLayoutRegion(new IlvRect(minX, minY, (maxX - minX), (maxY - minY)));
  }

  /**
   * Listen to changes of the manager.
   */
  Override
  public void contentsChanged(ManagerContentChangedEvent evt) {
    // the label assignment, if visible, should be kept up to date
    if (!evt.isAdjusting() && blockEventsCounter == 0) {
      if (labelAssignmentVisible)
        showLabelAssignment();
    }
  }

  /**
   * Listen to selections of the manager.
   */
  Override
  public void selectionChanged(ManagerSelectionChangedEvent event) {
    if (labelingModel != null && layout.isAutoUpdate()) {
      // to make sure a deselected object is always moveable
      if (event.getGraphic() != null)
        manager.setMovable(event.getGraphic(), true);

      // we need to make sure that, if a label and its related obstacle are
      // both selected, that the label is registered nonmovable. Otherwise
      // we get weird effects because the label tries to follow the obstacle
      // by itself.

      IlvGraphicEnumeration e = manager.getSelectedObjects(false);
      while (e.hasMoreElements()) {
        IlvGraphic g = e.nextElement();
        if (labelingModel.isLabel(g)) {
          IlvGraphic obstacle = assignmentTable.get(g);
          if (obstacle != null && manager.isSelected(obstacle))
            manager.setMovable(g, false);
          else
            manager.setMovable(g, true);
        }
      }
    }
  }

  /**
   * Listen to changes of the transformer.
   */
  Override
  public void transformerChanged(TransformerChangedEvent event) {
    IlvTransformer t = event.getNewValue();
    // limit the zoom factor. Otherwise, we may run into serious rounding
    // problems in the arithmetic formulas of the auxiliary lines.
    if (t.zoomFactor() < 0.01) {
      t.setValues(0.01, t.getx12(), t.getx21(), 0.01);
      mgrview.setTransformer(t);
    } else if (t.zoomFactor() > 20.0) {
      t.setValues(20.0, t.getx12(), t.getx21(), 20.0);
      mgrview.setTransformer(t);
    } else if (labelAssignmentVisible) {
      // update the label assignment just for sure.
      showLabelAssignment();
      manager.reDraw();
    }
  }
}

/**
 * A label layout iteration listener. Implementing the interface
 * LabelLayoutEventListener gives you the possibility to receive during the
 * layout information about the behavior of the layout algorithm. This
 * information is contained in the label layout event.
 * 
 * In our case, we will simply print the percentage completed by the layout each
 * time the method layoutStepPerformed is called, that is after each iteration
 * of the Annealing Label Layout algorithm.
 */
class PointLabelingListener implements LabelLayoutEventListener {
  private float oldPercentage;
  private PointLabelLayoutApp app;

  PointLabelingListener(PointLabelLayoutApp app) {
    this.app = app;
  }

  /**
   * This method is automatically called by the layout algorithm.
   */
  Override
  public void layoutStepPerformed(LabelLayoutEvent event) {
    float percentage = event.getLayoutReport().getPercentageComplete();
    if (percentage != oldPercentage) {
      app.showMessage("" + percentage + "%");
      oldPercentage = percentage;
    }
  }

  /**
   * Initialize the listener by reseting the old percentage variable. This
   * method must be called before the layout is started.
   */
  void initialize() {
    oldPercentage = -1.0f;
  }
}

/**
 * A manager view with limited range for zooming. It doesn't allow to set the
 * zoom level below 0.001 or above 30.0, so that the demo runs smoothly even if
 * the user stress-tests the zoomin and zoomout button.
 */
class PointLabelingManagerView extends IlvManagerView {
  boolean insideVerifyTransformer = false;

  public PointLabelingManagerView(IlvManager manager) {
    super(manager);
  }

  /**
   * Checks a transformer. This method is called every time the transformer of
   * the view is changed by calling <code>setTransformer</code> or
   * <code>addTransformer</code>. It makes sure that the zoomFactor is between
   * 0.001 and 30.0.
   */
  Override
  public void verifyTransformer() {
    super.verifyTransformer();
    if (insideVerifyTransformer)
      return;
    IlvTransformer t = getTransformer();
    double zoomFactor = t.zoomFactor();
    if (zoomFactor < 0.001) {
      double x0 = t.getx0() * 0.999 / (1 - zoomFactor);
      double y0 = t.gety0() * 0.999 / (1 - zoomFactor);
      t.setValues(0.001, t.getx12(), t.getx21(), 0.001, x0, y0);
      insideVerifyTransformer = true;
      setTransformer(t);
      insideVerifyTransformer = false;
    } else if (zoomFactor > 30.0) {
      double x0 = t.getx0() * 29.0 / (zoomFactor - 1);
      double y0 = t.gety0() * 29.0 / (zoomFactor - 1);
      t.setValues(30.0, t.getx12(), t.getx21(), 30.0, x0, y0);
      insideVerifyTransformer = true;
      setTransformer(t);
      insideVerifyTransformer = false;
    }
  }
}