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

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Enumeration;
import java.util.List;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.RepaintManager;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.BevelBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import ilog.views.chart.IlvChart;
import ilog.views.chart.IlvColor;
import ilog.views.chart.IlvScale;
import ilog.views.chart.IlvStyle;
import ilog.views.chart.data.IlvCyclicDataSet;
import ilog.views.chart.data.IlvDataSet;
import ilog.views.chart.data.IlvDefaultDataSet;
import ilog.views.chart.data.IlvMovingAvgDataSet;
import ilog.views.chart.graphic.IlvMarkerFactory;
import ilog.views.chart.graphic.IlvMarkerIcon;
import ilog.views.chart.renderer.IlvPieChartRenderer;
import ilog.views.chart.renderer.IlvSingleAreaRenderer;
import ilog.views.chart.renderer.IlvSingleBarRenderer;
import ilog.views.chart.renderer.IlvSingleChartRenderer;
import ilog.views.chart.renderer.IlvSinglePieRenderer;
import ilog.views.chart.renderer.IlvSinglePolylineRenderer;
import ilog.views.chart.renderer.IlvSingleScatterRenderer;
import ilog.views.chart.renderer.IlvSingleStairRenderer;
import ilog.views.util.IlvImageUtil;

/**
 * An example showing the capabilities of the Charts library for real-time
 * charting.
 */
public class RealTimeDemo extends shared.AbstractChartExample {
  /** The number of charts. */
  private static final int MONITOR_COUNT = 6;
  private static final String BUTTON_GROUP_PROP = "_ButtonGroup_Property__";
  private static final String ADD_POINT_TITLE = "New Points Per Update: ";
  // the default sizes of various windows
  // private static final int MAIN_WINDOW_DEFAULT_WIDTH = 550;
  // private static final int MAIN_WINDOW_DEFAULT_HEIGHT = 500;

  /** Number of visible points. */
  private static final int VISI_COUNT = 70;

  /** The data sets. */
  private MonitorDataSet[] inputData;
  private IlvChart[] monitors;
  /** Number of points added during each update. */
  private int updateCount = 2;
  /** The data sets of the redraw operation monitor. */
  private IlvDataSet drawTimeData;
  private IlvDefaultDataSet pieDrawTime;
  private IlvMovingAvgDataSet avgDrawTimeData;
  /** The panel used to change the update period. */
  protected AnimationPanel animPanel;
  /** A custom repaint manager that logs drawing time. */
  private LogRepaintManager logRpm;
  // private Random random = new Random();
  // private JFrame topFrame;

  /**
   * Initializes the example's user interface in the specified container.
   * 
   * @param container
   *          The <code>contentPane</code> of the
   *          <code>JFrame</code>.
   */
  Override
  public void init(Container container) {
    super.init(container);

    ((BorderLayout) container.getLayout()).setVgap(4);

    GridLayout layout = new GridLayout(MONITOR_COUNT / 2, 2);
    layout.setHgap(4);
    layout.setVgap(4);

    JPanel monitorPanel = new JPanel(layout);

    // Create the data sets. The data sets containing the random values are
    // instances of IlvCyclicDataSet with a buffer size equal to VISI_COUNT and
    // no x values storage.
    inputData = new MonitorDataSet[MONITOR_COUNT];
    monitors = new IlvChart[MONITOR_COUNT];
    List<Color> defaultColors = IlvColor.getDefaultColors();
    Color[] colors = defaultColors.toArray(new Color[defaultColors.size()]);
    colors[0] = IlvColor.lightBlue;
    colors[1] = IlvColor.orchid;
    for (int i = 0; i < MONITOR_COUNT; ++i) {
      IlvChart chart = createMonitor(i);
      monitors[i] = chart;
      inputData[i] = new MonitorDataSet("Input#" + i, VISI_COUNT);
      chart.addDecoration(inputData[i].getCursor());
      Color color = colors[i % colors.length];
      IlvStyle style = new IlvStyle(color, IlvColor.darker(color));
      IlvSingleChartRenderer r = createSelectedRenderer(chart);
      if (r != null) {
        if (r instanceof IlvSingleScatterRenderer)
          style = style.setStrokeOn(false);
        r.setStyle(style);
        chart.addRenderer(r, inputData[i]);
      }
      monitorPanel.add(chart);
    }
    container.add(monitorPanel, BorderLayout.CENTER);

    animPanel = new AnimationPanel(10) {
      Override
      public void sweepModeChanged(boolean sweepMode) {
        for (int i = 0; i < MONITOR_COUNT; ++i) {
          monitors[i].setShiftScroll(!sweepMode);
          monitors[i].getXAxis().setVisibleRange(0, VISI_COUNT - 1);
          inputData[i].setSweepMode(sweepMode);
        }
      }

      Override
      public void timerChanged() {
        addData();
      }
    };
    animPanel.setForeground(Color.white);

    JSlider slider = new JSlider(JSlider.HORIZONTAL, 1, 20, updateCount);
    slider.setMajorTickSpacing(5);
    slider.setMinorTickSpacing(1);
    final TitledBorder title = new TitledBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.black),
        ADD_POINT_TITLE + slider.getValue());
    title.setTitleColor(Color.black);
    slider.setBorder(title);
    slider.addChangeListener(new ChangeListener() {
      Override
      public void stateChanged(ChangeEvent evt) {
        JSlider slider = ((JSlider) evt.getSource());
        updateCount = slider.getValue();
        title.setTitle(ADD_POINT_TITLE + updateCount);
        slider.repaint();
      }
    });
    JPanel top = new JPanel(new GridLayout(1, 2));
    top.add(animPanel);
    top.add(slider);
    container.add(top, BorderLayout.NORTH);

    // Create the performance panel.
    container.add(createPerfPanel(), BorderLayout.SOUTH);

    // Create a LogRepaintManager to monitor the time spend
    // in drawing operations.
    logRpm = new LogRepaintManager() {
      // Adds the new measure to drawTimeData.
      Override
      public void newLog(long elapsed, long drawTime) {
        if (animPanel.getTimer().isRunning()) {
          double v = Math.min(100., (100. * drawTime) / elapsed);
          drawTimeData.addData(0, v);
          int count = avgDrawTimeData.getDataCount();
          if (count > 0) {
            v = avgDrawTimeData.getYData(count - 1);
            pieDrawTime.setData(0, -1, 100 - v);
            pieDrawTime.setData(1, -1, v);
          }
        }

      }
    };
    RepaintManager.setCurrentManager(logRpm);
    logRpm.startLog();
  }

  /**
   * Creates a chart monitor.
   */
  protected IlvChart createMonitor(int i) {
    // Create a new Cartesian chart with no default scales & grids.
    final IlvChart chart = new IlvChart(IlvChart.CARTESIAN, false);
    // Set a simple grid on the y-axis.
    chart.setYGrid(0, new SimpleGrid(Color.black, 2));

    // Configure the x and y axes.
    chart.getXAxis().setAutoDataRange(false);
    chart.getXAxis().setVisibleRange(0, VISI_COUNT - 1);
    chart.getYAxis(0).setDataRange(-8, 8);
    // Enable automatic scrolling as new data comes in outside the visible
    // range.
    chart.setShiftScroll(true);

    // Add a header. A header is a JComponent added to the chart on top
    // of the chart area.
    ActionListener lst = new ActionListener() {
      Override
      public void actionPerformed(ActionEvent evt) {
        IlvSingleChartRenderer r = createSelectedRenderer(chart);
        if (r != null) {
          IlvStyle style = chart.getRenderer(0).getStyle(0);
          if (r instanceof IlvSingleScatterRenderer)
            style = style.setStrokeOn(false);
          else
            style = style.setStrokeOn(true);
          try {
            r.setStyle(style);
            chart.setRenderer(0, r);
          } catch (IllegalArgumentException e) {
          }
        }
      }
    };
    JPanel header = new JPanel();
    header.setLayout(new BoxLayout(header, BoxLayout.X_AXIS));
    header.setBackground(Color.gray);
    header.setOpaque(true);
    JLabel label = new JLabel("Monitor#" + i, JLabel.LEFT);
    label.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 0));
    label.setForeground(Color.white);
    header.add(label);

    JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
    panel.setOpaque(false);
    ButtonGroup group = new ButtonGroup();
    JRadioButton button = createRendererButton("pline.gif", "pline2.gif", "POLYLINE", "Polyline");
    button.addActionListener(lst);
    group.add(button);
    panel.add(button);
    button = createRendererButton("stair.gif", "stair2.gif", "STAIR", "Stair");
    button.addActionListener(lst);
    group.add(button);
    panel.add(button);
    button = createRendererButton("scatter.gif", "scatter2.gif", "SCATTER", "Scatter");
    button.addActionListener(lst);
    group.add(button);
    panel.add(button);
    button = createRendererButton("area.gif", "area2.gif", "AREA", "Area");
    button.addActionListener(lst);
    group.add(button);
    panel.add(button);
    button = createRendererButton("bar.gif", "bar2.gif", "BAR", "Bar");
    button.addActionListener(lst);
    group.add(button);
    panel.add(button);

    Enumeration<AbstractButton> elems = group.getElements();
    int elemIdx = 0;
    while (elemIdx++ < i % group.getButtonCount())
      elems.nextElement();
    elems.nextElement().setSelected(true);

    header.add(panel);
    header.putClientProperty(BUTTON_GROUP_PROP, group);
    chart.setHeader(header);

    // Customize the chart appeareance.
    configureChart(chart, true);

    return chart;
  }

  private JRadioButton createRendererButton(String iconName, String selIconName, String actionCmd, String tooltip) {
    ImageIcon icon = null, selIcon = null;
    try {
      icon = new ImageIcon(IlvImageUtil.loadImageFromFile(getClass(), iconName));
      selIcon = new ImageIcon(IlvImageUtil.loadImageFromFile(getClass(), selIconName));
    } catch (Exception e) {
    }
    JRadioButton button = new JRadioButton(icon);
    button.setHorizontalAlignment(JRadioButton.LEFT);
    button.setBorder(null);
    button.setSelectedIcon(selIcon);
    button.setPreferredSize(new Dimension(button.getIcon().getIconWidth(), button.getIcon().getIconHeight()));
    button.setActionCommand(actionCmd);
    button.setToolTipText(tooltip);
    return button;
  }

  /**
   * Creates an instance of the current selected renderer type.
   */
  private IlvSingleChartRenderer createSelectedRenderer(IlvChart chart) {
    ButtonGroup group = (ButtonGroup) chart.getHeader().getClientProperty(BUTTON_GROUP_PROP);
    String action = group.getSelection().getActionCommand();
    IlvSingleChartRenderer r = null;
    if (action.equals("POLYLINE"))
      r = new IlvSinglePolylineRenderer();
    else if (action.equals("SCATTER"))
      r = new IlvSingleScatterRenderer(IlvMarkerFactory.getSquareMarker(), 2, null) {
        Override
        public boolean isClipped() {
          return true;
        }
      };
    else if (action.equals("AREA"))
      r = new IlvSingleAreaRenderer();
    else if (action.equals("STAIR"))
      r = new IlvSingleStairRenderer();
    else if (action.equals("BAR"))
      r = new IlvSingleBarRenderer();
    return r;
  }

  /**
   * Invoked by the background timer to add new data points.
   */
  protected void addData() {
    for (int i = 0; i < MONITOR_COUNT; ++i) {
      MonitorDataSet ds = inputData[i];
      // If we use the sweep mode, then data will be modified instead of being
      // appended. In that case, using batch updates would generate a
      // FULL_UPDATE
      // event.
      if (!ds.isSweepMode())
        ds.startBatch();
      for (int j = 0; j < updateCount; ++j) {
        ds.processData(RandomGenerator.rand(ds.getCounter()));
      }
      if (!ds.isSweepMode())
        ds.endBatch();
    }
  }

  /**
   * Creates a panel holding the chart that displays drawing time.
   */
  protected JPanel createPerfPanel() {
    JPanel perfPanel = new JPanel(new BorderLayout());
    perfPanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));

    IlvChart chart = new IlvChart();
    configureChart(chart, false);

    // Configure axes.
    chart.getXAxis().setAutoDataRange(false);
    chart.getXAxis().setVisibleRange(0, 50);
    chart.getYAxis(0).setDataMin(0);
    // We can specify a max of 100, but the auto-adjusting feature
    // shows small percentages in a better way.
    // chart.getYAxis(0).setDataMax(100);
    chart.setShiftScroll(true);
    chart.setScrollRatio(0.2);

    // Configure scales & grids.
    chart.setXScale(null);
    chart.setXGrid(new SimpleGrid(Color.black, 2));
    chart.getYGrid(0).setMajorPaint(Color.black);
    chart.getYScale(0).setTickLayout(IlvScale.TICK_INSIDE);

    // Add the chart renderer that displays drawing time.
    IlvStyle style = IlvStyle.createStroked(IlvColor.mediumSpringGreen);
    IlvSinglePolylineRenderer poly = new IlvSinglePolylineRenderer(style);
    poly.setMarker(IlvMarkerFactory.getSquareMarker());
    poly.setMarkerSize(2);
    // The renderer displays the moving average of the drawing time.
    drawTimeData = new IlvCyclicDataSet("PerfLog", 101, IlvCyclicDataSet.LINEAR_MODE, false);
    avgDrawTimeData = new IlvMovingAvgDataSet(drawTimeData, 5);
    chart.addRenderer(poly, avgDrawTimeData);

    // Add a header.
    IlvMarkerIcon icon = new IlvMarkerIcon(IlvMarkerIcon.SQUARE_MARKER, 6, new IlvStyle(IlvColor.mediumSpringGreen));
    JLabel header = new JLabel("Drawing time (% of overall spent time)", icon, SwingConstants.CENTER);
    header.setForeground(Color.white);
    header.setOpaque(true);
    header.setBackground(Color.gray);
    perfPanel.add(header, BorderLayout.NORTH);

    perfPanel.add(chart, BorderLayout.CENTER);

    chart = new IlvChart(IlvChart.PIE);
    chart.setBorder(BorderFactory.createEmptyBorder(0, 3, 0, 0));
    chart.getChartArea().setOpaque(true);

    configureChart(chart, false);
    chart.setAntiAliasing(true);
    pieDrawTime = new IlvDefaultDataSet("Drawing Time", new double[] { 100., 0. });
    pieDrawTime.setDataLabels(new String[] { "Other", "Drawing time" });
    IlvPieChartRenderer pieR = new IlvPieChartRenderer();
    pieR.getDataSource().addDataSet(pieDrawTime);
    chart.addRenderer(pieR);
    int dim = (int) Math.min(chart.getPreferredSize().getHeight(), chart.getPreferredSize().getWidth());
    chart.setPreferredSize(new Dimension(dim, dim));
    ((IlvSinglePieRenderer) pieR.getChild(0))
        .setSliceColors(new Color[] { IlvColor.darkSlateGray, IlvColor.mediumSpringGreen });
    perfPanel.add(chart, BorderLayout.EAST);

    return perfPanel;
  }

  /**
   * Customizes the appearance of the specified chart.
   */
  protected void configureChart(IlvChart chart, boolean withBorder) {
    chart.setForeground(Color.black);
    chart.setFont(new Font("Dialog", Font.PLAIN, 10));
    chart.getChartArea().setPlotStyle(new IlvStyle(Color.black, IlvColor.darkSlateGray));
    if (withBorder)
      chart.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
  }

  /**
   * Application mainline.
   */
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      Override
      public void run() {
        JFrame frame = new JFrame("Real-time Supervision sample");
        shared.AbstractChartExample demo = new RealTimeDemo();
        demo.init(frame.getContentPane());
        demo.setFrameGeometry(650, 600, true);
        frame.setVisible(true);
      }
    });
  }

}