"""This is the Bokeh charts interface. It gives you a high level API to build
complex plot is a simple way.

This is the Histogram class which lets you build your histograms just passing
the arguments to the Chart class and calling the proper functions.
"""
#-----------------------------------------------------------------------------
# Copyright (c) 2012 - 2014, Continuum Analytics, Inc. All rights reserved.
#
# Powered by the Bokeh Development Team.
#
# The full license is in the file LICENSE.txt, distributed with this software.
#-----------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
from __future__ import absolute_import

try:
    import scipy.special
    _is_scipy = True
except ImportError as e:
    _is_scipy = False
import numpy as np

from ..utils import chunk, cycle_colors
from .._builder import Builder, create_and_build
from ...models import ColumnDataSource, GlyphRenderer, Range1d
from ...models.glyphs import Line, Quad
from ...properties import Bool, Float, Int

#-----------------------------------------------------------------------------
# Classes and functions
#-----------------------------------------------------------------------------


def Histogram(values, bins, mu=None, sigma=None, density=True, **kws):
    """ Create a histogram chart using :class:`HistogramBuilder <bokeh.charts.builder.histogram_builder.HistogramBuilder>`
    to render the geometry from values, bins, sigma and density.

    Args:
        values (iterable): iterable 2d representing the data series
            values matrix.
        bins (int): number of bins to use in the Histogram building.
        mu (float, optional): theoretical mean value for the normal
            distribution. (default: None)
        sigma (float, optional): theoretical sigma value for the
            normal distribution. (default: None)
        density (bool, optional):  If False, the result will contain
            the number of samples in each bin.  If True, the result
            is the value of the probability *density* function at
            the bin, normalized such that the *integral* over the
            range is 1. For more info check numpy.histogram
            function documentation. (default: True)

    In addition the the parameters specific to this chart,
    :ref:`userguide_charts_generic_arguments` are also accepted as keyword parameters.

    Returns:
        a new :class:`Chart <bokeh.charts.Chart>`

    Examples:

    .. bokeh-plot::
        :source-position: above

        import pandas as pd
        from bokeh.charts import Histogram, output_file, show

        # (dict, OrderedDict, lists, arrays and DataFrames are valid inputs)
        xyvalues = pd.DataFrame(dict(normal=[1, 2, 3, 1], lognormal=[5, 4, 4, 1]))

        hm = Histogram(xyvalues, bins=5, title='Histogram')

        output_file('histogram.html')
        show(hm)

    """
    return create_and_build(
        HistogramBuilder, values, bins=bins, mu=mu, sigma=sigma, density=density,
        **kws
    )


class HistogramBuilder(Builder):
    """This is the Histogram class and it is in charge of plotting
    histograms in an easy and intuitive way.

    Essentially, we provide a way to ingest the data, make the proper
    calculations and push the references into a source object.
    We additionally make calculations for the ranges.
    And finally add the needed glyphs (quads and lines) taking the
    references from the source.

    """

    bins = Int(10, help="""
    Number of bins to use for the histogram. (default: 10)
    """)

    mu = Float(help="""
    Theoretical mean value for the normal distribution. (default: None)
    """)

    sigma = Float(help="""
    Theoretical standard deviation value for the normal distribution.
    (default: None)
    """)

    density = Bool(True, help="""
    Whether to normalize the histogram. (default: True)

    If True, the result is the value of the probability *density* function
    at the bin, normalized such that the *integral* over the range is 1. If
    False, the result will contain the number of samples in each bin.

    For more info check ``numpy.histogram`` function documentation.

    """)

    def _process_data(self):
        """Take the Histogram data from the input **value.

        It calculates the chart properties accordingly. Then build a dict
        containing references to all the calculated points to be used by
        the quad and line glyphs inside the ``_yield_renderers`` method.
        """
        # list to save all the groups available in the incomming input
        self._groups.extend(self._values.keys())

        # fill the data dictionary with the proper values
        for i, (val, values) in enumerate(self._values.items()):
            self.set_and_get("", val, values)
            #build the histogram using the set bins number
            hist, edges = np.histogram(
                np.array(values), density=self.density, bins=self.bins
            )
            self.set_and_get("hist", val, hist)
            self.set_and_get("edges", val, edges)
            self.set_and_get("left", val, edges[:-1])
            self.set_and_get("right", val, edges[1:])
            self.set_and_get("bottom", val, np.zeros(len(hist)))

            self._mu_and_sigma = False
            if self.mu is not None and self.sigma is not None:
                if _is_scipy:
                    self._mu_and_sigma = True
                    self.set_and_get("x", val, np.linspace(-2, 2, len(self._data[val])))
                    den = 2 * self.sigma ** 2