/**
 * Chart2D, a java library for drawing two dimensional charts.
 * Copyright (C) 2001 Jason J. Simas
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * The author of this library may be contacted at:
 * E-mail:  jjsimas@users.sourceforge.net
 * Street Address:  J J Simas, 887 Tico Road, Ojai, CA 93023-3555 USA
 */


package net.sourceforge.chart2d;


import java.awt.*;
import java.awt.geom.*;
import java.util.Date;


/**
 * A container for many variables and components relating to a pie area.
 * A pie area is the area that contains only the pie of a pie chart.  It doesn't
 * have a legend.<br>
 * <b>Features:</b><br>
 * Allows for charting both floats and int values.  Works fine even if all
 * values are zero.  Does not accept negative values.  Outputs center points
 * of sectors so that labels can be drawn into the sectors, touching these
 * points.  Outpouts how many sectors are in each quarter of the pie, so that
 * appropriate placement of labels can be decided.  The first datum in the
 * data set will be the first sector beginning from the angle of 135 degrees and
 * ending somewhere clockwise to it  It will have the first color in the
 * colors set.  The remaining sectors are laid out similarly, clockwise.
 */
final class PieArea extends Area {


  private float[] dataset;
  private Color[] colors;
  private Paint[] paints;

  private Arc2D.Float[] sectors;
  private int numSectors;
  private int[] numSectorsInQuarters;
  private boolean outlineSectors;
  private Color outlineSectorsColor;

  private float sectorPointDepthRatio;
  private Point[] pointsInSectors;
  private float sectorGapPointRatio;
  private Point[] pointsOutSectors;

  private int piePrefSpaceSize;
  private boolean customSizing;
  private Dimension customSpaceSize;

  private int lightSource;

  private boolean needsUpdate;


  /**
   * Creates a pie area with the default values.
   */
  PieArea() {

    setAutoSizes (false, false);
    setAutoJustifys (false, false);
    setBackgroundExistence (false);
    setBorderExistence (false);
    setGapExistence (true);
    setGapThicknessModel (0);
    setOutlineSectors (true);
    setOutlineSectorsColor (Color.black);
    setPiePrefSizeModel (100);
    setSectorPointDepthRatio (.25f); //setSectorPointRatio
    setSectorGapPointRatio (.25f);
    setDataset (new float[0]);
    setColors (new Color[0]);
    setCustomSpaceSize (false, new Dimension());
    setLightSource (TOP);
    resetPieAreaModel (true);
    needsUpdate = true;
    numSectorsInQuarters = new int[4];
  }


  /**
   * Sets from which direction the light is coming for shading of the pie sectors.
   * @param source The direction of the light.
   */
  final void setLightSource (int source) {

    needsUpdate = true;
    lightSource = source;
  }


  /**
   * Indicates whether the sectors should have a thin outline.
   * @param outline If true, then the outline exists.
   */
  final void setOutlineSectors (boolean outline) {

    needsUpdate = true;
    outlineSectors = outline;
  }


  /**
   * Indicates the color of the sectors outline if it exists.
   * @param color The color for the outline.
   */
  final void setOutlineSectorsColor (Color color) {

    needsUpdate = true;
    outlineSectorsColor = color;
  }


  /**
   * Determines how far into the sector the depth of the points from the
   * getPointsInSectors() method are.
   * @param ratio The ratio on the radius of the circle.
   */
  final void setSectorPointDepthRatio (float ratio) {
    needsUpdate = true;
    sectorPointDepthRatio = ratio;
  }


  /**
   * Determines how far out of the sector the points from the
   * getPointsOutSectors() method are.
   * @param ratio The ratio on the radius of the circle.
   */
  final void setSectorGapPointRatio (float ratio) {
    needsUpdate = true;
    sectorGapPointRatio = ratio;
  }


  /**
   * For input of the raw numbers to represent by the pie.  Array element i
   * is sector i, clockwise from degree 135.
   * @param values The raw numbers to represent by the pie.
   */
  final void setDataset (float[] values) {

    needsUpdate = true;
    dataset = values;
  }


  /**
   * For input of the color of each sector that represents a datum of the data
   * set.  Array element i is sector i, clockise from degree 135.
   * @param colors The colors of the sectors.
   */
  final void setColors (Color[] colors) {

    needsUpdate = true;
    this.colors = colors;
  }


  /**
   * Sets the model size of the pie.  When initially drawn, the pie will be
   * at least this size, if this size is less than the max space size of this
   * area.  After that and if magnificying, the pie size will be applied to the
   * size ratios.
   * @param size The model size of the pie.
   */
  final void setPiePrefSizeModel (int size) {

    needsUpdate = true;
    piePrefSpaceSize = size;
  }


  /**
   * Sets the "minimum" size of the pie directly.  If the minimum is too
   * large, then the size will be the pie's maximum size.
   * @param size The number to multiply to get the minimum size.
   */
  final void setCustomSpaceSize (boolean customize, Dimension size) {

    needsUpdate = true;
    customSizing = customize;
    customSpaceSize = size;
  }


  /**
   * Gets from which direction the light is coming for shading of the pie sectors.
   * @return The direction of the light.
   */
  final int getLightSource() {
    return lightSource;
  }


  /**
   * Returns whether the sectors should have a thin outline.
   * @return outline If true, then the outline exists.
   */
  final boolean getOutlineSectors() {

    return outlineSectors;
  }


  /**
   * Returns the color of the sectors outline if it exists.
   * @return color The color for the outline.
   */
  final Color getOutlineSectorsColor() {

    return outlineSectorsColor;
  }


  /**
   * Returns the raw numbers to represent by the pie.
   * @return The raw numbers to represent by the pie.
   */
  final float[] getDataset() {

    return dataset;
  }


  /**
   * Returns this property.
   * @return The colors of the lines.
   */
  final Color[] getColors() {

    return colors;
  }


  /**
   * Returns this property.
   * @return The number of sectors in this pie.
   */
  final int getNumSectors () {

    updatePieArea();
    return numSectors;
  }


  /**
   * Returns this property.  This property allows figuring out where to place
   * labels if one wants to label this pie, more than is provided by the legend
   * in PieChartArea.  One can use a
   * HorizontalTextListArea for the TOP and BOTTOM labels; and a
   * VerticalTextListArea for the LEFT and RIGHT labels.
   * This information helps them figure out how many and which labels belong in
   * each text list.
   * @return The number of sectors in the quarters.  Actually, the number of
   * center points of sectors in this quarter.  Quarters are defined as follows:
   * TOP = 135 to 45, LEFT = 45 to 315, BOTTOM = 315 to 225, RIGHT = 225 to 135.
   */
  final int[] getNumSectorsInQuarters() {

    updatePieArea();
    return numSectorsInQuarters;
  }


  /**
   * Returns how far into the sector the depth of the points from the
   * getPointsInSectors() method are.
   * @return The ratio on the radius of the circle.
   */
  final float getSectorPointDepthRatio() {

    return sectorPointDepthRatio;
  }


  /**
   * Returns a point in each sector at the depth specified by
   * setSectorsPointDepthRatio(float).
   * @return Point[] The points inside the sectors.
   */
  final Point[] getPointsInSectors() {

    updatePieArea();
    return pointsInSectors;
  }


  /**
   * Returns a point in out of each sector at the location indicated by
   * setSectorsPointExternalRatio(float).
   * @return Point[] The points outside the sectors.
   */
  final Point[] getPointsOutSectors() {
    updatePieArea();
    return pointsOutSectors;
  }


  /**
   * Resets the model for this class.  The model is used for shrinking and
   * growing of its components based on the maximum size of this class.  If this
   * method is called, then the next time the maximum size is set, this classes
   * model maximum size will be made equal to the new maximum size.  Effectively
   * what this does is ensure that whenever this objects maximum size is equal
   * to the one given, then all of the components will take on their default
   * model sizes.  Note:  This is only useful when auto model max sizing is
   * disabled.
   * @param reset True causes the max model size to be set upon the next max
   * sizing.
   */
  final void resetPieAreaModel (boolean reset) {

    needsUpdate = true;
    resetAreaModel (reset);
  }


  /**
   * Indicates whether some property of this class has changed.
   * @return True if some property has changed.
   */
  final boolean getPieAreaNeedsUpdate() {

    return (needsUpdate || getAreaNeedsUpdate());
  }


  /**
   * Updates this parent's variables, and this' variables.
   */
  final void updatePieArea () {

    if (getPieAreaNeedsUpdate()) {
      updateArea();
      update();
    }
    needsUpdate = false;
  }


  /**
   * Paints all the components of this class.  First all variables are updated.
   * @param g2D  The graphics context for calculations and painting.
   */
  final void paintComponent (Graphics2D g2D) {

    updatePieArea();
    super.paintComponent (g2D);

    for (int i = 0; i < numSectors; ++i) {
      g2D.setPaint (paints[i]);
      g2D.fill (sectors[i]);
    }

    if (outlineSectors) {
      for (int i = 0; i < numSectors; ++i) {
          g2D.setColor (outlineSectorsColor);
          g2D.draw (sectors[i]);
      }
    }
  }


  private void update() {

    if (!getAutoSize (MIN) && !customSizing) {
      piePrefSpaceSize = applyRatio (piePrefSpaceSize, getRatio (LESSER));
      int tempPrefSize = piePrefSpaceSize < getSpaceSize (MAX).width ?
        piePrefSpaceSize : getSpaceSize (MAX).width;
      tempPrefSize = piePrefSpaceSize < getSpaceSize (MAX).height ?
        piePrefSpaceSize : getSpaceSize (MAX).height;
      setSpaceSize (MIN, new Dimension (tempPrefSize, tempPrefSize));
    }
    else if (!getAutoSize (MIN) && customSizing) {
      setSpaceSize (MIN, customSpaceSize);
    }

    int width = getSpaceSize (MIN).width;
    int height = getSpaceSize (MIN).height;
    int minSpaceSizeSide = width < height ? width : height;

    Point location = getSpaceSizeLocation (MIN);
    int locationX = location.x + (int)((width - minSpaceSizeSide) / 2f);
    int locationY = location.y + (int)((height - minSpaceSizeSide) / 2f);

    numSectors = dataset.length;
    sectors = new Arc2D.Float[numSectors];

    float total = 0f;
    for (int i = 0; i < numSectors; ++i) {
      total = total + dataset[i];
    }

    if (total != 0f) {
      float end = 135f;
      float begin = 135f;
      float extent = 0f;
      for (int i = 0; i < (numSectors - 1); ++i) {
        extent = (dataset[i] / total) * 360f;
        begin = end - extent;
        sectors[i] = new Arc2D.Float (locationX, locationY,
          minSpaceSizeSide, minSpaceSizeSide, begin, extent, Arc2D.PIE);
        end = begin;
      }
      extent = (dataset[numSectors - 1] / total) * 360f;
      begin = 135f;
      sectors[numSectors - 1] = new Arc2D.Float (locationX, locationY,
        minSpaceSizeSide, minSpaceSizeSide, begin, extent, Arc2D.PIE);
    }
    else if (numSectors != 0) {
      float end = 135f;
      float begin = 135f;
      float extent = (1f / numSectors) * 360f;
      for (int i = 0; i < (numSectors - 1); ++i) {
        begin = end - extent;
        sectors[i] = new Arc2D.Float (locationX, locationY,
          minSpaceSizeSide, minSpaceSizeSide, begin, extent, Arc2D.PIE);
        end = begin;
      }
      begin = 135f;
      sectors[numSectors - 1] = new Arc2D.Float (locationX, locationY,
        minSpaceSizeSide, minSpaceSizeSide, begin, extent, Arc2D.PIE);
    }

    pointsInSectors = new Point[numSectors];
    float radius = minSpaceSizeSide / 2f;
    float centerX = locationX + radius;
    float centerY = locationY + radius;
    for (int i = 0; i < numSectors; ++i) {
      float begin = sectors[i].start;
      float extent = sectors[i].extent;
      float theta = begin + extent / 2f;
      float offsetX = (float)((1f - sectorPointDepthRatio) * radius *
        Math.cos (Math.toRadians (theta)));
      float offsetY = (float)((1f - sectorPointDepthRatio) * radius *
        Math.sin (Math.toRadians (theta)));
      float x = centerX + offsetX;
      float y = centerY - offsetY;
      pointsInSectors[i] = new Point (Math.round (x), Math.round (y));
    }

    int gapThickness = getGapThickness();
    pointsOutSectors = new Point[numSectors];
    float outOffset = sectorGapPointRatio * gapThickness;
    for (int i = 0; i < numSectors; ++i) {

      float begin = sectors[i].start;
      float extent = sectors[i].extent;
      float theta = begin + extent / 2f;
      float offsetX = (float)((radius + outOffset) *
        Math.cos (Math.toRadians(theta)));
      float offsetY = (float)((radius + outOffset) *
        Math.sin (Math.toRadians(theta)));
      float x = centerX + offsetX;
      float y = centerY - offsetY;
      pointsOutSectors[i] = new Point (Math.round (x), Math.round (y));
    }

    numSectorsInQuarters[TOP] = 0;
    numSectorsInQuarters[RIGHT] = 0;
    numSectorsInQuarters[BOTTOM] = 0;
    numSectorsInQuarters[LEFT] = 0;

    boolean topDone = false;

    for (int i = 0; i < numSectors; ++i) {

      float begin = sectors[i].start;
      float extent = sectors[i].extent;
      begin = (begin + 720) % 360;
      extent = (extent + 720) % 360;
      float angle = begin + (extent / 2);

      if (angle <= 135 && angle > 45){
        if (!topDone) ++numSectorsInQuarters[TOP];
        else ++numSectorsInQuarters[LEFT];
      }
      else if (angle > 135 && angle <= 225){
        ++numSectorsInQuarters[LEFT];
        topDone = true;
      }
      else if (angle > 225 && angle <= 315){
        ++numSectorsInQuarters[BOTTOM];
        topDone = true;
      }
      else {
        ++numSectorsInQuarters[RIGHT];
        topDone = true;
      }
    }

    paints = new Paint[colors.length];
    for (int i = 0; i < paints.length; ++i) {
      if (lightSource == TOP) {
        paints[i] = new GradientPaint (locationX, locationY, colors[i].brighter(),
          locationX, locationY + height, colors[i]);
      }
      else if (lightSource == BOTTOM) {
        paints[i] = new GradientPaint (locationX, locationY, colors[i],
          locationX, locationY + height, colors[i].brighter());
      }
      else if (lightSource == LEFT) {
        paints[i] = new GradientPaint (locationX, locationY, colors[i].brighter(),
          locationX + width, locationY, colors[i]);
      }
      else if (lightSource == RIGHT) {
        paints[i] = new GradientPaint (locationX, locationY, colors[i],
          locationX + width, locationY, colors[i].brighter());
      }
      else {
        paints[i] = colors[i];
      }
    }
  }
}