#!/usr/bin/env python
#
# Copyright 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Code related to bar charts."""

import copy
import warnings

from mapreduce.lib.graphy import common
from mapreduce.lib.graphy import util


class BarsStyle(object):
  """Style of a series of bars in a BarChart

  Object Attributes:
    color:  Hex string, like '00ff00' for green
  """
  def __init__(self, color):
    self.color = color


class BarChartStyle(object):
  """Represents the style for bars on a BarChart.

  Any of the object attributes may be set to None, in which case the
  value will be auto-calculated.

  Object Attributes:
    bar_thickness: The thickness of a bar, in pixels.
    bar_gap: The gap between bars, in pixels, or as a fraction of bar thickness
        if use_fractional_gap_spacing is True.
    group_gap: The gap between groups of bars, in pixels, or as a fraction of
        bar thickness if use_fractional_gap_spacing is True.
    use_fractional_gap_spacing: if True, bar_gap and group_gap specify gap
        sizes as a fraction of bar width. Default is False.
  """

  _DEFAULT_GROUP_GAP = 8
  _DEFAULT_BAR_GAP = 4

  def __init__(self, bar_thickness=None,
               bar_gap=_DEFAULT_BAR_GAP, group_gap=_DEFAULT_GROUP_GAP,
               use_fractional_gap_spacing=False):
    """Create a new BarChartStyle.

    Args:
     bar_thickness: The thickness of a bar, in pixels. Set this to None if
       you want the bar thickness to be auto-calculated (this is the default
       behaviour).
     bar_gap: The gap between bars, in pixels. Default is 4.
     group_gap: The gap between groups of bars, in pixels. Default is 8.
    """
    self.bar_thickness = bar_thickness
    self.bar_gap = bar_gap
    self.group_gap = group_gap
    self.use_fractional_gap_spacing = use_fractional_gap_spacing


class BarStyle(BarChartStyle):

  def __init__(self, *args, **kwargs):
    warnings.warn('BarStyle is deprecated.  Use BarChartStyle.',
                  DeprecationWarning, stacklevel=2)
    super(BarStyle, self).__init__(*args, **kwargs)


class BarChart(common.BaseChart):
  """Represents a bar chart.

  Object attributes:
    vertical: if True, the bars will be vertical. Default is True.
    stacked: if True, the bars will be stacked. Default is False.
    style: The BarChartStyle for all bars on this chart, specifying bar
      thickness and gaps between bars.
  """

  def __init__(self, points=None):
    """Constructor for BarChart objects."""
    super(BarChart, self).__init__()
    if points is not None:
      self.AddBars(points)
    self.vertical = True
    self.stacked = False
    self.style = BarChartStyle(None, None, None) # full auto

  def AddBars(self, points, label=None, color=None):
    """Add a series of bars to the chart.

      points: List of y-values for the bars in this series
      label:  Name of the series (used in the legend)
      color:  Hex string, like '00ff00' for green

    This is a convenience method which constructs & appends the DataSeries for
    you.
    """
    if label is not None and util._IsColor(label):
      warnings.warn('Your code may be broken! '
                    'Label is a hex triplet.  Maybe it is a color? The '
                    'old argument order (color before label) is deprecated.',
                    DeprecationWarning, stacklevel=2)
    style = BarsStyle(color)
    series = common.DataSeries(points, label=label, style=style)
    self.data.append(series)
    return series

  def GetDependentAxes(self):
    """Get the dependendant axes, which depend on orientation."""
    if self.vertical:
      return (self._axes[common.AxisPosition.LEFT] +
              self._axes[common.AxisPosition.RIGHT])
    else:
      return (self._axes[common.AxisPosition.TOP] +
              self._axes[common.AxisPosition.BOTTOM])

  def GetIndependentAxes(self):
    """Get the independendant axes, which depend on orientation."""
    if self.vertical:
      return (self._axes[common.AxisPosition.TOP] +
              self._axes[common.AxisPosition.BOTTOM])
    else:
      return (self._axes[common.AxisPosition.LEFT] +
              self._axes[common.AxisPosition.RIGHT])

  def GetDependentAxis(self):
    """Get the main dependendant axis, which depends on orientation."""
    if self.vertical:
      return self.left
    else:
      return self.bottom

  def GetIndependentAxis(self):
    """Get the main independendant axis, which depends on orientation."""
    if self.vertical:
      return self.bottom
    else:
      return self.left

  def GetMinMaxValues(self):
    """Get the largest & smallest bar values as (min_value, max_value)."""
    if not self.stacked:
      return super(BarChart, self).GetMinMaxValues()

    if not self.data:
      return None, None  # No data, nothing to do.
    num_bars = max(len(series.data) for series in self.data)
    positives = [0 for i in xrange(0, num_bars)]
    negatives = list(positives)
    for series in self.data:
      for i, point in enumerate(series.data):
        if point:
          if point > 0:
            positives[i] += point
          else:
            negatives[i] += point
    min_value = min(min(positives), min(negatives))
    max_value = max(max(positives), max(negatives))
    return min_value, max_value
