/*
 * Licensed Materials - Property of Rogue Wave Software, Inc. 
 * © Copyright Rogue Wave Software, Inc. 2014, 2017 
 * © 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 stock;

import java.awt.BasicStroke;
import java.awt.Color;

import ilog.views.chart.IlvAxis;
import ilog.views.chart.IlvChart;
import ilog.views.chart.IlvColor;
import ilog.views.chart.IlvDataInterval;
import ilog.views.chart.IlvLocalZoomAxisTransformer;
import ilog.views.chart.IlvScale;
import ilog.views.chart.IlvScaleAnnotation;
import ilog.views.chart.IlvStyle;
import ilog.views.chart.event.AxisChangeEvent;
import ilog.views.chart.event.AxisListener;
import ilog.views.chart.event.AxisRangeEvent;
import ilog.views.chart.graphic.IlvDataIndicator;

/**
 * A handler class for local zoom functionality.
 */
public class LocalZoomHandler implements AxisListener {
  private IlvScaleAnnotation _startAnnotation;
  private IlvScaleAnnotation _endAnnotation;
  private IlvLocalZoomAxisTransformer _t;
  private IlvChart _chart;
  private IlvChart[] _charts;
  private IlvDataIndicator[] _indicators;

  /**
   * Creates a new <code>LocalZoomHandler</code> object.
   * 
   * @param chart
   *          The chart to handle.
   * @param t
   *          The transformer to handle.
   * @param useScaleAnnotation
   *          A Boolean value indicating whether annotations should be set on
   *          the scales to display the values of the local zoom interval.
   * @param useIndicator
   *          A Boolean value indicating whether a data indicator should be used
   *          to display the zoom window.
   */
  private LocalZoomHandler(IlvChart chart, IlvChart[] auxCharts, IlvLocalZoomAxisTransformer t,
      boolean useScaleAnnotation, boolean useIndicator) {
    _t = t;
    _chart = chart;

    int count = (auxCharts == null ? 0 : auxCharts.length);
    _charts = new IlvChart[count + 1];
    _charts[0] = chart;
    if (count > 0)
      System.arraycopy(auxCharts, 0, _charts, 1, count);
    IlvScale scale = _chart.getScale(t.getAxis());
    if (scale != null && useScaleAnnotation) {
      IlvDataInterval zw = t.getZoomRange();
      _startAnnotation = new IlvScaleAnnotation(zw.getMin());
      _endAnnotation = new IlvScaleAnnotation(zw.getMax());
      scale.addAnnotation(_startAnnotation);
      scale.addAnnotation(_endAnnotation);
    }
    setUsingIndicator(useIndicator);
    t.getAxis().addAxisListener(this);
  }

  /**
   * Returns the chart associated with this object.
   */
  public final IlvChart getChart() {
    return _chart;
  }

  /**
   * Returns the transformer associated with this object.
   */
  public final IlvLocalZoomAxisTransformer getTransformer() {
    return _t;
  }

  /**
   * Returns the scale annotation that displays the start of the zoomed
   * interval, or <code>null</code> if no annotation is used.
   * 
   * @see #setStartAnnotation
   */
  public final IlvScaleAnnotation getStartAnnotation() {
    return _startAnnotation;
  }

  /**
   * Sets the scale annotation that displays the start of the zoomed interval.
   * 
   * @param anno
   *          The new annotation, or <code>null</code> to remove the previous
   *          one.
   * @see #getStartAnnotation
   */
  public void setStartAnnotation(IlvScaleAnnotation anno) {
    IlvScale scale = _chart.getScale(_t.getAxis());
    if (scale != null && _startAnnotation != null)
      scale.removeAnnotation(_startAnnotation);
    _startAnnotation = anno;
    if (scale != null && anno != null)
      scale.addAnnotation(anno);
  }

  /**
   * Returns the scale annotation that displays the end of the zoomed interval,
   * or <code>null</code> if no annotation is used.
   * 
   * @see #setEndAnnotation
   */
  public final IlvScaleAnnotation getEndAnnotation() {
    return _endAnnotation;
  }

  /**
   * Sets the scale annotation that displays the end of the zoomed interval.
   * 
   * @param anno
   *          The new annotation, or <code>null</code> to remove the previous
   *          one.
   * @see #getEndAnnotation
   */
  public void setEndAnnotation(IlvScaleAnnotation cursor) {
    IlvScale scale = _chart.getScale(_t.getAxis());
    if (scale != null && _endAnnotation != null)
      scale.removeAnnotation(_endAnnotation);
    _endAnnotation = cursor;
    if (scale != null && cursor != null)
      scale.addAnnotation(cursor);
  }

  /**
   * Returns the graphical indicator of the zoom window.
   * 
   * @see #useIndicator
   */
  public IlvDataIndicator[] getIndicators() {
    return _indicators;
  }

  /**
   * Specifies whether an indicator must be used to display the zoom window.
   * 
   * @param b
   *          A Boolean value indicating whether an indicator must be used.
   * @see #getIndicator
   */
  public void setUsingIndicator(boolean b) {
    if (_indicators != null && !b) {
      for (int i = 0; i < _charts.length; ++i) {
        if (_indicators[i] != null)
          _charts[i].removeDecoration(_indicators[i]);
      }
    }
    if (!b) {
      _indicators = null;
    } else {
      _indicators = createIndicators();
      for (int i = 0; i < _charts.length; ++i) {
        if (_indicators[i] != null)
          _charts[i].addDecoration(_indicators[i]);
      }
    }
  }

  protected IlvDataIndicator[] createIndicators() {
    int count = _charts.length;
    IlvDataIndicator[] indicators = new IlvDataIndicator[count];
    for (int i = 0; i < count; ++i) {
      if (_t.getAxis().getType() == IlvAxis.X_AXIS)
        indicators[i] = new IlvDataIndicator(-1, _t.getZoomRange(), null);
      else
        indicators[i] = new IlvDataIndicator(0, _t.getZoomRange(), null);
      IlvStyle style = new IlvStyle(new BasicStroke(2.f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND),
          IlvColor.setAlpha(Color.white, .6f), IlvColor.setAlpha(IlvColor.darkSlateGray, .2f));
      indicators[i].setStyle(style);
      indicators[i].setDrawOrder(IlvChart.DRAW_ABOVE);
    }
    return indicators;
  }

  /**
   * Invoked when the visible range of the associated axis changes.
   */
  Override
  public void axisRangeChanged(AxisRangeEvent ev) {
  }

  /**
   * Invoked when a change occurs on the associated axis. This method handles
   * <code>AxisChangeEvent.TRANSFORMER_CHANGE</code> events to update the scale
   * annotations and the data indicator.
   * 
   * @see #getIndicator
   * @see #getStartAnnotation
   * @see #getEndAnnotation
   */
  Override
  public void axisChanged(AxisChangeEvent evt) {
    if (evt.getType() == AxisChangeEvent.TRANSFORMER_CHANGE) {
      IlvDataInterval zw = _t.getZoomRange();
      if (_startAnnotation != null)
        _startAnnotation.setValue(zw.getMin());
      if (_endAnnotation != null)
        _endAnnotation.setValue(zw.getMax());
      if (_indicators != null) {
        for (int i = 0; i < _indicators.length; ++i) {
          if (_indicators[i] != null)
            _indicators[i].setRange(zw);
        }
      }
    }
  }

  /**
   * Associates the given chart and transformer through a
   * <code>LocalZoomHandler</code>.
   * 
   * @return The created <code>LocalZoomHandler</code>.
   */
  public static LocalZoomHandler set(IlvChart chart, IlvChart[] auxCharts, IlvLocalZoomAxisTransformer t) {
    LocalZoomHandler handler = new LocalZoomHandler(chart, auxCharts, t, false, true);

    chart.putClientProperty(new Key(t.getAxis()), handler);
    return handler;
  }

  /**
   * Creates an <code>IlvLocalZoomAxisTransformer</code> and associates it with
   * the given chart through a <code>LocalZoomHandler</code>.
   * 
   * @param chart
   *          The chart on which the transformer will be set.
   * @param axisIdx
   *          The index of the transformer axis. This parameter value should be
   *          <code>-1</code> to reference the x-axis, or a valid y-axis index
   *          value.
   * @param useScaleAnnotation
   *          A Boolean value indicating whether annotations should be set on
   *          the scales to display the values of the local zoom interval.
   * @param useIndicator
   *          A Boolean value indicating whether a data indicator should be used
   *          to display the zoom window.
   * @param zoomFactor
   *          The transformer zoom factor.
   * @param continuous
   *          The transformer <i>continuous</i> property.
   * @param min
   *          The minimum of the zoom interval.
   * @param max
   *          The maximum of the zoom interval.
   */
  public static LocalZoomHandler set(IlvChart chart, int axisIdx, boolean useScaleAnnotation, boolean useIndicator,
      double zoomFactor, boolean continuous, double min, double max) {
    IlvLocalZoomAxisTransformer t = new IlvLocalZoomAxisTransformer(min, max, zoomFactor, continuous);
    IlvAxis axis = (axisIdx == -1) ? chart.getXAxis() : chart.getYAxis(axisIdx);
    axis.setTransformer(t);
    LocalZoomHandler handler = new LocalZoomHandler(chart, null, t, useScaleAnnotation, useIndicator);
    chart.putClientProperty(new Key(axis), handler);
    return handler;
  }

  /**
   * Returns the <code>LocalZoomHandler</code> associated with the specified
   * chart and the given axis.
   */
  public static LocalZoomHandler get(IlvChart chart, int axisIdx) {
    IlvAxis axis = (axisIdx == -1) ? chart.getXAxis() : chart.getYAxis(axisIdx);
    return (LocalZoomHandler) chart.getClientProperty(new Key(axis));
  }

  /**
   * Removes the <code>LocalZoomHandler</code> associated with the specified
   * chart and the given axis.
   */
  public static void unset(IlvChart chart, int axisIdx) {
    LocalZoomHandler h = get(chart, axisIdx);
    if (h != null) {
      h.detach(chart);
    }
  }

  private void detach(IlvChart chart) {
    IlvAxis axis = _t.getAxis();
    IlvScale scale = chart.getScale(axis);
    axis.setTransformer(null);
    if (_startAnnotation != null)
      scale.removeAnnotation(_startAnnotation);
    if (_endAnnotation != null)
      scale.removeAnnotation(_endAnnotation);
    for (int i = 0; i < _charts.length; ++i) {
      if (_indicators[i] != null)
        _charts[i].removeDecoration(_indicators[i]);
    }
    chart.putClientProperty(new Key(axis), null);
  }

  // --------------------------------------------------------------------------
  /**
   * A key to associate an <code>LocalZoomHandler</code> with a chart and an
   * axis.
   */
  private static class Key {
    private IlvAxis axis;

    public Key(IlvAxis axis) {
      this.axis = axis;
    }

    Override
    public int hashCode() {
      return axis.hashCode();
    }

    Override
    public boolean equals(Object obj) {
      if (!(obj instanceof Key))
        return false;
      Key key = (Key) obj;
      return key.axis == this.axis;
    }
  }
}