/*
 * 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.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.net.URL;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JSplitPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;

import com.ibm.icu.text.DecimalFormat;
import com.ibm.icu.text.NumberFormat;

import ilog.views.chart.IlvAffineAxisTransformer;
import ilog.views.chart.IlvAxis;
import ilog.views.chart.IlvAxisTransformerException;
import ilog.views.chart.IlvChart;
import ilog.views.chart.IlvChartInteractor;
import ilog.views.chart.IlvChartRenderer;
import ilog.views.chart.IlvColor;
import ilog.views.chart.IlvDataInterval;
import ilog.views.chart.IlvDefaultStepsDefinition;
import ilog.views.chart.IlvDisplayPoint;
import ilog.views.chart.IlvGrid;
import ilog.views.chart.IlvLabelRenderer;
import ilog.views.chart.IlvLegend;
import ilog.views.chart.IlvLocalZoomAxisTransformer;
import ilog.views.chart.IlvScale;
import ilog.views.chart.IlvStyle;
import ilog.views.chart.IlvTimeUnit;
import ilog.views.chart.action.IlvChartAction;
import ilog.views.chart.action.IlvChartFitAction;
import ilog.views.chart.data.IlvDataSet;
import ilog.views.chart.data.IlvDefaultDataSet;
import ilog.views.chart.data.IlvDefaultDataSource;
import ilog.views.chart.event.AxisChangeEvent;
import ilog.views.chart.event.AxisListener;
import ilog.views.chart.event.AxisRangeEvent;
import ilog.views.chart.event.ChartHighlightInteractionEvent;
import ilog.views.chart.event.ChartInteractionEvent;
import ilog.views.chart.event.ChartInteractionListener;
import ilog.views.chart.event.DataSourceEvent;
import ilog.views.chart.event.DataSourceListener;
import ilog.views.chart.interactor.IlvChartLocalPanInteractor;
import ilog.views.chart.interactor.IlvChartLocalReshapeInteractor;
import ilog.views.chart.interactor.IlvChartPanInteractor;
import ilog.views.chart.interactor.IlvChartXScrollInteractor;
import ilog.views.chart.renderer.IlvHiLoChartRenderer;
import ilog.views.chart.renderer.IlvPolylineChartRenderer;
import ilog.views.chart.renderer.IlvSingleAreaRenderer;
import ilog.views.chart.renderer.IlvSingleBarRenderer;
import ilog.views.chart.renderer.IlvSinglePolylineRenderer;
import ilog.views.chart.renderer.IlvSingleStairRenderer;
import ilog.views.chart.util.IlvDoubleArray;
import ilog.views.util.IlvLocaleUtil;
import ilog.views.util.time.IlvCalendarFactory;
import shared.AbstractChartExample;
import stock.indicator.BollingerBandsIndicator;
import stock.indicator.MACDIndicator;
import stock.indicator.MovingAverageIndicator;
import stock.indicator.PriceChannelIndicator;
import stock.indicator.RSIIndicator;
import stock.indicator.StochasticIndicator;
import stock.indicator.TechnicalIndicator;
import stock.indicator.VolumeIndicator;
import stock.indicator.WilliamsRIndicator;

/**
 * A simple application that displays stock information. The sample loads quotes
 * from the Yahoo finance Web pages and displays them in two distinct charts on
 * which you can perform interactions. The displayed information is the stock
 * prices and the exchanged volume. The data is retrieved by sending a formatted
 * <i>cgi query</i> to the Yahoo Web site, and the result is decoded using a
 * custom <code>IlvDataReader</code> object. This reader (an instance of
 * <code>CSVDataReader</code>) extracts the data from the input stream and
 * initializes the data source with the corresponding data sets.
 */
public class StockDemo extends AbstractChartExample implements ActionListener, StockColors {
  // private static final int MAIN_WINDOW_DEFAULT_WIDTH = 600;
  private static final int MAIN_WINDOW_DEFAULT_HEIGHT = 700;

  /** The number of lower charts. */
  private static final int LOWER_CHART_COUNT = 2;

  private static final int RIGHT_MARGIN = 6;

  private static final String HIGHLIGHT_STATUS_KEY = "HIGHLIGHT_STATUS_KEY";
  private static final Font DEFAULT_FONT = new Font("Dialog", Font.PLAIN, 10);

  public static final int LINE = 0;
  public static final int AREA = 1;
  public static final int BAR = 2;
  public static final int STAIR = 3;
  public static final int HLOC = 4;
  public static final int CANDLE = 5;

  public static final String LINE_CMD = "Line_CMD";
  public static final String AREA_CMD = "Area_CMD";
  public static final String BAR_CMD = "Bar_CMD";
  public static final String STAIR_CMD = "Stair_CMD";
  public static final String HLOC_CMD = "HLOC_CMD";
  public static final String CANDLE_CMD = "Candle_CMD";
  public static final String LOCALZOOM_CMD = "LocalZoom_CMD";
  public static final String THRESHOLD_CMD = "Threshold_CMD";

  /**
   * The threshold beyond which quotes are displayed with an overview
   * representation mode. This constant is equal to the approximate duration of
   * 3 months for daily data and one year for weekly data.
   */
  public static final double OVERVIEW_THRESHOLD = 65;

  static {
    List<Color> colorList = IlvColor.getDefaultColors();
    colorList.clear();
    colorList.addAll(Arrays.asList(DEFAULT_COLORS));
  }

  // == Chart related data members
  StockDataSource stockDS;
  IlvChart mainChart;
  IlvChart[] lowerCharts;

  List<TechnicalIndicator> upperIndicators = new ArrayList<TechnicalIndicator>();
  TechnicalIndicator[] lowerIndicators;

  /** The overview renderer for the primary symbol. */
  IlvChartRenderer overviewRenderer;
  /** The detailed renderer for the primary symbol. */
  IlvChartRenderer detailedRenderer;
  /** The renderer for the seconary symbols. */
  IlvChartRenderer secondaryRenderer;

  /**
   * Stores all the interactors that should be set on the upper chart. The first
   * array contains the default interactors, while the second array holds the
   * interactors when the local zoom is active.
   */
  IlvChartInteractor[][] upperInteractors = new IlvChartInteractor[2][];
  IlvChartInteractor[][][] lowerInteractors = new IlvChartInteractor[2][][];

  AxisZoomHistory zoomHistory = new AxisZoomHistory();
  Highlighter highlighter;
  boolean antiAliasing = false;

  // == UI related data members
  private StockQueryPanel queryPanel;
  private JButton refreshButton;
  private JToggleButton thresholdButton;
  private IlvChartAction backAction;
  private IlvChartAction fwdAction;
  private IlvChartAction fitAction;
  private LocalZoomAction zoomInAction;
  private LocalZoomAction zoomOutAction;
  private List<IndicatorAction> upperIndicatorActions;
  private List<IndicatorAction>[] lowerIndicatorActions;

  private ThresholdLines threshold;
  private ChartMessage loadingMessage;

  private NumberFormat numFmt = new DecimalFormat(".###");
  private DateFormat dateFmt = DateFormat.getDateInstance(DateFormat.SHORT);
  private JFrame topFrame;
  private JToolBar toolBar;
  private QuoteDisplayPanel quoteDisplay;
  private PercentDisplayer percentDisplayer;

  /**
   * Initializes a new <code>TableModelDemo</code> object.
   **/
  Override
  public void init(Container container) {
    super.init(container);
    container.setLayout(new BorderLayout());
    container.setBackground(BACKGROUND);

    numFmt = NumberFormat.getNumberInstance();
    numFmt.setMaximumFractionDigits(3);

    // Create the stock data source to read the result of the query.
    URL defaultDataURL = null;

    try {
      defaultDataURL = getClass().getResource("default.csv");
    } catch (Exception x) {
      x.printStackTrace();
    }
    stockDS = new StockDataSource(defaultDataURL);

    // == Create the main chart.
    mainChart = createMainChart();
    percentDisplayer = new PercentDisplayer();
    setOverviewRepresentation(LINE);
    setDetailedRepresentation(CANDLE);
    secondaryRenderer = new IlvPolylineChartRenderer();
    mainChart.addRenderer(secondaryRenderer);

    // == Create indicator charts.
    lowerCharts = createLowerCharts();
    lowerIndicators = new TechnicalIndicator[lowerCharts.length];

    // == Create the interactors.
    createInteractors();

    // == Create and add the Query and Config panels.
    queryPanel = new StockQueryPanel();
    queryPanel.addActionListener(this);

    JPanel top = new JPanel(new BorderLayout());
    toolBar = createToolBar();
    top.add(toolBar, BorderLayout.NORTH);
    top.add(queryPanel, BorderLayout.CENTER);
    container.add(top, BorderLayout.NORTH);
    top.getRootPane().setDefaultButton((JButton) toolBar.getComponentAtIndex(0));

    // Install interactors
    installDefaultInteractors();

    int count = lowerCharts.length;
    JPanel lowerChartsPanel = new JPanel(new GridLayout(count, 1, 0, 4));
    lowerChartsPanel.setOpaque(false);
    lowerChartsPanel.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0));
    for (int i = 0; i < count; ++i) {
      lowerChartsPanel.add(lowerCharts[i]);
    }

    final JSplitPane center = new JSplitPane(JSplitPane.VERTICAL_SPLIT, mainChart, lowerChartsPanel);
    center.setDividerLocation(MAIN_WINDOW_DEFAULT_HEIGHT / 2 - 50);
    center.setDividerSize(2);
    center.setOpaque(false);
    center.setBorder(BorderFactory.createEmptyBorder(6, 0, 0, 0));
    container.add(center, BorderLayout.CENTER);

    // Create and install the menu bar.
    setMenuBar(createMenuBar());

    setAntiAliasing(true);

    // Load the default data.
    loadDefaultData();
  }

  /**
   * Returns the data source that holds the stock data.
   */
  public StockDataSource getStockDataSource() {
    return stockDS;
  }

  // --------------------------------------------------------------------------
  // - Indicators
  /** Sets a lower indicator. */
  protected void setLowerIndicator(int idx, TechnicalIndicator indicator) {
    if (lowerIndicators[idx] == indicator)
      return;
    if (lowerIndicators[idx] != null)
      lowerIndicators[idx].detach();
    if (indicator != null)
      indicator.attach(lowerCharts[idx]);
    lowerIndicators[idx] = indicator;
  }

  /** Adds an upper indicator. */
  protected void addUpperIndicator(TechnicalIndicator indicator) {
    if (indicator != null)
      indicator.attach(mainChart);
    upperIndicators.add(indicator);
  }

  /** Removes an upper indicator. */
  protected void removeUpperIndicator(TechnicalIndicator indicator) {
    if (!upperIndicators.contains(indicator))
      return;
    if (indicator != null)
      indicator.detach();
    upperIndicators.remove(indicator);
  }

  /**
   * Creates the highlight component. This component will be set in the header
   * of the specified chart.
   */
  protected JComponent createHighlightComponent(IlvChart chart, JComponent leftComponent, int highlightStatusWidth) {
    JPanel panel = new JPanel();
    panel.setBorder(BorderFactory.createEmptyBorder(4, 0, 4, RIGHT_MARGIN));
    panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
    panel.setBackground(HEADER_COLOR);

    panel.add(leftComponent);
    JLabel status = new JLabel(" ", JLabel.RIGHT);
    status.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 4));
    status.setFont(DEFAULT_FONT);

    Dimension size = status.getPreferredSize();
    size.width = highlightStatusWidth;
    status.setPreferredSize(size);
    status.setMinimumSize(size);
    status.setMaximumSize(size);
    status.setForeground(FOREGROUND);
    status.setBackground(BACKGROUND);
    status.setOpaque(true);
    chart.putClientProperty(HIGHLIGHT_STATUS_KEY, status);
    panel.add(status);

    return panel;
  }

  /**
   * Creates the main chart. This chart displays the stock data and the upper
   * indicators.
   */
  protected IlvChart createMainChart() {
    IlvChart chart = createChart();
    chart.setBorder(BorderFactory.createEmptyBorder(0, 0, 4, 0));
    chart.getXAxis().setVisibleRange(0, 10);
    chart.getYScale(0).setTitle("Share price ($)", 270);
    chart.getYScale(0).getTitleRenderer().setFont(new Font("Dialog", Font.PLAIN, 12));
    Stripes stripes = new Stripes(chart.getYScale(0), STRIPES_COLOR);
    stripes.setDrawOrder(-3);
    chart.addDecoration(stripes);
    chart.getYAxis(0).setVisibleRange(0, 100);

    chart.getXScale().setMajorTickVisible(false);
    chart.getXScale().setVisible(false);

    // Add an axis listener to toggle the visibility of the
    // quote renderers/quote overview.
    chart.getXAxis().addAxisListener(new AxisListener() {
      double oldZoomfactor;
      double oldLen = 0;
      boolean oldAntiAliasing;

      Override
      public void axisRangeChanged(AxisRangeEvent evt) {
        if (evt.isChangedEvent() && !evt.isAdjusting() && evt.isVisibleRangeEvent()) {
          updateDisplay(evt.getAxis());
        } else if (evt.isVisibleRangeEvent() && !evt.isChangedEvent()) {
          double min = evt.getNewMin();
          double max = evt.getNewMax();
          if (max - min < StockUtil.MIN_XLEN) {
            double mid = (max + min) / 2;
            evt.setNewMax(mid + StockUtil.MIN_XLEN / 2);
            evt.setNewMin(mid - StockUtil.MIN_XLEN / 2);
          }
        }
      }

      Override
      public void axisChanged(AxisChangeEvent evt) {
        if (evt.getType() == AxisChangeEvent.ADJUSTMENT_CHANGE) {
          if (evt.isAdjusting()) {
            if (StockDemo.this.antiAliasing) {
              StockDemo.this.setAntiAliasing(false);
              oldAntiAliasing = true;
            }
          } else {
            if (oldAntiAliasing) {
              StockDemo.this.setAntiAliasing(true);
              oldAntiAliasing = false;
            }
            updateDisplay(evt.getAxis());
          }
        } else if (evt.getType() == AxisChangeEvent.TRANSFORMER_CHANGE) {
          LocalZoomHandler lzh = LocalZoomHandler.get(mainChart, -1);
          if (lzh == null)
            return;
          if (oldZoomfactor != lzh.getTransformer().getZoomFactor()) {
            zoomInAction.computeEnabled();
            zoomOutAction.computeEnabled();
            oldZoomfactor = lzh.getTransformer().getZoomFactor();
          }
        }
      }

      private void updateDisplay(IlvAxis axis) {
        // Check if the zoom level has changed and call the
        // xRangeChanged() method.
        IlvDataInterval xRange = mainChart.getXAxis().getVisibleRange();
        double newLen = xRange.getLength();
        boolean zoom = (oldLen == 0) || (Math.abs(newLen - oldLen) / oldLen > .01);
        StockDemo.this.xRangeChanged(zoom);
        oldLen = newLen;
      }
    });

    // == Add the quote display panel and the legend as a header.
    JPanel header = new JPanel(new GridLayout(0, 1, 0, 4));
    header.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 8));
    header.setBackground(HEADER_COLOR);
    quoteDisplay = new QuoteDisplayPanel();
    header.add(quoteDisplay);
    IlvLegend legend = createLegend();
    chart.setLegend(legend);
    header.add(legend);
    chart.setHeader(header);
    return chart;
  }

  /**
   * Handles the display of percentage variation on a secondary y-scale.
   */
  class PercentDisplayer implements AxisListener, DataSourceListener {

    /** The transform that maps stock prices into percentage variation. */
    IlvAffineAxisTransformer transform = new IlvAffineAxisTransformer(1., 0);
    boolean active = false;
    boolean visible = false;
    IlvScale percentScale;
    IlvGrid percentGrid;

    PercentDisplayer() {

      // Add an y-axis
      IlvAxis yAxis = mainChart.addYAxis(false, false);
      yAxis.setTransformer(transform);
      yAxis.synchronizeWith(mainChart.getYAxis(0), true);

      // A scale with custom label formatting
      percentScale = new IlvScale() {
        Override
        public String computeLabel(double v) {
          String res = super.computeLabel(v);
          return (getPercentVariation(v) > 0) ? ('+' + res + '%') : (res + '%');
        }
      };
      percentScale.setMajorTickVisible(false);
      percentScale.setStepUnit(null, new Double(0));

      // A grid that draws a single grid line at 0% variation.
      percentGrid = new IlvGrid() {
        Override
        public void draw(Graphics g) {
          if (getChart() == null)
            return;
          IlvAxis axis = getAxis();
          if (axis.getTVisibleRange().isInside(0)) {
            try {
              IlvDoubleArray values = new IlvDoubleArray();
              values.add(transform.inverse(0));
              draw(g, values, true);
            } catch (IlvAxisTransformerException x) {
            }
          }
        }
      };
      Stroke stroke = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f,
          new float[] { 10.f, 6.f }, 0.0f);
      percentGrid.setMajorStroke(stroke);
      percentGrid.setMajorPaint(Color.white);
      setActive(true);
    }

    /**
     * Returns the percent variation corresponding to the specified stock price.
     */
    public double getPercentVariation(double price) {
      try {
        return transform.apply(price);
      } catch (IlvAxisTransformerException x) {
        return 0;
      }
    }

    Override
    public void axisRangeChanged(AxisRangeEvent evt) {
      if (evt.isChangedEvent() && evt.isVisibleRangeEvent())
        update();
    }

    Override
    public void axisChanged(AxisChangeEvent evt) {
    }

    Override
    public void dataSourceChanged(DataSourceEvent evt) {
      update();
    }

    private void update() {
      IlvDataSet closeDS = getStockDataSource().getCloseDataSet();
      if (closeDS != null) {
        int firstIdx = (int) Math.ceil(mainChart.getXAxis().getVisibleMin());
        if (firstIdx < closeDS.getDataCount()) {
          double v = closeDS.getYData(firstIdx);
          transform.setDefinition(100. / v, -100.);
          setVisible(true);
          return;
        }
      }
      transform.setDefinition(1., 0.);
      setVisible(false);
    }

    /**
     * Shhows or hide the percentage variation.
     */
    public void setVisible(boolean visible) {
      if (this.visible == visible)
        return;
      this.visible = visible;
      if (visible) {
        mainChart.getChartArea().setMargins(null);
        mainChart.setYScale(1, percentScale);
        mainChart.setYGrid(1, percentGrid);
      } else {
        mainChart.getChartArea().setRightMargin(RIGHT_MARGIN);
        mainChart.setYScale(1, null);
        mainChart.setYGrid(1, null);
      }
    }

    /**
     * Activates or desactivates the display of percentage variation.
     */
    public void setActive(boolean active) {
      if (this.active == active)
        return;
      this.active = active;
      if (active) {
        update();
        mainChart.getXAxis().addAxisListener(this);
        getStockDataSource().addDataSourceListener(this);
        setVisible(true);
      } else {
        setVisible(false);
        mainChart.getXAxis().removeAxisListener(this);
        getStockDataSource().removeDataSourceListener(this);
      }
    }
  }

  /** Creates the charts displaying lower indicators. */
  protected IlvChart[] createLowerCharts() {
    IlvChart[] charts = new IlvChart[LOWER_CHART_COUNT];
    for (int i = 0; i < LOWER_CHART_COUNT; ++i) {
      charts[i] = createLowerChart();
    }
    return charts;
  }

  /** Creates a lower chart. */
  private IlvChart createLowerChart() {
    IlvChart chart = createChart();
    chart.synchronizeAxis(mainChart, IlvAxis.X_AXIS, true);
    chart.setXScale(null);
    chart.setXGrid(new SharedGrid(mainChart.getXScale(), GRID_COLOR));
    IlvLegend legend = createLegend();
    chart.setLegend(legend);
    chart.setHeader(createHighlightComponent(chart, legend, 96));
    return chart;

  }

  /** Creates the list of upper indicators. */
  protected List<TechnicalIndicator> createUpperIndicators() {
    List<TechnicalIndicator> list = new ArrayList<TechnicalIndicator>();
    list.add(new BollingerBandsIndicator(stockDS));
    list.add(new PriceChannelIndicator(stockDS));
    list.add(MovingAverageIndicator.createSMA(stockDS, 20));
    list.add(MovingAverageIndicator.createSMA(stockDS, 40));
    list.add(MovingAverageIndicator.createEMA(stockDS, 20));
    list.add(MovingAverageIndicator.createEMA(stockDS, 40));
    return list;
  }

  /** Creates the list of lower indicators. */
  protected List<TechnicalIndicator> createLowerIndicators() {
    List<TechnicalIndicator> list = new ArrayList<TechnicalIndicator>();
    list.add(new VolumeIndicator(stockDS));
    list.add(new RSIIndicator(stockDS, 14));
    list.add(MACDIndicator.createMACD(stockDS));
    list.add(new WilliamsRIndicator(stockDS, 14));
    list.add(StochasticIndicator.createFastStochastic(stockDS));
    list.add(StochasticIndicator.createSlowStochastic(stockDS));
    return list;

  }

  /** Initializes the actions associated with indicators. */
  SuppressWarnings("unchecked")
  private void initIndicatorActions() {
    List<TechnicalIndicator> list = createUpperIndicators();
    Iterator<TechnicalIndicator> ite = list.iterator();
    upperIndicatorActions = new ArrayList<IndicatorAction>(list.size());
    while (ite.hasNext()) {
      TechnicalIndicator indicator = ite.next();
      UpperIndicatorAction action = new UpperIndicatorAction(indicator);
      action.updateEnabled();
      upperIndicatorActions.add(action);
    }

    lowerIndicatorActions = new ArrayList[lowerCharts.length];
    for (int i = 0; i < lowerIndicatorActions.length; ++i) {
      lowerIndicatorActions[i] = new ArrayList<IndicatorAction>();
      ite = createLowerIndicators().iterator();
      while (ite.hasNext()) {
        TechnicalIndicator indicator = ite.next();
        LowerIndicatorAction action = new LowerIndicatorAction(indicator, i);
        // action.updateEnabled();
        lowerIndicatorActions[i].add(action);
      }
    }
  }

  // --------------------------------------------------------------------------
  // - Menus & Toolbar

  /** Creates the toolbar. */
  Override
  protected JToolBar createToolBar() {
    JToolBar toolbar = new JToolBar();
    toolbar.setFloatable(false);
    populateToolBar(toolbar);
    return toolbar;
  }

  /** Creates the menu bar. */
  protected JMenuBar createMenuBar() {
    JMenuBar menu = new JMenuBar();
    menu.add(createDisplayMenu());
    menu.add(createIndicatorsMenu());
    return menu;
  }

  private JMenuItem getOverviewMenu() {
    return getMenuBar().getMenu(0).getItem(1);
  }

  private JMenuItem getDetailedMenu() {
    return getMenuBar().getMenu(0).getItem(0);
  }

  /** Creates the menu for display settings. */
  protected JMenu createDisplayMenu() {
    JMenu menu = new JMenu("Display");
    JMenuItem item = null;

    JMenu subMenu = new JMenu("Overview Representation");
    menu.add(subMenu);
    ButtonGroup group = new ButtonGroup();
    item = new JRadioButtonMenuItem("Line", true);
    item.setActionCommand(LINE_CMD);
    group.add(item);
    item.addActionListener(this);

    subMenu.add(item);
    item = new JRadioButtonMenuItem("Area");
    item.setActionCommand(AREA_CMD);
    group.add(item);
    item.addActionListener(this);
    subMenu.add(item);

    item = new JRadioButtonMenuItem("Stair");
    item.setActionCommand(STAIR_CMD);
    group.add(item);
    item.addActionListener(this);
    subMenu.add(item);

    item = new JRadioButtonMenuItem("Bar");
    item.setActionCommand(BAR_CMD);
    group.add(item);
    item.addActionListener(this);
    subMenu.add(item);

    subMenu = new JMenu("Detailed Representation");
    menu.add(subMenu);
    group = new ButtonGroup();
    item = new JRadioButtonMenuItem("Candle", true);
    item.setActionCommand(CANDLE_CMD);
    group.add(item);
    item.addActionListener(this);

    subMenu.add(item);
    item = new JRadioButtonMenuItem("HLOC");
    item.setActionCommand(HLOC_CMD);
    group.add(item);
    item.addActionListener(this);
    subMenu.add(item);

    menu.addSeparator();
    item = new JCheckBoxMenuItem("Anti-aliasing", true);
    item.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent evt) {
        setAntiAliasing(((JCheckBoxMenuItem) evt.getSource()).isSelected());
      }
    });
    menu.add(item);
    item = new JCheckBoxMenuItem("Logarithmic Price Axis");
    item.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent evt) {
        boolean log = ((JCheckBoxMenuItem) evt.getSource()).isSelected();
        if (log)
          mainChart.getYAxis(0).setTransformer(new ExponentAxisTransformer(.5));
        else
          mainChart.getYAxis(0).setTransformer(null);
      }
    });
    menu.add(item);

    return menu;
  }

  /** Creates the menu that controls technical indicators. */
  protected JMenu createIndicatorsMenu() {
    // Initialize the actions for setting/removing the indicators.
    initIndicatorActions();

    JMenu menu = new JMenu("Indicators");

    JMenu submenu = createUpperIndicatorMenu();
    menu.add(submenu);

    menu.addSeparator();

    for (int i = 0; i < lowerCharts.length; ++i) {
      submenu = createLowerIndicatorMenu(i);
      menu.add(submenu);
    }

    return menu;
  }

  /** Creates the menu that controls upper indicators. */
  protected JMenu createUpperIndicatorMenu() {
    JMenu menu = new JMenu("Upper Indicators");
    Iterator<IndicatorAction> ite = upperIndicatorActions.iterator();
    while (ite.hasNext()) {
      IndicatorAction action = ite.next();
      JCheckBoxMenuItem item = new JCheckBoxMenuItem(action);
      menu.add(item);
    }
    return menu;
  }

  /** Creates the menu that controls lower indicators. */
  protected JMenu createLowerIndicatorMenu(int idx) {
    String desc = "Lower Indicator" + (idx + 1);
    JMenu menu = new JMenu(desc);
    ButtonGroup group = new ButtonGroup();
    int i = 0;
    Iterator<IndicatorAction> ite = lowerIndicatorActions[idx].iterator();
    while (ite.hasNext()) {
      IndicatorAction action = ite.next();
      JRadioButtonMenuItem item = new JRadioButtonMenuItem(action);
      if (i == idx) {
        setLowerIndicator(idx, action.getIndicator());
        item.setSelected(true);
      }
      group.add(item);
      menu.add(item);
      ++i;
    }

    return menu;
  }

  /**
   * Populates the specified toolbar. This method is called by the
   * <code>createToolBar()</code> method.
   */
  Override
  protected void populateToolBar(JToolBar toolbar) {
    // == Refresh action.
    IlvChartAction refreshAction = new IlvChartAction("Reload", null, null, "Reload Charts", null) {
      Override
      public void actionPerformed(ActionEvent evt) {
        String symbol = queryPanel.getSymbol();
        Date[] range = queryPanel.getQueryRange();
        if (symbol == null || symbol.length() == 0) {
          String msg = "Please enter a ticker symbol.";
          JOptionPane.showMessageDialog(topFrame, msg, "", JOptionPane.WARNING_MESSAGE);
        } else
          loadQuotes(symbol, range[0], range[1], queryPanel.getFrequency());
      }
    };
    refreshAction.setIcon(getClass(), "refresh.gif");
    refreshAction.setChart(mainChart);
    refreshButton = addAction(toolbar, refreshAction);
    Dimension size = refreshButton.getPreferredSize();
    toolbar.addSeparator();

    // == Zoom History navigation.
    backAction = new BackwardAction(zoomHistory);
    backAction.setChart(mainChart);
    addAction(toolbar, backAction);
    fwdAction = new ForwardAction(zoomHistory);
    fwdAction.setChart(mainChart);
    addAction(toolbar, fwdAction);
    fitAction = new IlvChartFitAction("Fit", null, null, "Reset Zoom", null) {
      Override
      public void actionPerformed(ActionEvent evt) {
        StockUtil.performAnimatedZoom(mainChart, 0, mainChart.getXAxis().getDataRange(), StockUtil.ANIMATION_STEPS);
        super.actionPerformed(evt);
        zoomHistory.reset();
        zoomHistory.add(mainChart.getXAxis().getVisibleRange());
      }
    };
    fitAction.setIcon(getClass(), "fit.gif");
    fitAction.setChart(mainChart);
    addAction(toolbar, fitAction);
    toolbar.addSeparator();

    // == Local zoom
    toolbar.add(createToggleButton("lzoom.gif", "lzoom_sel.gif", size, LOCALZOOM_CMD, "Local Zoom"));

    zoomInAction = new LocalZoomAction(2.d, false);
    zoomInAction.setChart(mainChart);
    addAction(toolbar, zoomInAction);
    zoomOutAction = new LocalZoomAction(2.f, true);
    zoomOutAction.setChart(mainChart);
    addAction(toolbar, zoomOutAction);
    toolbar.addSeparator();

    // == Price threshold
    thresholdButton = createToggleButton("threshold.gif", "threshold_sel.gif", size, THRESHOLD_CMD, "Threshold Lines");
    thresholdButton.setSelected(true);
    toolbar.add(thresholdButton);

    toolbar.setBorder(BorderFactory.createEtchedBorder());
  }

  // --------------------------------------------------------------------------
  /**
   * Handles <code>ActionEvent</code> events that occur on the registered
   * listeners.
   */
  Override
  public void actionPerformed(ActionEvent evt) {
    if (evt.getActionCommand().equals(StockQueryPanel.RELOAD_CMD)) {
      String symbol = queryPanel.getSymbol();
      if (symbol == null || symbol.length() == 0)
        return;
      Date[] range = queryPanel.getQueryRange();
      loadQuotes(symbol, range[0], range[1], queryPanel.getFrequency());
    } else if (evt.getActionCommand().equals(LINE_CMD)) {
      setOverviewRepresentation(LINE);
    } else if (evt.getActionCommand().equals(AREA_CMD)) {
      setOverviewRepresentation(AREA);
    } else if (evt.getActionCommand().equals(STAIR_CMD)) {
      setOverviewRepresentation(STAIR);
    } else if (evt.getActionCommand().equals(BAR_CMD)) {
      setOverviewRepresentation(BAR);
    } else if (evt.getActionCommand().equals(HLOC_CMD)) {
      setDetailedRepresentation(HLOC);
    } else if (evt.getActionCommand().equals(CANDLE_CMD)) {
      setDetailedRepresentation(CANDLE);
    } else if (evt.getActionCommand().equals(LOCALZOOM_CMD)) {
      setLocalZoom(((JToggleButton) evt.getSource()).isSelected());
    } else if (evt.getActionCommand().equals(THRESHOLD_CMD)) {
      setThreshold(((JToggleButton) evt.getSource()).isSelected());
    }
  }

  /** Toggles the local zoom. */
  private void setLocalZoom(boolean set) {
    if (set) {
      double middle = mainChart.getXAxis().getVisibleRange().getMiddle();
      double length = mainChart.getXAxis().getVisibleRange().getLength();
      IlvLocalZoomAxisTransformer t = new IlvLocalZoomAxisTransformer(middle - length / 30, middle + length / 30, 3,
          false);
      mainChart.getXAxis().setTransformer(t);
      LocalZoomHandler lzh = LocalZoomHandler.set(mainChart, lowerCharts, t);
      lzh.setStartAnnotation(null);
      lzh.setEndAnnotation(null);
      mainChart.getXScale().setSkipLabelMode(IlvScale.ADAPTIVE_SKIP);
      installLocalInteractors();
    } else {
      LocalZoomHandler.unset(mainChart, -1);
      mainChart.getXScale().setSkipLabelMode(IlvScale.CONSTANT_SKIP);
      installDefaultInteractors();
    }
  }

  /** Toggles the price threshold. */
  private synchronized void setThreshold(boolean set) {
    int idx = toolBar.getComponentIndex(thresholdButton);
    if (set) {
      if (threshold == null) {
        Color upperColor = new Color(120, 220, 120);
        Color midColor = new Color(233, 150, 122);
        Color lowerColor = IlvColor.indianRed;
        threshold = new ThresholdLines(FOREGROUND, BACKGROUND, lowerColor, midColor, upperColor);
      }
      ThresholdLines.set(threshold, mainChart, 0);
      toolBar.add(threshold.getRangeSlider(), idx + 1);
      overviewRenderer.setRenderingHint(threshold);
      detailedRenderer.setRenderingHint(threshold);
    } else {
      ThresholdLines threshold = ThresholdLines.remove(mainChart, 0);
      if (threshold != null)
        toolBar.remove(threshold.getRangeSlider());
      overviewRenderer.setRenderingHint(null);
      detailedRenderer.setRenderingHint(null);
    }
    toolBar.revalidate();
    toolBar.repaint();
  }

  /** Installs the interactors when the local zoom is active. */
  private void installLocalInteractors() {
    fitAction.setChart(null);
    backAction.setChart(null);
    fwdAction.setChart(null);
    zoomInAction.setChart(mainChart);
    zoomOutAction.setChart(mainChart);
    mainChart.setInteractors(upperInteractors[1]);
    for (int i = 0; i < lowerCharts.length; ++i) {
      lowerCharts[i].setInteractors(lowerInteractors[1][i]);
    }
  }

  /** Installs the default interactors. */
  private void installDefaultInteractors() {
    fitAction.setChart(mainChart);
    backAction.setChart(mainChart);
    fwdAction.setChart(mainChart);
    zoomInAction.setChart(null);
    zoomOutAction.setChart(null);
    mainChart.setInteractors(upperInteractors[0]);
    for (int i = 0; i < lowerCharts.length; ++i) {
      lowerCharts[i].setInteractors(lowerInteractors[0][i]);
    }
  }

  /** Toggles anti-aliasing for the charts. */
  public void setAntiAliasing(boolean b) {
    if (antiAliasing == b)
      return;
    mainChart.setAntiAliasing(b);
    mainChart.getChartArea().repaint();
    for (int i = 0; i < lowerCharts.length; ++i) {
      lowerCharts[i].setAntiAliasing(b);
      lowerCharts[i].getChartArea().repaint();
    }
    antiAliasing = b;
  }

  /** Invoked before data is loaded. */
  protected void startLoading() {
    mainChart.getCoordinateSystem(0).getVisibleWindow();
    if (loadingMessage == null) {
      IlvLabelRenderer label = new IlvLabelRenderer();
      label.setFont(new Font("Dialog", Font.BOLD, 18));
      label.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(FOREGROUND),
          BorderFactory.createEmptyBorder(8, 8, 8, 8)));
      label.setOpaque(true);
      label.setBackground(IlvColor.setAlpha(BACKGROUND, .8f));
      loadingMessage = new ChartMessage("LOADING ...", label);
    }
    mainChart.addChartDrawListener(loadingMessage);
    mainChart.getChartArea().repaint();
    refreshButton.setEnabled(false);
  }

  /**
   * Invoked when data has been loaded for both primary and secondary symbols.
   */
  protected void endLoading() {
    if (loadingMessage != null)
      mainChart.removeChartDrawListener(loadingMessage);
    mainChart.getChartArea().repaint();
    refreshButton.setEnabled(true);
  }

  /** Loads the quotes. */
  private void loadQuotes(final String symbol, Date startDate, Date endDate, int frequency) {
    highlighter.unhighlight();
    startLoading();

    // == Reset all data
    overviewRenderer.getDataSource().setDataSets(null);
    detailedRenderer.getDataSource().setDataSets(null);
    secondaryRenderer.getDataSource().setDataSets(null);

    // == Workaround BUG IlvHiLoChartRenderer (setDataSets(null))
    int mode = ((IlvHiLoChartRenderer) detailedRenderer).getMode();
    if (mode == IlvHiLoChartRenderer.CANDLE)
      setDetailedRepresentation(CANDLE);
    else
      setDetailedRepresentation(HLOC);
    // == End Workaround

    stockDS.reset();
    updateIndicators();
    StockDataSource.LoadHook afterLoad = new StockDataSource.LoadHook() {
      Override
      public void dataLoaded(StockDataSource stockDS) {
        primaryDataLoaded(symbol);
      }

      Override
      public void dataLoaded(IlvDoubleArray[] data) {
      }
    };
    stockDS.loadData(symbol, startDate, endDate, frequency, afterLoad);
  }

  private void loadDefaultData() {
    stockDS.loadDefaultData();
    primaryDataLoaded(StockDataSource.DEFAULT_SYMBOL);
    setThreshold(true);
    threshold.setThresholds(new IlvDataInterval(25, 45));
  }

  private void primaryDataLoaded(String expectedSymbol) {
    if (stockDS.getDataSetCount() > 0) {
      String symbol = stockDS.getSymbol().toUpperCase();
      if (!symbol.equalsIgnoreCase(expectedSymbol)) {
        String msg = "Primary symbol [" + expectedSymbol + "] not found. Default data loaded";
        JOptionPane.showMessageDialog(topFrame, msg, "Loading Error", JOptionPane.ERROR_MESSAGE);
      }

      // == Update overview Renderer
      overviewRenderer.getDataSource().addDataSet(stockDS.getCloseDataSet());
      overviewRenderer.setName(symbol);

      // == Update detailed renderer
      IlvDataSet[] quotes = { stockDS.getHighDataSet(), // high
          stockDS.getLowDataSet(), // low
          stockDS.getOpenDataSet(), // open
          stockDS.getCloseDataSet() // close
      };
      detailedRenderer.getDataSource().setDataSets(quotes);
      detailedRenderer.setName(symbol);
      Color c = PRIMARY_COLOR;
      IlvStyle style1 = IlvStyle.createStroked(c);
      IlvStyle style2 = new IlvStyle(c, IlvColor.brighter(c));
      IlvStyle style3 = new IlvStyle(c, IlvColor.darker(c));
      detailedRenderer.setStyles(new IlvStyle[] { style1, style1, style2, style3 });

      // == Load data for secondary symbols
      final String[] secondarySymbols = queryPanel.getSecondarySymbols();
      if (secondarySymbols != null) {
        percentDisplayer.setActive(false);
        StockDataSource.LoadHook afterLoad = new StockDataSource.LoadHook() {
          Override
          public void dataLoaded(StockDataSource stockDS) {
          }

          Override
          public void dataLoaded(IlvDoubleArray[] data) {
            secondaryDataLoaded(secondarySymbols, data);
          }
        };
        stockDS.loadData(secondarySymbols, afterLoad);
      } else {
        percentDisplayer.setActive(true);
        endLoading();
      }
    } else {
      endLoading();
      // Loading can be interrupted,
      // String msg = "Cannot load data.";
      // JOptionPane.showMessageDialog(topFrame, msg, "Loading Error",
      // JOptionPane.ERROR_MESSAGE);
    }

    // checkSorted();
    // == Update indicatore
    updateIndicators();
    // == Reset visible range and history.
    resetVisibleRange();
    zoomHistory.add(mainChart.getXAxis().getVisibleRange());

    // == Update the x-scale
    updateXScale();
    // System.out.println("primaryDataLoaded");
  }

  private void secondaryDataLoaded(String[] symbols, IlvDoubleArray[] data) {
    IlvDefaultDataSource dataSource = new IlvDefaultDataSource();
    if (data != null) { // else loading was interrupted by another request
      // Update the renderer for the secondary symbols.
      String error = null;
      for (int i = 0; i < data.length; ++i) {
        if (data[i] == null) {
          if (error == null)
            error = symbols[i];
          else
            error += ", " + symbols[i];
          continue;
        }
        String name = symbols[i].toUpperCase();
        data[i].trim();
        dataSource.addDataSet(new IlvDefaultDataSet(name, data[i].data()));
      }
      if (error != null) {
        String msg = "The following secondary symbol(s) were not found: " + error;
        JOptionPane.showMessageDialog(topFrame, msg, "Loading Error", JOptionPane.ERROR_MESSAGE);
      }
    }
    secondaryRenderer.setDataSource(dataSource);
    endLoading();
  }

  /**
   * Invoked when quotes are loaded for the primary symbol. This method updates
   * the steps definition of the x-scale.
   */
  private void updateXScale() {
    Date[] dates = stockDS.getDates();
    if (dates == null) {
      mainChart.getXScale().setVisible(false);
      mainChart.getXScale().setStepsDefinition(new IlvDefaultStepsDefinition());
    } else {
      IlvTimeUnit unit = null;
      switch (stockDS.getFrequency()) {
      case StockDataSource.MONTHLY:
        unit = IlvTimeUnit.MONTH;
        break;
      case StockDataSource.WEEKLY:
        unit = IlvTimeUnit.WEEK;
        break;
      case StockDataSource.DAILY:
      default:
        unit = IlvTimeUnit.DAY;
        break;
      }
      CategoryTimeSteps def;
      def = new CategoryTimeSteps(dates, unit);
      mainChart.getXScale().setStepsDefinition(def);
      mainChart.getXScale().setVisible(true);
    }
  }

  /**
   * Invoked when quotes are loaded for the primary symbol. This method updates
   * the indicators and the enabled state of their associated actions
   */
  private void updateIndicators() {
    // Refresh indicators.
    for (int i = 0; i < lowerIndicators.length; ++i) {
      if (lowerIndicators[i] != null)
        lowerIndicators[i].refresh();
    }

    Iterator<TechnicalIndicator> ite = upperIndicators.iterator();
    while (ite.hasNext())
      ite.next().refresh();

    // Update the enabled state of actions.
    Iterator<IndicatorAction> ite2 = upperIndicatorActions.iterator();
    while (ite2.hasNext()) {
      ite2.next().updateEnabled();
    }
  }

  private void resetVisibleRange() {
    mainChart.getXAxis().setAutoVisibleRange(true);
    mainChart.getYAxis(0).setAutoVisibleRange(true);
    zoomHistory.reset();
  }

  /**
   * Creates a legend for the charts.
   */
  protected IlvLegend createLegend() {
    IlvLegend legend = new IlvLegend();
    legend.setLayout(new FlowLayout(FlowLayout.LEFT, 12, 2));
    legend.setPaintingBackground(false);
    legend.setBorder(null);
    legend.setForeground(FOREGROUND);
    legend.setMovable(false);
    legend.setFont(DEFAULT_FONT);
    legend.setSymbolSize(new Dimension(16, 12));
    return legend;
  }

  /**
   * Creates a new <code>IlvChart</code> and sets common properties.
   */
  protected IlvChart createChart() {
    IlvChart chart = new IlvChart();
    chart.getXGrid().setMajorPaint(GRID_COLOR);
    chart.getYGrid(0).setMajorPaint(GRID_COLOR);
    chart.getXGrid().setDrawOrder(-2);
    chart.getYGrid(0).setDrawOrder(-2);
    chart.setForeground(FOREGROUND);
    chart.setFont(DEFAULT_FONT);
    IlvStyle style = chart.getChartArea().getPlotStyle();
    if (style != null)
      chart.getChartArea().setPlotStyle(style.setFillOn(false));
    return chart;
  }

  /**
   * Creates the chart interactors.
   */
  private void createInteractors() {
    // == Upper interactors
    upperInteractors[0] = new IlvChartInteractor[5];

    // Create the interactor that highlights quotes.
    HighlightQuotesInteractor highlightInter = new HighlightQuotesInteractor() {
      Override
      protected boolean isTarget(IlvChartRenderer r) {
        return r == overviewRenderer || r == detailedRenderer || r.getParent() == detailedRenderer;
      }
    };

    // Add a listener to handle highlighting.
    highlighter = new Highlighter();
    highlightInter.addChartInteractionListener(highlighter);
    upperInteractors[0][0] = highlightInter;

    // Add zooming behavior. Subclassed to store the new visible range
    // in the axis zoom history.
    AutoYZoomInteractor autoYZoomInter = new AutoYZoomInteractor() {
      Override
      protected void doIt() {
        IlvDataInterval oldRange = getXAxis().getVisibleRange();
        super.doIt();
        IlvDataInterval newRange = getXAxis().getVisibleRange();
        if (!oldRange.equals(newRange))
          zoomHistory.add(newRange);
      }

    };
    autoYZoomInter.setAnimationStep(StockUtil.ANIMATION_STEPS);
    upperInteractors[0][1] = autoYZoomInter;

    // Add panning behavior
    upperInteractors[0][2] = new IlvChartPanInteractor();
    upperInteractors[0][3] = new IlvChartXScrollInteractor();

    // Add zooming behavior on the scale
    ZoomScaleInteractor zoomScaleInter = new ZoomScaleInteractor(-1) {
      Override
      protected void zoomScale() {
        super.zoomScale();
        IlvDataInterval itv = getXAxis().getVisibleRange();
        zoomHistory.add(itv);
      }
    };
    upperInteractors[0][4] = zoomScaleInter;

    // <code>localInters</code> stores interactors related to local zoom
    // interactions.
    upperInteractors[1] = new IlvChartInteractor[3];
    upperInteractors[1][0] = new IlvChartLocalPanInteractor(0, MouseEvent.BUTTON3_DOWN_MASK);

    upperInteractors[1][1] = new IlvChartLocalReshapeInteractor() {
      // == Workaround limitation (adjusting state not set on the axis).
      Override
      protected void startOperation(MouseEvent evt) {
        super.startOperation(evt);
        mainChart.getXAxis().setAdjusting(true);
      }

      Override
      protected void endOperation(MouseEvent evt) {
        super.endOperation(evt);
        mainChart.getXAxis().setAdjusting(false);
      }

    };
    upperInteractors[1][2] = highlightInter;

    // == Lower interactors
    lowerInteractors[0] = new IlvChartInteractor[lowerCharts.length][];
    lowerInteractors[1] = new IlvChartInteractor[lowerCharts.length][];
    for (int i = 0; i < lowerCharts.length; ++i) {
      lowerInteractors[0][i] = new IlvChartInteractor[] { new IlvChartPanInteractor() };
      lowerInteractors[1][i] = null;
    }
  }

  /**
   * Invoked when the visible time range changes.
   * <p>
   * This method toggles the visibility of the quote overview/quote renderers
   * according to the visible time range.
   */
  private void xRangeChanged(boolean zoom) {
    IlvDataInterval xRange = mainChart.getXAxis().getVisibleRange();
    if (xRange.isEmpty())
      return;

    IlvDataInterval yRange = StockUtil.getYDataRange(mainChart, 0, xRange);

    if (!yRange.isEmpty())
      mainChart.getYAxis(0).setVisibleRange(yRange);

    if (zoom) {
      boolean hideOverview = (xRange.getLength() < OVERVIEW_THRESHOLD);
      mainChart.getYAxis(0).setAutoDataRange(false);
      getDetailedMenu().setEnabled(!hideOverview);
      getOverviewMenu().setEnabled(hideOverview);
      overviewRenderer.setVisible(!hideOverview);
      detailedRenderer.setVisible(hideOverview);
      mainChart.getYAxis(0).setAutoDataRange(true);
      LocalZoomHandler lzh = LocalZoomHandler.get(mainChart, -1);
      if (lzh != null) {
        double middle = xRange.getMiddle();
        double length = xRange.getLength();
        lzh.getTransformer().setZoomRange(middle - length / 30, middle + length / 30);
      }
    }
  }

  /**
   * Specifies the stock overview representation.
   * 
   * @param representation
   *          The desired representation.
   * @see #BAR
   * @see #LINE
   * @see #AREA
   * @see #STAIR
   */
  public void setOverviewRepresentation(int representation) {
    int idx = mainChart.getRenderers().indexOf(overviewRenderer);
    IlvChartRenderer r = null;
    Color c = DEFAULT_COLORS[0];
    // Stroke stroke = new BasicStroke(2.f, BasicStroke.CAP_ROUND,
    // BasicStroke.JOIN_ROUND);
    IlvStyle filledStyle = new IlvStyle(c, IlvColor.setAlpha(c, .45f));
    IlvStyle strokedStyle = new IlvStyle(2.f, c);
    switch (representation) {
    case BAR:
      r = new IlvSingleBarRenderer(filledStyle, 100.);
      break;
    case STAIR:
      r = new IlvSingleStairRenderer(filledStyle);
      break;
    case AREA:
      r = new IlvSingleAreaRenderer(filledStyle);
      break;
    case LINE:
    default:
      r = new IlvSinglePolylineRenderer(strokedStyle);
      break;
    }
    if (idx == -1) {
      mainChart.addRenderer(r);
    } else {
      r.setVisible(overviewRenderer.isVisible());
      r.setName(overviewRenderer.getName());
      mainChart.setRenderer(idx, r);
      // Update y-range explicitly since the setRenderer can modify
      // the x-range before the new renderer is added.
      xRangeChanged(false);
    }
    overviewRenderer = r;
  }

  /**
   * Specifies the stock overview representation.
   * 
   * @param representation
   *          The desired representation.
   * @see #HLOC
   * @see #CANDLE
   */
  public void setDetailedRepresentation(int representation) {
    int idx = mainChart.getRenderers().indexOf(detailedRenderer);
    IlvChartRenderer r = null;
    switch (representation) {
    case HLOC:
      r = new IlvHiLoChartRenderer(IlvHiLoChartRenderer.OPENCLOSE, 0, 80);
      break;
    case CANDLE:
    default:
      r = new IlvHiLoChartRenderer(IlvHiLoChartRenderer.CANDLE, 0, 80);
      break;
    }
    if (idx == -1)
      mainChart.addRenderer(r);
    else {
      r.setVisible(detailedRenderer.isVisible());
      r.setName(detailedRenderer.getName());
      IlvStyle[] styles = detailedRenderer.getStyles();
      mainChart.setRenderer(idx, r);
      r.setStyles(styles);
      xRangeChanged(false);
    }
    detailedRenderer = r;
  }

  /**
   * Returns the default start date for quote queries.
   * 
   * @return One year before the current date.
   */
  static Date getDefaultStartDate() {
    Calendar cal = IlvCalendarFactory.createInstance(IlvLocaleUtil.getCurrentULocale());
    cal.setTime(new Date());
    cal.add(Calendar.YEAR, -1);
    return cal.getTime();
  }

  /** Creates a label with a default font and foreground */
  private static JLabel createLabel(String text) {
    JLabel label = new JLabel(text);
    label.setForeground(FOREGROUND);
    label.setFont(DEFAULT_FONT);
    return label;
  }

  /** Returns the highlight label for the specified chart. */
  private static JLabel getHighlightStatus(IlvChart chart) {
    return (JLabel) chart.getClientProperty(HIGHLIGHT_STATUS_KEY);
  }

  /** Creates a toggle button for the toolbar */
  private JToggleButton createToggleButton(String iconName, String selIconName, Dimension size, String actionCommand,
      String actionDesc) {
    Icon icon = StockUtil.loadIcon(iconName);
    JToggleButton toggle = new JToggleButton(icon);
    icon = StockUtil.loadIcon(selIconName);
    toggle.setSelectedIcon(icon);
    toggle.setPreferredSize(size);
    toggle.setMaximumSize(size);
    toggle.setMinimumSize(size);
    toggle.setActionCommand(actionCommand);
    toggle.setToolTipText(actionDesc);
    toggle.addActionListener(this);
    return toggle;
  }

  /**
   * Adds the specified action to a toolbar and returns the created
   * <code>JButton</code>.
   */
  Override
  protected JButton addAction(JToolBar toolbar, IlvChartAction action) {
    JButton button = super.addAction(toolbar, action);
    button.setPreferredSize(new Dimension(28, 28));
    return button;
  }

  /**
   * Application mainline.
   */
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      Override
      public void run() {
        JFrame topFrame = new JFrame("Rogue Wave JViews Charts Stock Demo");
        StockDemo demo = new StockDemo();
        demo.init(topFrame.getContentPane());
        demo.setFrameGeometry(600, 700, true);
        topFrame.setVisible(true);
      }
    });
  }

  // --------------------------------------------------------------------------
  /**
   * The base action class for setting indicators.
   */
  abstract class IndicatorAction extends AbstractAction {
    private TechnicalIndicator indicator;

    /**
     * Creates a new <code>UpperIndicatorAction</code> that references the
     * specified indicator.
     */
    public IndicatorAction(TechnicalIndicator indicator) {
      super(indicator.getName());
      this.indicator = indicator;
    }

    /**
     * Computes the enable state of the action.
     */
    public void updateEnabled() {
      boolean enabled = (stockDS.getDataSetCount() > 0);
      setEnabled(enabled);
    }

    /**
     * Returns the indicator associated with this action.
     */
    public final TechnicalIndicator getIndicator() {
      return indicator;
    }
  }

  /**
   * An action class used to add upper indicators.
   */
  class UpperIndicatorAction extends IndicatorAction {
    private boolean added;

    /**
     * Creates a new <code>UpperIndicatorAction</code> that references the
     * specified indicator.
     */
    public UpperIndicatorAction(TechnicalIndicator indicator) {
      super(indicator);
    }

    /** Invoked when an action occurs. */
    Override
    public void actionPerformed(ActionEvent evt) {
      if (!isEnabled())
        return;
      if (added) {
        removeUpperIndicator(getIndicator());
        added = false;
      } else {
        addUpperIndicator(getIndicator());
        added = true;
      }
    }
  }

  /**
   * An action class used to set lower indicators.
   */
  class LowerIndicatorAction extends IndicatorAction {
    private int chartIdx;

    /**
     * Creates a new <code>LowerIndicatorAction</code> that references the
     * specified indicator.
     */
    public LowerIndicatorAction(TechnicalIndicator indicator, int chartIdx) {
      super(indicator);
      this.chartIdx = chartIdx;
    }

    /** Invoked when an action occurs. */
    Override
    public void actionPerformed(ActionEvent evt) {
      if (!isEnabled())
        return;
      setLowerIndicator(chartIdx, getIndicator());
    }
  }

  // --------------------------------------------------------------------------
  /**
   * Listens to the events sent by the highlight interactor and performs the
   * highlighting operations.
   */
  private class Highlighter implements ChartInteractionListener {
    private HighlightIndicator upperHighlight;
    private HighlightIndicator[] lowerHighlights;

    public Highlighter() {
      upperHighlight = createHighlightIndicator();
      mainChart.addDecoration(upperHighlight);

      int count = lowerCharts.length;
      lowerHighlights = new HighlightIndicator[count];
      for (int i = 0; i < count; ++i) {
        lowerHighlights[i] = createHighlightIndicator();
        lowerCharts[i].addDecoration(lowerHighlights[i]);
      }
    }

    Override
    public void interactionPerformed(ChartInteractionEvent evt) {
      ChartHighlightInteractionEvent hevt = (ChartHighlightInteractionEvent) evt;
      if (hevt.isHighlighted())
        highlight(hevt.getDisplayPoint());
      else
        unhighlight();
    }

    private void highlight(IlvDisplayPoint hdp) {
      if (detailedRenderer.isVisible()) {
        hdp = detailedRenderer.getDisplayPoint(stockDS.getCloseDataSet(), hdp.getIndex());
        if (hdp == null)
          return;
      }

      upperHighlight.setHighlightedPoint(hdp);
      for (int i = 0; i < lowerHighlights.length; ++i) {
        IlvDisplayPoint idp = lowerIndicators[i].getHighlightedPoint(hdp.getIndex());
        lowerHighlights[i].setHighlightedPoint(idp);
        if (idp == null) {
          lowerHighlights[i].setValue(hdp.getXData());
          if (!lowerHighlights[i].isVisible())
            lowerHighlights[i].setVisible(true);
        } else {
          lowerHighlights[i].setVisible(true);
          JLabel status = getHighlightStatus(lowerCharts[i]);

          if (status != null && idp != null) {
            status.setText(numFmt.format(idp.getYData()));
          }
        }
      }
      quoteDisplay.displayQuote(hdp);
    }

    private void unhighlight() {
      upperHighlight.setHighlightedPoint(null);
      for (int i = 0; i < lowerHighlights.length; ++i) {
        lowerHighlights[i].setHighlightedPoint(null);
        if (lowerHighlights[i].isVisible())
          lowerHighlights[i].setVisible(false);
        JLabel status = getHighlightStatus(lowerCharts[i]);
        if (status != null)
          status.setText("");
      }
      quoteDisplay.displayQuote(null);
    }

    private HighlightIndicator createHighlightIndicator() {
      HighlightIndicator indic = new HighlightIndicator(-1);
      indic.setDrawOrder(2);
      indic.setColor(FOREGROUND);
      indic.setVisible(false);
      return indic;
    }
  }

  /** A panel displaying highlighed quote values. */
  private class QuoteDisplayPanel extends JPanel {
    private final int WIDTH = 62;
    private final int SPACE = 6;
    JLabel date, high, low, open, close;

    public QuoteDisplayPanel() {
      super(new BorderLayout());
      setBorder(BorderFactory.createEmptyBorder(2, 12, 2, 4));
      Box box = Box.createHorizontalBox();
      setOpaque(false);
      box.add(createLabel("DATE"));
      box.add(Box.createHorizontalStrut(SPACE));
      date = new FixedLabel(WIDTH, JLabel.RIGHT);
      box.add(date);
      box.add(Box.createHorizontalGlue());
      box.add(createLabel("OPEN"));
      box.add(Box.createHorizontalStrut(SPACE));
      open = new FixedLabel(WIDTH, JLabel.RIGHT);
      box.add(open);
      box.add(Box.createHorizontalGlue());
      box.add(createLabel("HIGH"));
      box.add(Box.createHorizontalStrut(SPACE));
      high = new FixedLabel(WIDTH, JLabel.RIGHT);
      box.add(high);
      box.add(Box.createHorizontalGlue());
      box.add(createLabel("LOW"));
      box.add(Box.createHorizontalStrut(SPACE));
      low = new FixedLabel(WIDTH, JLabel.RIGHT);
      box.add(low);
      box.add(Box.createHorizontalGlue());
      box.add(createLabel("CLOSE"));
      box.add(Box.createHorizontalStrut(SPACE));
      close = new FixedLabel(WIDTH, JLabel.RIGHT);
      box.add(close);
      box.add(Box.createHorizontalGlue());
      add(box);
    }

    public void displayQuote(IlvDisplayPoint dp) {
      if (stockDS.getDataSetCount() < 4 || dp == null) {
        date.setText(" ");
        open.setText(" ");
        high.setText(" ");
        low.setText(" ");
        close.setText(" ");
      } else {
        int idx = dp.getIndex();
        date.setText(dateFmt.format(stockDS.getDate(idx)));
        double val = stockDS.getOpenDataSet().getYData(idx);
        open.setText(numFmt.format(val));
        val = stockDS.getHighDataSet().getYData(idx);
        high.setText(numFmt.format(val));
        val = stockDS.getLowDataSet().getYData(idx);
        low.setText(numFmt.format(val));
        val = stockDS.getCloseDataSet().getYData(idx);
        close.setText(numFmt.format(val));
      }
    }
  }

}