/*
 * 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.
 */
package treemap;

import java.awt.Color;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.faces.event.ActionEvent;
import javax.faces.event.ValueChangeEvent;
import javax.swing.tree.TreePath;

import ilog.views.chart.IlvChart;
import ilog.views.chart.IlvDisplayPoint;
import ilog.views.chart.IlvStyle;
import ilog.views.chart.data.IlvDataSource;
import ilog.views.chart.datax.adapter.IlvClusterNode;
import ilog.views.chart.event.TreemapFocusEvent;
import ilog.views.chart.event.TreemapFocusListener;
import ilog.views.chart.graphic.IlvDataRenderingHint;
import ilog.views.chart.renderer.IlvTreemapChartRenderer;

/**
 * A treemap that manages selection.
 */
public class TreemapWithHistory implements Serializable {
  // The treemap chart.
  private IlvChart chart;

  // The focuses that can be reached through the back button.
  private LinkedList<TreePath> /* of TreePath */ history;

  // The focuses that can be reached through the forward button.
  private LinkedList<TreePath> /* of TreePath */ future;

  // The path to the selected object, or null for none.
  private TreePath selection;

  // A rendering hint that highlights a rectangle.
  private IlvDataRenderingHint highlightRendering;

  // Selection state of the buttons
  private boolean backButtonEnabled;

  private boolean chooseButtonEnabled;

  private boolean outButtonEnabled;

  private boolean forwardButtonEnabled;

  // selection listeners
  private List<SelectionListener> listeners;

  // ========================================================================

  /**
   * Creates a new <code>TreemapWithHistory</code> instance.
   * 
   * @param dataSource
   *          The data source to set.
   */
  public TreemapWithHistory(IlvDataSource dataSource) {
    chart = new IlvChart(IlvChart.TREEMAP);
    chart.setUsingEventThread(false);
    chart.setDataSource(dataSource);
    initSelection();
    initHistory();
  }

  /**
   * Returns the encapsulated treemap chart.
   * 
   * @return The treemap chart.
   */
  public IlvChart getChart() {
    return chart;
  }

  // ============================ The Selection ============================

  /**
   * Initializes the support for the selection.
   */
  private void initSelection() {
    // Create an object that describes how to render a selected rectangle.
    // Here we make it draw a yellow, three pixels wide border.
    highlightRendering = new IlvDataRenderingHint() {
      final IlvStyle style1 = new IlvStyle(3.0f, Color.yellow).setFillOn(false);

      Override
      public IlvStyle getStyle(IlvDisplayPoint dp, IlvStyle defaultStyle) {
        // IlvDisplayObjectArea da = (IlvDisplayObjectArea) dp;
        if (style1.isFillOn())
          return style1;
        else
          return style1.setFillOn(true).setFillPaint(defaultStyle.getFillPaint());
      }
    };

    // Initially no rectangle is selected.
    selection = null;
  }

  /**
   * The selection event.
   * 
   * @param event
   *          The value change event.
   */
  public void selectionChanged(ValueChangeEvent event) {
    TreePath path = (TreePath) event.getNewValue();
    if (path != null)
      setSelection(path);
  }

  /**
   * Returns the path to the selected object, or null for none.
   * 
   * @return Current selection
   */
  public TreePath getSelection() {
    return selection;
  }

  /**
   * Changes the selection.
   * 
   * @param newSelection
   *          The new selection to set.
   */
  public void setSelection(TreePath newSelection) {
    if (!(newSelection != null ? newSelection.equals(selection) : selection == null)) {
      TreePath oldSelection = selection;
      selection = newSelection;
      IlvTreemapChartRenderer renderer = (IlvTreemapChartRenderer) chart.getRenderer(0);
      // Remove the rendering hint for the old selected rectangle.
      if (oldSelection != null)
        renderer.setRenderingHint(oldSelection.getLastPathComponent(), null);
      // Enable the rendering hint for the new selected rectangle.
      if (newSelection != null)
        renderer.setRenderingHint(newSelection.getLastPathComponent(), highlightRendering);
      updateEnabledStatuses();
      // update synchronized components
      fireSelectionChanged(newSelection);
    }
  }

  // ============================= The History =============================

  /**
   * Back action listener.
   * 
   * @param event
   *          The action event
   */
  public void backActionListener(ActionEvent event) {
    if (history.size() > 0) {
      TreePath last = history.removeLast();
      IlvTreemapChartRenderer renderer = (IlvTreemapChartRenderer) chart.getRenderer(0);
      TreePath current = renderer.getFocus();
      future.addFirst(current);
      renderer.setFocus(last);
      // fireSubtreeChanged(last);
      updateEnabledStatuses();
    }
  }

  private boolean shouldChooseActionEnabled() {
    if (selection != null) {
      IlvTreemapChartRenderer renderer = (IlvTreemapChartRenderer) chart.getRenderer(0);
      TreePath current = renderer.getFocus();
      return (current == null || (current.isDescendant(selection) && !current.equals(selection)));
    } else
      return false;

  }

  /**
   * Choose action listener.
   * 
   * @param event
   *          The action event
   */
  public void chooseActionListener(ActionEvent event) {
    if (shouldChooseActionEnabled()) {
      TreePath next = selection;
      IlvTreemapChartRenderer renderer = (IlvTreemapChartRenderer) chart.getRenderer(0);
      TreePath current = renderer.getFocus();
      history.addLast(current);
      renderer.setFocus(next);
      // fireSubtreeChanged(next);
      updateEnabledStatuses();
    }

  }

  private boolean shouldOutActionEnabled() {
    IlvTreemapChartRenderer renderer = (IlvTreemapChartRenderer) chart.getRenderer(0);
    TreePath current = renderer.getFocus();
    return (current != null && current.getPathCount() > 1);
  }

  /**
   * Out action listener.
   * 
   * @param event
   *          The action event
   */
  public void outActionListener(ActionEvent event) {
    if (shouldOutActionEnabled()) {
      IlvTreemapChartRenderer renderer = (IlvTreemapChartRenderer) chart.getRenderer(0);
      TreePath current = renderer.getFocus();
      TreePath next = current.getParentPath();
      if (next.getParentPath() == null) // model's root?
        next = null;
      history.addLast(current);
      renderer.setFocus(next);
      // fireSubtreeChanged(next);
      updateEnabledStatuses();
    }
  }

  /**
   * forward action listener.
   * 
   * @param event
   *          The action event
   */
  public void forwardActionListener(ActionEvent event) {
    if (future.size() > 0) {
      TreePath next = future.removeFirst();
      IlvTreemapChartRenderer renderer = (IlvTreemapChartRenderer) chart.getRenderer(0);
      TreePath current = renderer.getFocus();
      history.addLast(current);
      renderer.setFocus(next);
      // fireSubtreeChanged(next);
      updateEnabledStatuses();
    }
  }

  /**
   * Initializes the history and the actions that modify or notify the history.
   */
  private void initHistory() {
    history = new LinkedList<TreePath>();
    future = new LinkedList<TreePath>();

    // Add an interactor that will drill-down on a rectangle when the user
    // double-clicks on it.
    // This interactor works in parallel to the selectInteractor above.
    /*
     * IlvChartInteractor doubleClickInteractor = new IlvChartPickInteractor() {
     * protected boolean isPickingEvent(MouseEvent event) { return
     * (event.getID() == MouseEvent.MOUSE_PRESSED && event.getClickCount() ==
     * 2); } }; doubleClickInteractor.setConsumeEvents(false); // for
     * selectInteractor doubleClickInteractor.addChartInteractionListener( new
     * ChartInteractionListener() { public void
     * interactionPerformed(ChartInteractionEvent event) { // Get the rectangle
     * on which the user clicked. IlvDisplayObjectArea da =
     * (IlvDisplayObjectArea)event.getDisplayPoint(); setSelection(da != null ?
     * da.getPath() : null); chooseAction.actionPerformed(null); } });
     * chart.addInteractor(doubleClickInteractor);
     */

    // Add a listener that will avoid "blank screen" situations by
    // resetting the focus when some changes in the tree model have the
    // consequence that the current focus no longer is in the tree model.
    chart.addTreemapFocusListener(new TreemapFocusListener() {
      Override
      public void focusChanged(TreemapFocusEvent event) {
        if (event.getType() == TreemapFocusEvent.FOCUS_DELETED) {
          TreePath oldFocus = event.getOldFocus();
          IlvTreemapChartRenderer renderer = (IlvTreemapChartRenderer) chart.getRenderer(0);
          TreePath newFocus;
          if (oldFocus.getLastPathComponent() instanceof IlvClusterNode)
            // It'd tricky to find the new IlvClusterNode
            // corresponding to the old one.
            newFocus = null;
          else
            newFocus = renderer.getTreeModel().getPath(oldFocus.getLastPathComponent());
          renderer.setFocus(newFocus);
          // fireSubtreeChanged(newFocus);
          updateEnabledStatuses();
        }
      }
    });
  }

  /**
   * Updates the selection state of the navigation buttons.
   */
  public void updateEnabledStatuses() {
    backButtonEnabled = history.size() > 0;
    chooseButtonEnabled = shouldChooseActionEnabled();
    outButtonEnabled = shouldOutActionEnabled();
    forwardButtonEnabled = future.size() > 0;
  }

  /**
   * Adds a selection listener.
   * 
   * @param listener
   *          The listener to add.
   */
  public void addSelectionListener(SelectionListener listener) {
    if (listeners == null)
      listeners = new ArrayList<TreemapWithHistory.SelectionListener>();
    if (!listeners.contains(listener))
      listeners.add(listener);
  }

  /**
   * Remove a selection listener.
   * 
   * @param listener
   *          The listener to remove.
   */
  public void removeSelectionListener(SelectionListener listener) {
    if (listeners.contains(listener))
      listeners.remove(listener);
  }

  /**
   * Broacast an event that involves a sub tree to the registered listeners.
   * 
   * @param treepath
   *          The new selection.
   */
  protected void fireSelectionChanged(TreePath treepath) {
    if (listeners != null)
      for (Iterator<SelectionListener> iter = listeners.iterator(); iter.hasNext();) {
        SelectionListener listener = iter.next();
        listener.selectionChanged(treepath);
      }

  }

  /**
   * Clears the history's contents.
   */
  public void clearHistory() {
    history.clear();
    future.clear();
    updateEnabledStatuses();
  }

  /**
   * Returns <code>true</code> if the back button is disabled,
   * <code>false</code> otherwise.
   * 
   * @return <code>true</code> if the back button is disabled,
   *         <code>false</code> otherwise.
   */
  public boolean isBackButtonDisabled() {
    return !backButtonEnabled;
  }

  /**
   * Returns <code>true</code> if the choose button is disabled,
   * <code>false</code> otherwise.
   * 
   * @return <code>true</code> if the choose button is disabled,
   *         <code>false</code> otherwise.
   */
  public boolean isChooseButtonDisabled() {
    return !chooseButtonEnabled;
  }

  /**
   * Returns <code>true</code> if the forward button is disabled,
   * <code>false</code> otherwise.
   * 
   * @return <code>true</code> if the forward button is disabled,
   *         <code>false</code> otherwise.
   */
  public boolean isForwardButtonDisabled() {
    return !forwardButtonEnabled;
  }

  /**
   * Returns <code>true</code> if the out button is disabled, <code>false</code>
   * otherwise.
   * 
   * @return <code>true</code> if the out button is disabled, <code>false</code>
   *         otherwise.
   */
  public boolean isOutButtonDisabled() {
    return !outButtonEnabled;
  }

  /**
   * This interface defines a listener to register on a
   * <code>TreemapWithHistory</code>.
   */
  public static interface SelectionListener {
    /**
     * Action called when the selection has changed.
     * 
     * @param subtree
     *          The sub tree affected by this event.
     */
    public void selectionChanged(TreePath subtree);
  }

}