import logging
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

from ..calculator import Calculator
from ..utils import get_extension, set_chart_style

from .cycletime import CycleTimeCalculator

logger = logging.getLogger(__name__)

class HistogramCalculator(Calculator):
    """Build histogram data for the cycle times in `cycle_data`. Returns
    a dictionary with keys `bin_values` and `bin_edges` of numpy arrays
    """

    def run(self):
        cycle_data = self.get_result(CycleTimeCalculator)
        
        values, edges = np.histogram(cycle_data['cycle_time'].astype('timedelta64[D]').dropna(), bins=10)

        index = []
        for i, _ in enumerate(edges):
            if i == 0:
                continue
            index.append("%.01f to %.01f" % (edges[i - 1], edges[i],))

        return pd.Series(values, name="Items", index=index)
    
    def write(self):
        data = self.get_result()

        if self.settings['histogram_data']:
            self.write_file(data, self.settings['histogram_data'])
        else:
            logger.debug("No output file specified for histogram data")
        
        if self.settings['histogram_chart']:
            self.write_chart(data, self.settings['histogram_chart'])
        else:
            logger.debug("No output file specified for histogram chart")

    def write_file(self, data, output_file):
        output_extension = get_extension(output_file)

        file_data = self.get_result()

        logger.info("Writing histogram data to %s", output_file)
        if output_extension == '.json':
            file_data.to_json(output_file, date_format='iso')
        elif output_extension == '.xlsx':
            file_data.to_frame(name='histogram').to_excel(output_file, 'Histogram', header=True)
        else:
            file_data.to_csv(output_file, header=True)
    
    def write_chart(self, data, output_file):
        cycle_data = self.get_result(CycleTimeCalculator)
        chart_data = cycle_data[['cycle_time', 'completed_timestamp']].dropna(subset=['cycle_time'])

        # The `window` calculation and the chart output will fail if we don't
        # have at least two valid data points.
        ct_days = chart_data['cycle_time'].dt.days
        if len(ct_days.index) < 2:
            logger.warning("Need at least 2 completed items to draw histogram")
            return

        # Slice off items before the window
        window = self.settings['histogram_window']
        if window:
            start = chart_data['completed_timestamp'].max().normalize() - pd.Timedelta(window, 'D')
            chart_data = chart_data[chart_data.completed_timestamp >= start]
        
            # Re-check that we have enough data
            ct_days = chart_data['cycle_time'].dt.days
            if len(ct_days.index) < 2:
                logger.warning("Need at least 2 completed items to draw histogram")
                return
        
        quantiles = self.settings['quantiles']
        logger.debug("Showing histogram at quantiles %s", ', '.join(['%.2f' % (q * 100.0) for q in quantiles]))

        fig, ax = plt.subplots()

        sns.distplot(ct_days, bins=30, ax=ax, axlabel="Cycle time (days)")
        
        if self.settings['histogram_chart_title']:
            ax.set_title(self.settings['histogram_chart_title'])

        _, right = ax.get_xlim()
        ax.set_xlim(0, right)

        # Add quantiles
        bottom, top = ax.get_ylim()
        for quantile, value in ct_days.quantile(quantiles).iteritems():
            ax.vlines(value, bottom, top - 0.001, linestyles='--', linewidths=1)
            ax.annotate("%.0f%% (%.0f days)" % ((quantile * 100), value,),
                xy=(value, top),
                xytext=(value - 0.1, top - 0.001),
                rotation="vertical",
                fontsize="x-small",
                ha="right"
            )

        set_chart_style()
        
        # Write file
        logger.info("Writing histogram chart to %s", output_file)
        fig.savefig(output_file, bbox_inches='tight', dpi=300)
        plt.close(fig)
