/**
 * 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.util.*;


/**
 * The area that the x axis and y axis attach to, and in which bars, lines, or
 * dots are painted to represent the data set.  The "LB" in the name of this
 * class stands for Labels Bottom.  This graph should always be used when the
 * data descriptors for the the graph are located on the bottom.  Data
 * descriptors have values such as September and November, and not like 0 or 50.
 * <b>Features:</b><br>
 * Provides bordering functionality.  Allows lines to be painted that match
 * up with y axis and x axis tick marks in order to know where the bars, dots,
 * or plot lines are in relation to the y axis values.  Supports vertical
 * bar graphs, line charts, and scatter charts (dots), and any combintation
 * thereof.  If a bars are present in any graph, then it is highly recommended
 * that the GraphArea method, allowComponentAlignment be passed the value of
 * false.  This will ensure that all the bars are visible on the graph.  For
 * graphs that do not have bars present, the opposite is advised.  The GraphArea
 * method should be passed the value of true.  This provides a more intuitive
 * representation of the data.
 */
final class LBGraphArea extends GraphArea {

  private FancyBar[][] bars;
  private FancyDot[][] dots;
  private FancyLine[] lines;
  private boolean needsUpdate;


  /**
   * Creates a graph area with GraphArea's default values. Default values:
   * resetLBGraphAreaModel (true);
   */
  LBGraphArea() {

    bars = new FancyBar[0][0];
    dots = new FancyDot[0][0];
    lines = new FancyLine[0];

    setBarRoundingRatio (.25f);

    resetLBGraphAreaModel (true);
    needsUpdate = true;
  }


  /**
   * Does a quick calculation of the preferred width of the graph.
   * @param numSets The number of data series.
   * @param numComps The number of data categories per series.
   * @param numCompsPerCat  The number of data per series, per category.
   * @return The preferred width.
   */
  final int getPrefSpaceWidth (int numSets, int numCats, int numCompsPerCat) {

    float ratio = getRatio (WIDTH);
    int barsWidth = 0, dotsWidth = 0, linesWidth = 0, prefWidth = 0;
    if (getAllowComponentAlignment()) {
      barsWidth = getBarsExistence() ?
        (int)(numCats * (1 + (1 - getBarsWithinCategoryOverlapRatio()) *
        (numCompsPerCat - 1)) *
        applyRatio (getBarsThicknessModel(), ratio)) : 0;
      dotsWidth = getDotsExistence() ?
        (int)(numCats * (1 + (1 - getDotsWithinCategoryOverlapRatio()) *
        (numCompsPerCat - 1)) *
        applyRatio (getDotsThicknessModel(), ratio)) : 0;
      linesWidth = getLinesExistence() ?
        (int)(numCats * (1 + (1 - getLinesWithinCategoryOverlapRatio()) *
        (numCompsPerCat - 1)) *
        applyRatio (getLinesThicknessModel(), ratio)) : 0;
    }
    else {
      barsWidth = getBarsExistence() ?
        (int)(numSets * numCats *
        (1 + (1 - getBarsWithinCategoryOverlapRatio()) *
        (numCompsPerCat - 1)) *
        applyRatio (getBarsThicknessModel(), ratio)) : 0;
      dotsWidth = getDotsExistence() ?
        (int)(numSets * numCats * (1 +
        (1 - getDotsWithinCategoryOverlapRatio()) *
        (numCompsPerCat - 1)) *
        applyRatio (getDotsThicknessModel(), ratio)) : 0;
      linesWidth = getLinesExistence() ?
        (int)(numSets * numCats * (1 +
        (1 - getLinesWithinCategoryOverlapRatio()) *
        (numCompsPerCat - 1)) *
        applyRatio (getLinesThicknessModel(), ratio)) : 0;

    }
    prefWidth = barsWidth > dotsWidth ? barsWidth : dotsWidth;
    prefWidth = prefWidth > linesWidth ? prefWidth : linesWidth;
    int gapsWidth = getBetweenComponentsGapExistence() ?
      numCats * applyRatio (getBetweenComponentsGapThicknessModel(), ratio) : 0;
    prefWidth = prefWidth + gapsWidth;
    prefWidth = prefWidth < getSpaceSize(MAX).width ? prefWidth : getSpaceSize(MAX).width;
    return (prefWidth);
  }


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

    return (needsUpdate || getGraphAreaNeedsUpdate());
  }


  /**
   * 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 resetLBGraphAreaModel (boolean reset) {

    needsUpdate = true;
    resetGraphAreaModel (reset);
  }


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

    if (getLBGraphAreaNeedsUpdate()) {
      updateGraphArea();
      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) {

    updateLBGraphArea();
    super.paintComponent (g2D);

    Shape oldClip = g2D.getClip();
    if (getClip()) g2D.setClip (new Rectangle (getSpaceSizeLocation (MIN), getSpaceSize (MIN)));

    Composite oldComposite = g2D.getComposite();
    g2D.setComposite (getComponentsAlphaComposite());

    int numBars = bars.length > 0 ? bars[0].length : 0;
    int numDots = dots.length > 0 ? dots[0].length : 0;
    int numLines = lines.length;
    int numSets = getGraphValues().length;
    if (!getAllowComponentAlignment() && numBars > 0) {
      for (int i = 0; i < numSets; ++i) {
        for (int j = 0; j < numBars; ++j) {
          bars[i][j].paint (g2D);
        }
      }
    }
    else if (numBars > 0) {
      int[][] graphValues = getGraphValues();
      graphValues = stackedBarConvert (graphValues, getBarLowValues());
      for (int j = 0; j < numBars; ++j) {
        int[] sorted = stackedBarSort (graphValues, j);
        for (int i = 0; i < numSets; ++i) {
          bars[sorted[i]][j].paint (g2D);
        }
      }
    }

    for (int i = numLines - 1; i >= 0 ; --i) lines[i].paint (g2D);

    for (int i = numSets - 1; i >= 0; --i) {
      for (int j = numDots - 1; j >= 0 ; --j) {
        dots[i][j].paint (g2D);
      }
    }

    g2D.setClip (oldClip);
    g2D.setComposite (oldComposite);
  }


  private void update() {

    GraphLayoutEngine.LayoutResult result = GraphLayoutEngine.layout (this);
    bars = result.bars;
    dots = result.dots;
    lines = result.lines;
  }
}
