// This may look like C code, but it's really -*- C++ -*-
/*
 * Copyright (C) 2008 Emweb bvba, Kessel-Lo, Belgium.
 *
 * See the LICENSE file for terms of use.
 */
#ifndef CHART_WAXIS_H_
#define CHART_WAXIS_H_

#include <Wt/WFont>
#include <Wt/WPen>
#include <Wt/WString>
#include <Wt/WDateTime>
#include <boost/any.hpp>
#include <cfloat>

namespace Wt {
  namespace Chart {

class WCartesianChart;
class WChart2DRenderer;

/*! \brief Enumeration that indicates a chart axis.
 *
 * \sa WCartesianChart::axis(Axis)
 *
 * \ingroup charts
 */
enum Axis {
  XAxis = 0,            //!< X axis.
  YAxis = 1,            //!< First Y axis (== Y1Axis).
  Y1Axis = YAxis,       //!< First Y axis (== YAxis).
  Y2Axis = 2,           //!< Second Y Axis.
  OrdinateAxis = YAxis  //!< Ordinate axis (== Y1Axis for a 2D plot).
  /* Future:
   ,XAxis_3D = 0,
    ZAxis_3D = 1, // = Y
    YAxis_3D = 2
   */
};

/*! \brief Enumeration that indicates a logical location for an axis.
 *
 * The location is dependent on the values of the other axis.
 *
 * \sa WAxis::setLocation(AxisValue)
 *
 * \ingroup charts
 */
enum AxisValue {
  MinimumValue = 0x1,  //!< At the minimum value.
  MaximumValue = 0x2,  //!< At the maximum value.
  ZeroValue = 0x4      //!< At the zero value (if displayed).
};

// for < 3.0.1 compatibility
typedef AxisValue AxisLocation;

W_DECLARE_OPERATORS_FOR_FLAGS(AxisValue)

/*! \brief Enumeration that indicates a scale for an axis.
 *
 * The scale determines how values are mapped onto an axis.
 *
 * \sa WAxis::setScale(AxisScale scale)
 *
 * \ingroup charts
 */
enum AxisScale {
  /*! A category scale is set as the scale for the X axis in a \link
   * Chart::CategoryChart CategoryChart\endlink, and is only
   * applicable there. It lists all values, evenly spaced, and
   * consecutively in the order of the model.
   */
  CategoryScale = 0,

  /*! A linear scale is the default scale for all axes, except for the
   * X scale in a CategoryScale. It maps values in a linear
   * fashion on the axis.
   */
  LinearScale = 1,

  /*! A logarithmic scale is useful for plotting values with of a
   * large range, but only works for positive values.
   */
  LogScale = 2,

  /*! A date scale is a special linear scale, which is useful for the
   * X axis in a ScatterPlot, when the X series contain dates (of type
   * WDate). The dates are converted to numbers, as Julian Days.
   */
  DateScale = 3,

  /*! A datetime scale is a special linear scale, which is useful for
   * the X axis in a ScatterPlot, when the X series contain timedates
   * (of type WDateTime). The dates are converted to numbers, as the
   * number of seconds since the Unix Epoch (midnight Coordinated
   * Universal Time (UTC) of January 1, 1970).
   */
  DateTimeScale = 4
};

/*! \class WAxis Wt/Chart/WAxis Wt/Chart/WAxis
 *  \brief Class which represents an axis of a cartesian chart.
 *
 * A cartesian chart has two or three axes: an X axis (\link
 * Chart::XAxis XAxis\endlink), a Y axis (\link Chart::YAxis
 * YAxis\endlink) and optionally a second Y axis (\link Chart::Y2Axis
 * Y2Axis\endlink). Each of the up to three axes in a cartesian
 * chart has a unique id() that identifies which of these three axes
 * it is in the enclosing chart().
 *
 * Use setVisible() to change the visibility of an axis,
 * setGridLinesEnabled() to show grid lines for an axis. The pen
 * styles for rendering the axis or grid lines may be changed using
 * setPen() and setGridLinesPen(). A margin between the axis and the
 * main plot area may be configured using setMargin().
 *
 * By default, the axis will automatically adjust its range so that
 * all data will be visible. You may manually specify a range using
 * setMinimum(), setMaximum or setRange(). The interval between labels
 * is by default automatically adjusted depending on the axis length
 * and the range, but may be manually specified using
 * setLabelInterval().
 *
 * The axis has support for being "broken", to support displaying data
 * with a few outliers which would otherwise swamp the chart. This is
 * not done automatically, but instead you need to use setBreak() to
 * specify the value range that needs to be omitted from the axis. The
 * omission is rendered in the axis and in bars that cross the break.
 *
 * The labels are shown using a "%.4g" format string for numbers, and
 * a suitable format for \link Chart::DateScale DateScale\endlink or
 * \link Chart::DateTimeScale DateTimeScale\endlink formats, which
 * uses heuristics to choose a suitable format. The format may be
 * customized using setLabelFormat(). The angle of the label text may
 * be changed using setLabelAngle(). By default, all labels are
 * printed horizontally.
 * 
 * \sa WCartesianChart
 *
 * \ingroup charts
 */
class WT_API WAxis
{
public:
  /*! \brief Constant which indicates automatic minimum calculation.
   *
   * \sa setMinimum()
   */
  static const double AUTO_MINIMUM;

  /*! \brief Constant which indicates automatic maximum calculation
   *
   * \sa setMaximum()
   */
  static const double AUTO_MAXIMUM;

  /*! \brief Returns the axis id.
   *
   * \sa chart(), WCartesianChart::axis()
   */
  Axis id() const { return axis_; }

  /*! \brief Sets whether this axis is visible.
   *
   * Changes whether the axis is displayed, including ticks and
   * labels. The rendering of the grid lines is controlled seperately
   * by setGridLinesEnabled().
   *
   * The default value is true for the X axis and first Y axis, but false
   * for the second Y axis.
   *
   * \sa setGridLinesEnabled().
   */
  void setVisible(bool visible);

  /*! \brief Returns whether this axis is visible.
   *
   * \sa setVisible()
   */
  bool isVisible() const { return visible_; }

  /*! \brief Sets the axis location.
   *
   * Configures the location of the axis, relative to values on the
   * other axis (i.e. Y values for the X axis, and X values for the
   * Y axis).
   *
   * The default value is Chart::MinimumValue.
   *
   * \sa location()
   */
  void setLocation(AxisValue value);

  /*! \brief Returns the axis location.
   *
   * \sa setLocation()
   */
  AxisValue location() const { return location_; }

  /*! \brief Sets the scale of the axis.
   *
   * For the X scale in a \link Chart::CategoryChart
   * CategoryChart\endlink, the scale should be left unchanged to
   * \link Chart::CategoryScale CategoryScale\endlink.
   *
   * For all other axes, the default value is \link Chart::LinearScale
   * LinearScale\endlink, but this may be changed to \link
   * Chart::LogScale LogScale\endlink or \link Chart::DateScale
   * DateScale\endlink. \link Chart::DateScale DateScale\endlink is
   * only useful for the X axis in a ScatterPlot which contains WDate
   * values.
   *
   * \sa scale()
   */
  void setScale(AxisScale scale);

  /*! \brief Returns the scale of the axis.
   *
   * \sa setScale()
   */
  AxisScale scale() const { return scale_; }

  /*! \brief Sets the minimum value displayed on the axis.
   *
   * By default, the minimum and maximum values are determined
   * automatically so that all the data can be displayed.
   *
   * The numerical value corresponding to a data point is 
   * defined by it's AxisScale type.
   *
   * \sa setMaximum(), setAutoLimits()
   */
  void setMinimum(double minimum);

  /*! \brief Returns the minimum value displayed on the axis.
   *
   * This returned the minimum value that was set using setMinimum(),
   * or otherwise the automatically calculated (and rounded) minimum.
   *
   * The numerical value corresponding to a data point is defined by
   * it's AxisScale type.
   *
   * \sa setMinimum(), setAutoLimits(), setRoundLimits()
   */
  double minimum() const;

  /*! \brief Sets the maximum value for the axis displayed on the axis.
   *
   * By default, the minimum and maximum values are determined
   * automatically so that all the data can be displayed.
   *
   * The numerical value corresponding to a data point is 
   * defined by it's AxisScale type.
   *
   * \sa setMinimum(), setAutoLimits()
   */
  void setMaximum(double maximum);

  /*! \brief Returns the maximum value displayed on the axis.
   *
   * This returned the maximum value that was set using setMaximum(),
   * or otherwise the automatically calculated (and rounded) maximum.
   *
   * The numerical value corresponding to a data point is defined by
   * it's AxisScale type.
   *
   * \sa setMaximum(), setAutoLimits(), setRoundLimits()
   */
  double maximum() const;

  /*! \brief Sets the axis range (minimum and maximum values) manually.
   *
   * Specifies both minimum and maximum value for the axis. This
   * automatically disables automatic range calculation.
   *
   * The numerical value corresponding to a data point is defined by
   * it's AxisScale type.
   *
   * \sa setMinimum(), setMaximum()
   */
  void setRange(double minimum, double maximum);

  /*! \brief Sets the axis resolution.
   *
   * Specifies the axis resolution, in case maximum-minimum <
   * resolution minimum and maximum are modified so the maximum -
   * minimum = resolution
   *
   * The default resolution is 0, which uses a built-in epsilon.
   *
   * \sa resolution()
   */
  void setResolution(double resolution);

  /*! \brief Returns the axis resolution.
   *
   * \sa setResolution()
   */
  double resolution() const { return resolution_; }

  /*! \brief Let the minimum and/or maximum be calculated from the
   *         data.
   *
   * Using this method, you can indicate that you want to have
   * automatic limits, rather than limits set manually using
   * setMinimum() or setMaximum().
   *
   * \p locations can be Chart::MinimumValue and/or Chart::MaximumValue.
   *
   * The default value is Chart::MinimumValue | Chart::MaximumValue.
   */
  void setAutoLimits(WFlags<AxisValue> locations);

  /*! \brief Returns the limits that are calculated automatically.
   *
   * This returns the limits (Chart::MinimumValue and/or
   * Chart::MaximumValue) that are calculated automatically from the
   * data, rather than being specified manually using setMinimum()
   * and/or setMaximum().
   *
   * \sa setAutoLimits()
   */
  WFlags<AxisValue> autoLimits() const;

  /*! \brief Specifies whether limits should be rounded.
   *
   * When enabling rounding, this has the effect of rounding down the
   * minimum value, or rounding up the maximum value, to the nearest
   * label interval.
   *
   * By default, rounding is enabled for an auto-calculated limited,
   * and disabled for a manually specifed limit.
   *
   * \sa setAutoLimits()
   */
  void setRoundLimits(WFlags<AxisValue> locations);

  /*! \brief Returns whether limits should be rounded.
   *
   * \sa setRoundLimits()
   */
  WFlags<AxisValue> roundLimits() const { return roundLimits_; }

  /*! \brief Specifies a range that needs to be omitted from the axis.
   *
   * This is useful to display data with a few outliers which would
   * otherwise swamp the chart. This is not done automatically, but
   * instead you need to use setBreak() to specify the value range
   * that needs to be omitted from the axis. The omission is rendered
   * in the axis and in BarSeries that cross the break.
   */
  void setBreak(double minimum, double maximum);

  /*! \brief Sets the label interval.
   *
   * Specifies the interval for displaying labels (and ticks) on the axis.
   * The default value is 0.0, and indicates that the interval should be
   * computed automatically.
   *
   * \sa setLabelFormat()
   */
  void setLabelInterval(double labelInterval);

  /*! \brief Returns the label interval.
   *
   * \sa setLabelInterval()
   */
  double labelInterval() const { return labelInterval_; }

  /*! \brief Sets the label format.
   *
   * Sets a format string which is used to format values, both for the
   * axis labels as well as data series values
   * (see WDataSeries::setLabelsEnabled()).
   *
   * For an axis with a \link Chart::LinearScale LinearScale\endlink
   * or \link Chart::LogScale LogScale\endlink scale, the format string
   * must be a format string that is accepted by snprintf() and which
   * formats one double. If the format string is an empty string,
   * "%.4g" is used.
   *
   * For an axis with a \link Chart::DateScale DateScale\endlink
   * scale, the format string must be a format string accepted by
   * WDate::toString(), to format a date. If the format string is an
   * empty string, a suitable format is chosen based on heuristics.
   *
   * For an axis with a
   * \link Chart::DateTimeScale DateTimeScale\endlink scale, the format
   * string must be a format string accepted by WDateTime::toString(),
   * to format a date. If the format string is an empty string, a suitable
   * format is chosen based on heuristics.
   *
   * The default value is an empty string ("").
   *
   * \sa labelFormat()
   */
  void setLabelFormat(const WString& format);

  /*! \brief Returns the label format string.
   *
   * \sa setLabelFormat()
   */
  const WString& labelFormat() const { return labelFormat_; }

  /*! \brief Sets the label angle.
   *
   * Sets the angle used for displaying the labels (in degrees). A 0 angle
   * corresponds to horizontal text. Note that this option is only supported
   * by the InlineSvgVml renderers, but not by HtmlCanvas.
   *
   * The default value is 0.0.
   *
   * \sa labelAngle()
   */
  void setLabelAngle(double angle);

  /*! \brief Returns the label angle.
   *
   * \sa setLabelAngle()
   */
  double labelAngle() const { return labelAngle_; }

  /*! \brief Sets whether gridlines are displayed for this axis.
   *
   * When <i>enabled</i>, gird lines are drawn for each tick on this axis,
   * using the gridLinesPen().
   *
   * Unlike all other visual aspects of an axis, rendering of the
   * gridlines is not controlled by setDisplayEnabled().
   *
   * \sa setGridLinesPen(), isGridLinesEnabled()
   */
  void setGridLinesEnabled(bool enabled);

  /*! \brief Returns whether gridlines are displayed for this axis.
   *
   * \sa setGridLinesEnabled()
   */
  bool isGridLinesEnabled() const { return gridLines_; }

  /*! \brief Changes the pen used for rendering the axis and ticks.
   *
   * The default value is a black pen of 0 width.
   *
   * \sa setGridLinesPen().
   */
  void setPen(const WPen& pen);

  /*! \brief Returns the pen used for rendering the axis and ticks.
   *
   * \sa setPen()
   */
  const WPen& pen() const { return pen_; }

  /*! \brief Changes the pen used for rendering the grid lines.
   *
   * The default value is a gray pen of 0 width.
   *
   * \sa setPen(), gridLinesPen().
   */
  void setGridLinesPen(const WPen& pen);

  /*! \brief Returns the pen used for rendering the grid lines.
   *
   * \sa setGridLinesPen()
   */
  const WPen& gridLinesPen() const { return gridLinesPen_; }

  /*! \brief Sets the margin between the axis and the plot area.
   *
   * The margin is defined in pixels.
   *
   * The default value is 0.
   *
   * \sa margin()
   */
  void setMargin(int pixels);

  /*! \brief Returns the margin between the axis and the plot area.
   *
   * \sa setMargin()
   */
  int margin() const { return margin_; }

  /*! \brief Sets the axis title.
   *
   * The default title is empty.
   *
   * \sa title()
   */
  void setTitle(const WString& title);

  /*! \brief Returns the axis title.
   *
   * \sa setTitle()
   */
  const WString& title() const { return title_; }

  /*! \brief Sets the axis title font.
   *
   * The default title font is a 12 point Sans Serif font.
   *
   * \sa titleFont()
   */
  void setTitleFont(const WFont& titleFont);

  /*! \brief Returns the axis title font.
   *
   * \sa setTitleFont()
   */
  const WFont& titleFont() const { return titleFont_; }

  /*! \brief Sets the axis label font.
   *
   * The default label font is a 10 point Sans Serif font.
   *
   * \sa labelFont()
   */
  void setLabelFont(const WFont& labelFont);

  /*! \brief Returns the axis label font.
   *
   * \sa setLabelFont()
   */
  const WFont& labelFont() const { return labelFont_; }

  WString label(double u) const;

  /*! \brief Returns the chart to which this axis belongs.
   *
   * \sa WCartesianChart::axis()
   */
  WCartesianChart *chart() const { return chart_; }

  int segmentCount() const { return (int)segments_.size(); }

private:
  enum DateTimeUnit { Seconds, Minutes, Hours, Days, Months, Years };

  WCartesianChart *chart_;
  Axis             axis_;
  bool             visible_;
  AxisValue        location_;
  AxisScale        scale_;
  double           resolution_;
  double           labelInterval_;
  WString          labelFormat_;
  bool             gridLines_;
  WPen             pen_, gridLinesPen_;
  int              margin_;
  double           labelAngle_;
  WString          title_;
  WFont            titleFont_, labelFont_;
  WFlags<AxisValue> roundLimits_;

  struct Segment {
    double minimum, maximum;
    mutable double renderMinimum, renderMaximum, renderLength, renderStart;
    mutable DateTimeUnit dateTimeRenderUnit;
    mutable int dateTimeRenderInterval;

    Segment();
  };

  std::vector<Segment> segments_;

  mutable double renderInterval_;

  WAxis();
  void init(WCartesianChart *chart, Axis axis);
  void update();

  template <typename T>
  bool set(T& m, const T& v);

  bool prepareRender(WChart2DRenderer& renderer) const;
  void computeRange(WChart2DRenderer& renderer, const Segment& segment) const;
  void setOtherAxisLocation(AxisValue otherLocation) const;

  struct TickLabel {
    enum TickLength { Zero, Short, Long };

    double u;
    TickLength tickLength;
    WString    label;

    TickLabel(double v, TickLength length, const WString& l = WString());
  };

  void getLabelTicks(WChart2DRenderer& renderer, std::vector<TickLabel>& ticks,
		     int segment) const;

  double getValue(const boost::any& v) const;
  static double getDateValue(const boost::any& value);

  double calcAutoNumLabels(const Segment& s) const;

  double mapFromDevice(double d) const;
  double mapToDevice(const boost::any& value, int segment = 0) const;
  double mapToDevice(double value, int segment = 0) const;

  long getDateNumber(Wt::WDateTime dt) const;

  friend class WCartesianChart;
  friend class WChart2DRenderer;
};

template <typename T>
bool WAxis::set(T& m, const T& v)
{
  if (m != v) {
    m = v;
    update();
    return true;
  } else
    return false;
}

  }
}

#endif // CHART_WAXIS_H_
