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

package net.sourceforge.chart2d;


import java.awt.*;
import java.util.*;


/**
 * Shared layout helpers for graph chart areas.  These methods preserve the
 * legacy behavior while reducing duplication between labels-bottom and
 * labels-left chart layouts.
 */
final class GraphChartAreaLayoutEngine {

  static final class RangeContext {
    final float greatestValue;
    final float leastValue;
    final float maxValue;
    final int dataSign;
    final int precisionNum;
    final String[] labels;

    RangeContext (
      float greatest, float least, float max, int sign, int precision,
      String[] plotLabels) {

      greatestValue = greatest;
      leastValue = least;
      maxValue = max;
      dataSign = sign;
      precisionNum = precision;
      labels = plotLabels;
    }
  }


  private GraphChartAreaLayoutEngine() {}


  static void configureComponentsAndRatios (
    GraphChartArea chartArea, Vector graphVector, Vector datasetVector,
    AxisArea labelsAxis, AxisArea numbersAxis, LegendArea legend,
    boolean labelsBottom) {

    int colorOffset = 0;
    for (int i = 0; i < graphVector.size(); ++i) {

      int datasetsLength = ((Dataset)datasetVector.get (i)).getNumSets();
      Color[] graphColors =
        chartArea.getDatasetColors (colorOffset, colorOffset + datasetsLength);

      GraphArea graph = (GraphArea)graphVector.get (i);
      graph.setBarColors (graphColors);
      graph.setDotColors (graphColors);
      graph.setLineColors (graphColors);
      graph.setWarningRegions (chartArea.getWarningRegions());
      graph.setComponentsColoringByCat (chartArea.getGraphComponentsColoringByCat());
      graph.setComponentsColorsByCat (chartArea.getGraphComponentsColorsByCat());
      colorOffset += datasetsLength;
    }

    float widthRatio = chartArea.getRatio (Area.WIDTH);
    float heightRatio = chartArea.getRatio (Area.HEIGHT);
    labelsAxis.setCustomRatio (Area.WIDTH, true, widthRatio);
    labelsAxis.setCustomRatio (Area.HEIGHT, true, heightRatio);
    numbersAxis.setCustomRatio (Area.WIDTH, true, widthRatio);
    numbersAxis.setCustomRatio (Area.HEIGHT, true, heightRatio);
    legend.setCustomRatio (Area.WIDTH, true, widthRatio);
    legend.setCustomRatio (Area.HEIGHT, true, heightRatio);

    int tickAlignment = labelsBottom ?
      ((XAxisArea)labelsAxis).getTicksAlignment() :
      ((YAxisArea)labelsAxis).getTicksAlignment();
    for (int i = 0; i < graphVector.size(); ++i) {
      GraphArea graph = (GraphArea)graphVector.get (i);
      graph.setCustomRatio (Area.WIDTH, true, widthRatio);
      graph.setCustomRatio (Area.HEIGHT, true, heightRatio);
      graph.setLabelsAxisTicksAlignment (tickAlignment);
    }
  }


  static int getBetweenChartAndLegendGapThickness (GraphChartArea chartArea, int availableWidth) {

    if (!chartArea.getBetweenChartAndLegendGapExistence() || !chartArea.getLegendExistence()) {
      return 0;
    }

    int thickness = chartArea.applyRatio (
      chartArea.getBetweenChartAndLegendGapThicknessModel(),
      chartArea.getRatio (Area.WIDTH));
    return thickness <= availableWidth ? thickness : availableWidth;
  }


  static RangeContext buildRangeContext (GraphChartArea chartArea, Vector datasetVector) {

    float greatestValue = -9999999999999999f;
    float leastValue = 9999999999999999f;
    for (int i = 0; i < datasetVector.size(); ++i) {
      greatestValue =
        ((Dataset)datasetVector.get (i)).getGreatest() > greatestValue ?
        ((Dataset)datasetVector.get (i)).getGreatest() : greatestValue;
      leastValue =
        ((Dataset)datasetVector.get (i)).getLeast() < leastValue ?
        ((Dataset)datasetVector.get (i)).getLeast() : leastValue;
    }

    greatestValue =
      chartArea.getCustomizeGreatestValue() &&
      (chartArea.getCustomGreatestValue() > greatestValue) ?
      chartArea.getCustomGreatestValue() : greatestValue;
    leastValue =
      chartArea.getCustomizeLeastValue() &&
      (chartArea.getCustomLeastValue() < leastValue) ?
      chartArea.getCustomLeastValue() : leastValue;

    float maxValue = chartArea.getGraphableToAvailableRatio() > 0 ?
      (greatestValue - leastValue) / chartArea.getGraphableToAvailableRatio() : 0f;
    float emptyValue = maxValue - (greatestValue - leastValue);

    int dataSign = GraphArea.NEG;
    if (greatestValue > 0f && leastValue < 0f) {
      greatestValue = greatestValue + (emptyValue / 2f);
      leastValue = leastValue - (emptyValue / 2f);
      float nomValue = Math.abs (greatestValue) > Math.abs (leastValue) ?
        Math.abs (greatestValue) : Math.abs (leastValue);
      greatestValue = nomValue;
      leastValue = -nomValue;
      dataSign = GraphArea.MIX;
    }
    else if (greatestValue >= 0f && leastValue >= 0f) {
      greatestValue = greatestValue + emptyValue;
      if (!chartArea.getCustomizeLeastValue()) leastValue = 0f;
      dataSign = GraphArea.POS;
    }
    else {
      leastValue = leastValue - emptyValue;
      if (!chartArea.getCustomizeGreatestValue()) greatestValue = 0f;
    }

    int precisionNum = chartArea.getLabelsPrecisionNum();
    greatestValue = chartArea.getPrecisionCeil (greatestValue, precisionNum);
    leastValue = chartArea.getPrecisionFloor (leastValue, precisionNum);
    maxValue = greatestValue - leastValue;

    return new RangeContext (
      greatestValue, leastValue, maxValue, dataSign, precisionNum,
      buildPlotLabels (chartArea, leastValue, maxValue, precisionNum));
  }


  static String[] buildPlotLabels (
    GraphChartArea chartArea, float leastValue, float maxValue, int precisionNum) {

    float difference = 0f;
    float label = 0f;
    if (chartArea.getNumPlotAxisLabels() > 1) {
      difference = maxValue / (chartArea.getNumPlotAxisLabels() - 1);
      label = leastValue;
    }
    else {
      label = maxValue / 2f;
    }

    String[] labels = new String[chartArea.getNumPlotAxisLabels()];
    String lastLabel = null;
    for (int i = 0; i < chartArea.getNumPlotAxisLabels(); ++i) {
      float sign = label > 0 ? 1f : -1f;
      if (i == chartArea.getNumPlotAxisLabels() - 1 || i == 0) {
        labels[i] = chartArea.getFloatToString (
          sign * chartArea.getPrecisionRound (sign * label, precisionNum),
          precisionNum);
      }
      else {
        labels[i] = chartArea.getFloatToString (label, precisionNum);
      }
      label += difference;
      if (labels[i].equals (lastLabel)) labels[i] = "^";
      else lastLabel = labels[i];
    }
    return labels;
  }


  static void assignLabelsToYAxis (YAxisArea yAxis, String[] labels) {

    String[] reversed = new String[labels.length];
    for (int i = 0; i < labels.length; ++i) reversed[i] = labels[labels.length - 1 - i];
    yAxis.getTextList().setLabels (reversed);
  }


  static void assignLabelsToXAxis (XAxisArea xAxis, String[] labels) {
    xAxis.getTextList().setLabels (labels);
  }


  static void applyAutoLayoutRatios (
    GraphChartArea chartArea, int width, int height, int yAxisMinWidth,
    int xAxisMinWidth, int graphMinWidth, int yAxisMinHeight, int xAxisMinHeight,
    int graphMinHeight) {

    float yAxisToWidthRatio = width > 0 ? yAxisMinWidth / (float)width : 0f;
    yAxisToWidthRatio = yAxisToWidthRatio < 1f ? yAxisToWidthRatio : 1f;
    float xAxisToWidthRatio = width > 0 ? xAxisMinWidth / (float)width : 0f;
    xAxisToWidthRatio = xAxisToWidthRatio < 1f ? xAxisToWidthRatio : 1f;
    float graphToWidthRatio = width > 0 ? graphMinWidth / (float)width : 0f;
    graphToWidthRatio = graphToWidthRatio < 1f ? graphToWidthRatio : 1f;
    if (xAxisToWidthRatio > graphToWidthRatio) graphToWidthRatio = xAxisToWidthRatio;
    else xAxisToWidthRatio = graphToWidthRatio;

    float yAxisToHeightRatio = height > 0 ? yAxisMinHeight / (float)height : 0f;
    yAxisToHeightRatio = yAxisToHeightRatio < 1f ? yAxisToHeightRatio : 1f;
    float xAxisToHeightRatio = height > 0 ? xAxisMinHeight / (float)height : 0f;
    xAxisToHeightRatio = xAxisToHeightRatio < 1f ? xAxisToHeightRatio : 1f;
    float graphToHeightRatio = height > 0 ? graphMinHeight / (float)height : 0f;
    graphToHeightRatio = graphToHeightRatio < 1f ? graphToHeightRatio : 1f;
    if (yAxisToHeightRatio > graphToHeightRatio) graphToHeightRatio = yAxisToHeightRatio;
    else yAxisToHeightRatio = graphToHeightRatio;

    if (yAxisToWidthRatio <= 0f || yAxisToHeightRatio <= 0f) {
      yAxisToWidthRatio = yAxisToHeightRatio = 0f;
    }
    if (xAxisToWidthRatio <= 0f || xAxisToHeightRatio <= 0f) {
      xAxisToWidthRatio = xAxisToHeightRatio = 0f;
    }
    if (graphToWidthRatio <= 0f || graphToHeightRatio <= 0f) {
      graphToWidthRatio = graphToHeightRatio = 0f;
    }
    float legendToWidthRatio = 1f - yAxisToWidthRatio - graphToWidthRatio;
    float legendToHeightRatio = 1f;
    if (legendToWidthRatio <= 0f || legendToHeightRatio <= 0f) {
      legendToWidthRatio = legendToHeightRatio = 0f;
    }

    chartArea.setYAxisToWidthRatio (yAxisToWidthRatio);
    chartArea.setXAxisToWidthRatio (xAxisToWidthRatio);
    chartArea.setGraphToWidthRatio (graphToWidthRatio);
    chartArea.setLegendToWidthRatio (legendToWidthRatio);
    chartArea.setYAxisToHeightRatio (yAxisToHeightRatio);
    chartArea.setXAxisToHeightRatio (xAxisToHeightRatio);
    chartArea.setGraphToHeightRatio (graphToHeightRatio);
    chartArea.setLegendToHeightRatio (legendToHeightRatio);
    chartArea.setAutoSetLayoutRatios (false);
  }


  static void updateWarningRegions (
    Vector warningRegions, RangeContext range, int graphSpaceExtent,
    float multiplier, boolean horizontal) {

    for (int i = 0; i < warningRegions.size(); ++i) {
      WarningRegion warningRegion = (WarningRegion)warningRegions.get (i);
      if (range.greatestValue > 0f && range.leastValue < 0f) {
        warningRegion.setHighGraph (
          warningRegion.getHigh() == Float.POSITIVE_INFINITY ? graphSpaceExtent :
          (int)((warningRegion.getHigh() + range.maxValue / 2f) * multiplier));
        warningRegion.setLowGraph (
          warningRegion.getLow() == Float.NEGATIVE_INFINITY ? 0f :
          (int)((warningRegion.getLow() + range.maxValue / 2f) * multiplier));
      }
      else if (range.greatestValue >= 0f) {
        warningRegion.setHighGraph (
          warningRegion.getHigh() == Float.POSITIVE_INFINITY ? graphSpaceExtent :
          (int)((warningRegion.getHigh() - range.leastValue) * multiplier));
        warningRegion.setLowGraph (
          warningRegion.getLow() == Float.NEGATIVE_INFINITY ? 0f :
          (int)((warningRegion.getLow() - range.leastValue) * multiplier));
      }
      else {
        warningRegion.setHighGraph (
          warningRegion.getHigh() == Float.POSITIVE_INFINITY ? graphSpaceExtent :
          (int)((warningRegion.getHigh() + range.maxValue - range.greatestValue) * multiplier));
        warningRegion.setLowGraph (
          warningRegion.getLow() == Float.NEGATIVE_INFINITY ? 0f :
          (int)((warningRegion.getLow() + range.maxValue - range.greatestValue) * multiplier));
      }
      if (warningRegion.getHighGraph() > graphSpaceExtent) {
        warningRegion.setHighGraph (graphSpaceExtent);
      }
      if (warningRegion.getLowGraph() < 0) warningRegion.setLowGraph (0);
      if (warningRegion.getHighGraph() < warningRegion.getLowGraph()) {
        warningRegion.setHighGraph (warningRegion.getLowGraph());
      }
      if (warningRegion.getLowGraph() > warningRegion.getHighGraph()) {
        warningRegion.setLowGraph (warningRegion.getHighGraph());
      }
      if (multiplier <= 0f) {
        warningRegion.setHighGraph (0);
        warningRegion.setLowGraph (0);
      }
    }
  }
}
