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

import java.awt.Color;
import java.awt.Font;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.faces.event.ActionEvent;
import javax.faces.event.ValueChangeEvent;
import javax.faces.model.SelectItem;
import javax.swing.BorderFactory;

import ilog.views.chart.IlvChart;
import ilog.views.chart.IlvChartLayout;
import ilog.views.chart.IlvLegend;
import ilog.views.chart.IlvStyle;
import ilog.views.chart.data.IlvDataSource;
import ilog.views.chart.data.IlvFunctionDataSet;
import ilog.views.chart.event.DataSetContentsEvent;
import ilog.views.chart.event.DataSetPropertyEvent;
import ilog.views.chart.faces.dhtml.component.IlvChartDHTMLView;
import ilog.views.chart.faces.servlet.IlvFacesChart;
import ilog.views.chart.faces.servlet.IlvFacesChartLegend;
import ilog.views.chart.graphic.IlvMarkerFactory;
import ilog.views.chart.renderer.IlvPolylineChartRenderer;
import ilog.views.util.IlvResourceUtil;

/**
 * A sample showing how a Polar chart can be used to display mathematical
 * functions.
 */
public class PolarDemo implements Serializable {

  // private static final char RHO = '\u03C1';
  // private static final char THETA = '\u03B8';
  // private static final char DEGREE = '\u00B0';

  /** The default colors for the polylines. */
  static final Color[] COLORS = new Color[] { new Color(152, 251, 152), new Color(235, 235, 0),
      new Color(233, 150, 122), new Color(245, 222, 179), new Color(176, 196, 222), new Color(0, 130, 132),
      new Color(198, 132, 198), new Color(40, 195, 195), new Color(198, 255, 198) };

  static final Color BK_COLOR = new Color(112, 128, 144);

  /**
   * Defines a function.
   */
  interface Function extends Serializable {

    public String getDescription();

    public double eval(double x);
  }

  // == Constants for function definition.
  static final double X_MIN = 0;
  static final double X_MAX = 360;
  static final int DATACOUNT = 91;

  /**
   * The functions used by the sample.
   */
  static final Function[] FUNCTIONS = new Function[] { new Function() {

    Override
    public String getDescription() {
      return "sin(2*x)";
    }

    Override
    public double eval(double x) {
      return Math.sin(2 * Math.toRadians(x));
    }
  }, new Function() {

    Override
    public String getDescription() {
      return "cos(x/2)";
    }

    Override
    public double eval(double x) {
      return Math.cos(Math.toRadians(x) / 2);
    }
  }, new Function() {

    Override
    public String getDescription() {
      return "2*cos(2*x)-3*sin(x/3)";
    }

    Override
    public double eval(double x) {
      return 2 * Math.cos(Math.toRadians(2 * x) - 3 * Math.sin(Math.toRadians(x / 3)));
    }
  }, new Function() {

    Override
    public String getDescription() {
      return "sin(2*x)+cos(x/2)";
    }

    Override
    public double eval(double x) {
      return Math.sin(Math.toRadians(2 * x)) + Math.cos(Math.toRadians(x / 2));
    }
  }, new Function() {

    Override
    public String getDescription() {
      return "sin(x/2)+cos(2*x)";
    }

    Override
    public double eval(double x) {
      return Math.sin(Math.toRadians(x / 2)) + Math.cos(Math.toRadians(2 * x));
    }
  }, new Function() {

    Override
    public String getDescription() {
      return "sqrt(x)*cos(x)";
    }

    Override
    public double eval(double x) {
      return Math.sqrt(x) * Math.cos(Math.toRadians(x));
    }
  }, new Function() {

    Override
    public String getDescription() {
      return "sin(x)+cos(x)";
    }

    Override
    public double eval(double x) {
      return Math.sin(Math.toRadians(x)) + Math.cos(Math.toRadians(x));
    }
  }, new Function() {

    Override
    public String getDescription() {
      return "Leaf";
    }

    Override
    public double eval(double x) {
      double a = Math.toRadians(x);
      double scale = 100 / (100 + Math.pow(0.5 * (a - Math.PI), 8));
      scale *= 2 - Math.sin(3.5 * a) - 0.5 * Math.cos(15 * a);
      return scale * Math.sin(0.5 * a);
    }
  } };

  private IlvDataSource dataSource;
  private boolean addDataSet = true;

  /**
   * An <code>IlvFunctionDataSet</code> that references a <code>Function</code>
   * object.
   */
  class FunctionDataSet extends IlvFunctionDataSet {

    Function function;

    /**
     * Initializes a new <code>FunctionDataSet</code>.
     */
    FunctionDataSet(double xMin, double xMax, int dataCount) {
      super(xMin, xMax, dataCount);
    }

    /**
     * Returns the description of the function.
     */
    Override
    public String getName() {
      String name = super.getName();
      if (name == null) {
        name = (function == null) ? "" : function.getDescription();
      }
      return name;
    }

    /**
     * Returns the referenced function.
     */
    public final Function getFunction() {
      return function;
    }

    /**
     * Sets the referenced <code>Function</code> object.
     */
    public void setFunction(Function function) {
      String oldName = getName();
      this.function = function;
      invalidateLimits();
      if (super.getName() == null) {
        fireDataSetPropertyEvent(new DataSetPropertyEvent(this, "name", oldName, function.getDescription()));
      }
      fireDataSetContentsEvent(new DataSetContentsEvent(this));
    }

    /**
     * Calls the function.
     */
    Override
    public double callFunction(double val) {
      return (function == null) ? val : function.eval(val);
    }
  }

  /**
   * Javaserver Faces value event listener. Changes the function to display.
   * 
   * @param evt
   *          The JSF value changed event.
   */
  public void setFunction(ValueChangeEvent evt) {
    int index = Integer.parseInt((String) evt.getNewValue());
    // reset saved visible window
    chartView.setClientState("&xMin=&xMax=&yMin=&yMax=");
    //
    setFunction(index);
  }

  private int lastIndex = 0;

  /**
   * Changes the displayed function.
   */
  private void setFunction(int index) {
    FunctionDataSet ds = null;
    if (addDataSet) {
      ds = new FunctionDataSet(X_MIN, X_MAX, DATACOUNT);
      dataSource.addDataSet(ds);
      addDataSet = false;
    } else {
      ds = (FunctionDataSet) dataSource.getDataSet(dataSource.getDataSetCount() - 1);
    }
    lastIndex = index;
    ds.setFunction(FUNCTIONS[index]);
    zoomChartToFit();
  }

  /**
   * Removed the previously stored plots
   */
  private void reset() {
    dataSource.setDataSets(null);
    addDataSet = true;
  }

  /**
   * The chart component to display in the chart view component.
   */
  private IlvFacesChart chart;

  public PolarDemo() {

    // Create a Polar chart.
    chart = new IlvFacesChart(IlvChart.POLAR);

    // Create a polyline renderer.
    IlvPolylineChartRenderer poly = new IlvPolylineChartRenderer() {

      Override
      public Color[] getDefaultColors() {
        return COLORS;
      }

    };
    poly.setMarker(IlvMarkerFactory.getCircleMarker(), 2);
    this.dataSource = poly.getDataSource();
    setFunction(0);
    chart.addRenderer(poly);

    // Customize the chart and add it to the panel.
    configureChart(chart);
    populateFunctionItems();

    // Sets the chart to display to the faces chart view component.
    chartView.setChart(chart);
  }

  /**
   * Populates the item list with the function available.
   */
  private void populateFunctionItems() {
    functionItems = new ArrayList<SelectItem>();
    for (int i = 0; i < FUNCTIONS.length; i++)
      functionItems.add(new SelectItem("" + i, FUNCTIONS[i].getDescription()));
  }

  /**
   * Customizes the chart appearance. Another way to customize the chart is the
   * CSS usage.
   */
  void configureChart(IlvChart chart) {
    // Customize the chart.
    chart.setAntiAliasing(true);
    chart.setBackground(Color.black);
    chart.setForeground(Color.white);
    chart.setOpaque(true);
    chart.setFont(new Font("Dialog", Font.PLAIN, 12));
    chart.getChartArea().setPlotStyle(new IlvStyle(Color.white, BK_COLOR));
    chart.getChartArea().setBorder(BorderFactory.createLineBorder(Color.gray));

    // Customize the scales and grids.
    chart.getXScale().setAxisVisible(false);
    chart.getXScale().setStepUnit(Double.valueOf(10.), Double.valueOf(0.));
    chart.getYScale(0).setLabelFont(new Font("Dialog", Font.BOLD, 14));
    chart.getYGrid(0).setMinorLineVisible(true);

    // Add a header.
    String header = IlvResourceUtil.getCurrentLocaleString(PolarDemo.class, "chartHeader");
    chart.setHeaderText(header);
    chart.getHeader().setForeground(Color.white);

    // Add a footer
    String footer = IlvResourceUtil.getCurrentLocaleString(PolarDemo.class, "chartFooter");
    chart.setFooterText(footer);
    chart.getFooter().setForeground(Color.white);

    // Add a legend.
    IlvLegend legend = new IlvFacesChartLegend();
    legend.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.gray),
        BorderFactory.createEmptyBorder(4, 4, 4, 4)));
    legend.setFont(new Font("Monospaced", Font.PLAIN, 12));
    chart.addLegend(legend, IlvChartLayout.NORTH_BOTTOM);

    zoomChartToFit();
  }

  /**
   * Zooms to show all the datas of the chart
   */
  private void zoomChartToFit() {
    chart.getXAxis().setVisibleRange(chart.getXAxis().getDataRange());
    for (int i = 0; i < chart.getYAxisCount(); i++) {
      chart.getYAxis(i).setVisibleRange(chart.getYAxis(i).getDataRange());
    }
  }

  /**
   * JavaServer Faces action Store the current function plot.
   * 
   * @param evt
   *          The JSF action event.
   */
  public void addPlot(ActionEvent evt) {
    addDataSet = true;
  }

  /**
   * JavaServer Faces action Removes all the ppreviously stored plots.
   * 
   * @param evt
   *          The JSF action event.
   */
  public void reset(ActionEvent evt) {
    reset();
    setFunction(lastIndex);
  }

  /**
   * The select items representing the available functions to display.
   */
  private List<SelectItem> functionItems;

  /**
   * Returns the function set SelectItems to be displayed in the combo box.
   * 
   * @return The select items representing the available functions to display.
   */
  public List<SelectItem> getFunctionItems() {
    return functionItems;
  }

  /**
   * @param functionItems
   *          The functionItems to set.
   */
  public void setFunctionItems(List<SelectItem> functionItems) {
    this.functionItems = functionItems;
  }

  /**
   * The current value of the combo box. This coombo box offers the user to
   * select a function to print.
   */
  private Object comboValue = null;

  /**
   * Returns the current combo value
   * 
   * @return Returns the comboValue.
   */
  public Object getComboValue() {
    return comboValue;
  }

  /**
   * Sets a current value for the combo box.
   * 
   * @param comboValue
   *          The comboValue to set.
   */
  public void setComboValue(Object comboValue) {
    this.comboValue = comboValue;
  }

  /**
   * JViews Faces Chart View component.
   */
  private IlvChartDHTMLView chartView = new IlvChartDHTMLView();

  /**
   * Returns the faces chart view component.
   * 
   * @return Returns the chartView.
   */
  public IlvChartDHTMLView getChartView() {
    return chartView;
  }

  /**
   * Sets the faces chart view component.
   * 
   * @param chartView
   *          The chartView to set.
   */
  public void setChartView(IlvChartDHTMLView chartView) {
    this.chartView = chartView;
  }
}