/*
 * 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 listener;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.SwingUtilities;

import ilog.views.chart.IlvAffineAxisTransformer;
import ilog.views.chart.IlvAxis;
import ilog.views.chart.IlvChart;
import ilog.views.chart.IlvColor;
import ilog.views.chart.IlvLegend;
import ilog.views.chart.IlvScale;
import ilog.views.chart.IlvStyle;
import ilog.views.chart.IlvValueFormat;
import ilog.views.chart.data.IlvDataSet;
import ilog.views.chart.data.IlvDataSource;
import ilog.views.chart.data.IlvDefaultDataSet;
import ilog.views.chart.event.DataSetContentsEvent;
import ilog.views.chart.event.DataSetListener;
import ilog.views.chart.event.DataSetPropertyEvent;
import ilog.views.chart.graphic.IlvGradientRenderingHint;
import ilog.views.chart.renderer.IlvBarChartRenderer;
import ilog.views.chart.renderer.IlvHiLoChartRenderer;
import shared.AbstractChartExample;
import shared.AnimationPanel;

/**
 * The <code>ListenerDemo</code> class.
 */
public class ListenerDemo extends AbstractChartExample {
  /** Font for y-scales. */
  protected static final Font Y_SCALE_FONT = new Font("Dialog", Font.PLAIN, 10);
  /** The default stroke used by renderers. */
  protected static final Stroke DEFAULT_STROKE = new BasicStroke(2.f);
  /** The default background color used for plotting areas. */
  protected static final Color BG_COLOR = IlvColor.getColor("slategrey");// dimGray;
  // IlvColor.darker(Color.gray);
  /** The hilo chart renderer. */
  protected IlvHiLoChartRenderer hiloR;
  /** The hilo data source. */
  protected IlvDataSource hiloDS;
  /** The temperature data set. */
  protected IlvDataSet tempDs;

  /**
   * The <code>DataSetListener</code> used to update the hilo data source when a
   * point is added to the temperature data set.
   */
  DataSetListener tempListener = new DataSetListener() {
    // Call when a change occurs on the temperature data set.
    Override
    public void dataSetContentsChanged(DataSetContentsEvent evt) {
      if (evt.getType() == DataSetContentsEvent.DATA_ADDED) {
        IlvDataSet ds = evt.getDataSet();
        double x = ds.getXData(evt.getFirstIdx());
        double y = ds.getYData(evt.getFirstIdx());
        int count = ds.getDataCount() - 1;
        if (count < 0)
          count = 0;
        if ((count % 24) == 0) {
          // If the point begins a new day, add it to all the hilo data
          // sets (Low, High, Start, End).
          for (int i = 0; i < hiloDS.getDataSetCount(); ++i) {
            hiloDS.getDataSet(i).addData(0, y);
          }
        } else {
          // Otherwise, update the hilo data sets depending on the new
          // value:
          // - The new point is added to the "end" data set (since it
          // is the last point at this time).
          // - If its y value is the highest value, add it to the
          // high data set.
          // - If its y value is the lowest value, add it to the low
          // data set.
          int idx = count / 24;
          // Add the new point to the "end" data set.
          hiloDS.getDataSet(3).setData(idx, x, y);
          // Update the high data set.
          double oldTemp = hiloDS.getDataSet(1).getYData(idx);
          if (y > oldTemp)
            hiloDS.getDataSet(1).setData(idx, x, y);
          // Update the low data set.
          oldTemp = hiloDS.getDataSet(0).getYData(idx);
          if (y < oldTemp)
            hiloDS.getDataSet(0).setData(idx, x, y);
        }
      }
    }

    Override
    public void dataSetPropertyChanged(DataSetPropertyEvent evt) {
    }
  };

  // The timer ActionListener used to generate the new values.
  ActionListener tl = new ActionListener() {
    Override
    public void actionPerformed(ActionEvent evt) {
      int count = tempDs.getDataCount();
      double oldy = (count != 0) ? tempDs.getYData(count - 1) : 0;
      tempDs.addData(0, ValueGenerator.nextValue(oldy));
    }
  };

  /**
   * Initializes the sample.
   */
  Override
  public void init(Container container) {
    super.init(container);

    AnimationPanel animPanel = new AnimationPanel(10);
    animPanel.getTimer().setInitialDelay(0);
    // Plug the action listener used to generate the values on the timer.
    animPanel.getTimer().addActionListener(tl);

    JPanel panel = new JPanel();
    panel.setBorder(BorderFactory.createTitledBorder("HiLo mode"));
    ButtonGroup group = new ButtonGroup();
    // A hilo renderer can have 3 representation modes: CLUSTERED, CANDLE,
    // and OPENCLOSE. The first one only handles pairs of data sets, so it
    // cannot be used here (we want to handle low/high/start/end data).
    // This radio button switches the mode to CANDLE.
    JRadioButton radio = new JRadioButton("Candle");
    radio.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent evt) {
        hiloR.setMode(IlvHiLoChartRenderer.CANDLE);
      }
    });
    group.add(radio);
    panel.add(radio);
    // This radio button switches the mode to OPENCLOSE.
    radio = new JRadioButton("OpenClose", true);
    radio.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent evt) {
        hiloR.setMode(IlvHiLoChartRenderer.OPENCLOSE);
      }
    });
    group.add(radio);
    panel.add(radio);
    animPanel.add(panel);
    animPanel.setBorder(BorderFactory.createEtchedBorder());
    container.setLayout(new BorderLayout());
    container.add(animPanel, BorderLayout.NORTH);

    // == Create the charts.
    IlvChart tempChart = createChart();
    IlvChart hiloChart = createChart();

    // == tempChart initialization.

    // Disable the auto data range calculation on both axes and set the
    // x visible range to 48 hours
    tempChart.getXAxis().setAutoDataRange(false);
    tempChart.getXAxis().setVisibleRange(0, 48);

    IlvScale xScale = tempChart.getXScale();
    // We want to format the scale labels as Day:Hour. To do so, a
    // specialized IlvValueFormat is set on the x scale. This format is used
    // by the scale when computing the labels.
    xScale.setLabelFormat(new IlvValueFormat() {
      StringBuffer buffer = new StringBuffer();

      Override
      public synchronized String formatValue(double value) {
        buffer.setLength(0);
        buffer.append(Integer.toString((int) value / 24 + 1));
        buffer.append(":");
        buffer.append(Integer.toString((int) value % 24));
        return buffer.toString();
      }
    });
    xScale.setStepUnit(Double.valueOf(12.), Double.valueOf(1.));

    // Set the x-scale title.
    xScale.setTitle("Day : Hour");

    // The data set containing the temperatures.
    tempDs = new IlvDefaultDataSet("Temperature", 32, false);
    // Add the listener used to update the hilo data sets.
    tempDs.addDataSetListener(tempListener);
    // The temperatures are rendered as bars, covering 100% of the cluster
    // area.
    IlvBarChartRenderer barR = new IlvBarChartRenderer(100, IlvBarChartRenderer.CLUSTERED);
    // Add the bar renderer to the chart and associate it with the
    // temperature data set.
    tempChart.addRenderer(barR, tempDs);

    // To show graphically where a measurement lies in the
    // temperature range, each bar will be rendered using its own color,
    // the color being determined according to the temperature value:
    // the coldest temperature will be rendered as a blue bar, the hottest
    // as a red one. This color determination is done automatically by the
    // bar renderer at drawing time, thanks to an IlvGradientRenderingHint.
    // You just have to define the gradient (a value-color binding) and
    // set this rendering hint on the temperature data set.
    double[] values = { ValueGenerator.TMIN, 0, ValueGenerator.TMAX };
    Color[] colors = { Color.blue, Color.white, Color.red };
    IlvGradientRenderingHint hint = new IlvGradientRenderingHint(values, colors);
    barR.setRenderingHint(tempDs, hint);

    // Set visible range.
    hiloChart.getXAxis().setAutoDataRange(false);
    hiloChart.getXAxis().setVisibleRange(0, 28);
    // A ValueFormat that formats the scale labels as Week:Day.
    hiloChart.getXScale().setLabelFormat(new IlvValueFormat() {
      StringBuffer buffer = new StringBuffer();

      Override
      public synchronized String formatValue(double value) {
        buffer.setLength(0);
        buffer.append(Integer.toString((int) value / 7 + 1));
        buffer.append(":");
        buffer.append(Integer.toString((int) value % 7 + 1));
        return buffer.toString();
      }
    });
    hiloChart.getXScale().setStepUnit(Double.valueOf(7.), Double.valueOf(1.));
    hiloChart.getXScale().setTitle("Week : Day", 0.);
    // hiloChart.getXScale().setTitlePlacement(99);

    // Create an IlvHiLoChartRenderer in OPENCLOSE mode. In this mode, an
    // IlvHiLoChartRenderer handles 2 pairs of data sets per child renderer:
    // the Low/High data sets and the Start/End (that is, OPENCLOSE) data sets.
    // Each pair is associated with one IlvSingleHiLoRenderer child.
    hiloR = new IlvHiLoChartRenderer(IlvHiLoChartRenderer.OPENCLOSE, 0, 50);
    // Create the Low, High, Start, and End data sets that will be
    // associated with this renderer.
    IlvDataSet[] hiloDatasets = { new IlvDefaultDataSet("Low", 32, false), new IlvDefaultDataSet("High", 32, false),
        new IlvDefaultDataSet("Start", 32, false), new IlvDefaultDataSet("End", 32, false) };
    hiloDS = hiloR.getDataSource();
    hiloDS.setDataSets(hiloDatasets);
    hiloChart.addRenderer(hiloR);
    // Initialize custom rendering styles. An IlvSingleHiLoRenderer uses two
    // rendering styles: one as the rise rendering style, one as the fall
    // rendering style. Since 2 children are handled, we need to define 4
    // rendering styles, each pair associated with a child renderer.
    Color rise = new Color(183, 183, 255);
    Color fall = new Color(255, 123, 123);
    IlvStyle[] styles = { new IlvStyle(DEFAULT_STROKE, Color.white, Color.lightGray), // rise
        new IlvStyle(DEFAULT_STROKE, new Color(100, 149, 237), new Color(60, 119, 197)), // fall
        new IlvStyle(DEFAULT_STROKE, rise, IlvColor.brighter(rise)), // rise
        new IlvStyle(DEFAULT_STROKE, fall, IlvColor.brighter(fall)) // fall
    };
    hiloR.setStyles(styles);

    panel = new JPanel();
    panel.setLayout(new GridLayout(0, 1));
    panel.setBorder(BorderFactory.createEtchedBorder());
    panel.add(tempChart);
    panel.add(hiloChart);
    container.add(panel, BorderLayout.CENTER);

    // Initialize a legend.
    IlvLegend legend = new IlvLegend();
    legend.setBackground(BG_COLOR);
    legend.setForeground(Color.white);
    legend.setBorder(BorderFactory.createLineBorder(Color.black, 2));
    // Set a flow layout. By default, the legend uses a GridLayout when
    // it is not added to a chart.
    legend.setLayout(new FlowLayout());
    // Associate the legend with the hilo chart. This legend is not added to
    // the chart itself, but to the main container.
    hiloChart.setLegend(legend);
    panel = new JPanel();
    panel.setBorder(BorderFactory.createEtchedBorder());
    panel.add(legend);
    container.add(panel, BorderLayout.SOUTH);
  }

  /**
   * Creates a chart instance with custom settings.
   */
  protected IlvChart createChart() {
    IlvChart chart = new IlvChart();
    chart.setForeground(Color.black);
    chart.setScalingFont(true);

    // == Customize y-scales.
    IlvScale yScale = chart.getYScale(0);
    yScale.setTitle("Celsius");
    // The y-scale title position will be at 100% of
    // the visible range, that is, at the maximum value.
    // The default value is 50, that is, at the middle of the scale.
    yScale.setTitlePlacement(100);
    yScale.setLabelFont(Y_SCALE_FONT);

    // Explicitly set the data range of the y-scales to [TMIN, TMAX].
    chart.getYAxis(0).setDataRange(ValueGenerator.TMIN, ValueGenerator.TMAX);

    addFahrenheitScale(chart);

    // == Customize Chart Area.
    IlvChart.Area chartArea = chart.getChartArea();
    chartArea.setPlotBackground(BG_COLOR);

    // Enable automatic scrolling as new data comes in outside the visible
    // range. The amount of visible range scrolled back is set to .5, meaning
    // that half the visible range will be scrolled.
    chart.setShiftScroll(true);
    chart.setScrollRatio(.5);

    return chart;
  }

  /**
   * Adds another y-scale to the chart. This scale will display graduations
   * using fahrenheit degrees.
   */
  private void addFahrenheitScale(IlvChart chart) {
    // Add another y-scale
    IlvAxis yAxis = chart.addYAxis(true, false);
    // Set a transformer that represents Celcius->Fahrenheit conversion
    yAxis.setTransformer(new IlvAffineAxisTransformer(1.8, 32));
    // Synchronize the 'Fahrenheit' axis with the 'Celcius' axis.
    yAxis.synchronizeWith(chart.getYAxis(0), true);

    IlvScale yScale = chart.getYScale(1);
    yScale.setTitle("Fahrenheit");
    yScale.setTitlePlacement(100);
    yScale.setLabelFont(Y_SCALE_FONT);
  }

  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      Override
      public void run() {
        JFrame frame = new JFrame("DataSet listener sample");
        ListenerDemo demo = new ListenerDemo();
        demo.init(frame.getContentPane());
        demo.setFrameGeometry(600, 600, true);
        frame.setVisible(true);
      }
    });
  }
}