# -*- coding: utf-8 -*-
"""
Created on Dec 2, 2015

@author: noob
"""

import logging
from collections import Counter, OrderedDict

import numpy as np

from common.util.aggregators import Aggregator
from common.util.decorators import memoized_property
from .plots_base import DistPlot


class QuantilesVsTimes(DistPlot):
    title = 'Перцентиль'
    graphs = 'Перцентиль',

    def get_chunk(self):
        aggregator = Aggregator()
        chunk = {'Перцентиль': {
            'values': aggregator.count_quantiles([v[1] for v in self.data_values]),
            'color': '#0184d6',
            'type': 'spline' if self.ticks > 1 else 'column',
            'name': 'Перцентиль',
            }
        }
        return chunk

    @memoized_property
    def data_values(self):
        sql = '''select bin/1000, toUInt32(sum(cnt))
                    from loaddb.rt_microsecond_histograms_buffer
                    where job_id={job}
                    and job_date=toDate({job_date})
                    '''
        if self.job_obj.multitag and self.tag:
            sql += 'and tag in ({cases_with_tag}) '
        else:
            sql += '''and tag='{tag}'  '''
        sql += '''
                  group by bin
                  order by bin'''
        query_params = self.query_params.copy()
        return self.ch_client.select(sql, query_params=query_params)

    @property
    def overrides(self):
        """
        returns dict of data to override default plot parameters
        if necessary, elements are added separately for each Plot subclass
        """
        overrides = super(QuantilesVsTimes, self).overrides
        overrides['legend'] = {}
        overrides['legend']['enabled'] = False
        overrides['plotOptions'] = {'series': {'marker': {'enabled': None}}}
        if self.ticks > 15:
            overrides['xAxis']['tickInterval'] = self.ticks // 15
        return overrides

    @property
    def yaxis(self):
        return [{'label': '%',
                 'overrides': {'min': -5, 'max': 105},
                 'position': 'left',
                 'type': 'spline' if self.ticks > 1 else 'column',
                 }]

    @property
    def xaxis_type(self):
        if self.ticks == 1:
            return 'category'
        else:
            return 'linear'


class TimesDistPlot(DistPlot):
    title = 'Распределение времен ответов'
    graphs = 'Ответы',

    def get_chunk(self):
        chunk = {'Ответы': {
            'values': [v[1] for v in self.data_values],
            'color': '#0184d6',
            'type': 'column',
            'name': 'Ответы',
            }
        }
        return chunk

    @memoized_property
    def data_values(self):
        """
        returns tuple of tuples of times and resp count
        """
        sql = '''select bin/1000, toUInt32(sum(cnt)) as sum_cnt
                    from loaddb.rt_microsecond_histograms_buffer
                    where job_id={job}
                    and job_date=toDate({job_date})
                    '''
        if self.job_obj.multitag and self.tag:
            sql += 'and tag in ({cases_with_tag}) '
        else:
            sql += '''and tag='{tag}'  '''
        sql += '''
                  group by bin
                  order by bin'''
        query_params = self.query_params.copy()
        data = self.ch_client.select(sql, query_params=query_params)
        return data

    @property
    def ticks(self):
        """
        quantity of points on xAxis
        """
        return len([value[0] for value in self.data_values])

    @property
    def overrides(self):
        """
        returns dict of data to override default plot parameters
        if necessary, elements are added separately for each Plot subclass
        """
        overrides = super(TimesDistPlot, self).overrides
        overrides['xAxis']['gridLineWidth'] = 1
        overrides['legend'] = {}
        overrides['legend']['enabled'] = False
        if self.ticks > 15:
            overrides['xAxis']['tickInterval'] = self.ticks // 15
        return overrides

    @property
    def xaxis_type(self):
        return 'category'

    @property
    def yaxis(self):
        return [{'label': ' ',
                 'position': 'left',
                 'type': 'column',
                 }]


class QuantilesVsRps(DistPlot):
    title = 'Квантили vs RPS'
    graphs = '100', '99', '98', '95', '90', '85', '80', '75', '50'

    def get_chunk(self):
        xaxis_points = sorted(set(value[0] for value in self.data_values))
        chunk = OrderedDict()
        for quantile in self.graphs:
            chunk['{}%'.format(quantile)] = {
                'values': [],
                'color': self.color_mapping(quantile),
                'type': 'area',
                'name': '{}%'.format(quantile),
            }
        i = 0
        data_values_len = len(self.data_values)
        grouped = {point: {} for point in xaxis_points}
        # группируем за один проход для всех resps
        for point in xaxis_points:
            while i < data_values_len and self.data_values[i][0] == point:
                for time_to in self.data_values[i][1]:  # перебираем корзинки в этой секунде
                    if time_to in list(grouped[point].keys()):
                        grouped[point][time_to] += self.data_values[i][2][self.data_values[i][1].index(time_to)]
                    else:
                        grouped[point][time_to] = self.data_values[i][2][self.data_values[i][1].index(time_to)]
                i += 1
        for point in list(grouped.keys()):
            for q in self.graphs:
                pdata = list(Counter(grouped[point]))
                chunk['{}%'.format(q)]['values'].append(round(float(np.percentile(pdata, int(q))), 3))
        return chunk

    @memoized_property
    def data_values(self):
        """
        returns tuple of tuples
        """
        sql = '''select toUInt32(sum(cnt)), groupArray(toUInt32(bin)), groupArray(toUInt32(cnt))
                from loaddb.rt_microsecond_histograms_buffer
                where job_id={job}
                and job_date=toDate({job_date})
                '''
        if self.job_obj.multitag and self.tag:
            sql += 'and tag in ({cases_with_tag}) '
        else:
            sql += '''and tag='{tag}' '''
        sql += '''
                group by time
                '''
        query_params = self.query_params.copy()
        # clickhouse не умеет группировать или сортировать по сумме
        # сортируем данные по сумме (точка на оси абсцисс), чтобы потом сгруппировать за один проход.
        return sorted(self.ch_client.select(sql, query_params=query_params), key=lambda v: v[0])

    @property
    def graphs_settings(self):
        """
        settings for highcharts series
        """
        settings = [{'values': '{}%'.format(quantile),
                     'title': '{}%'.format(quantile),
                     'disabled': True if self.ticks > 1 else False,
                     'type': 'line' if self.ticks > 1 else 'column',
                     'color': self.color_mapping(quantile)} for quantile in ('100', '99')]
        settings += [{'values': '{}%'.format(quantile),
                      'title': '{}%'.format(quantile),
                      'color': self.color_mapping(quantile)} for quantile in ('98', '95', '90', '85', '80', '75', '50')]
        return settings

    @staticmethod
    def color_mapping(quantile):
        color_map = {'50': '#f85750',
                     '75': '#fe8233',
                     '80': '#fca43f',
                     '85': '#f2ce65',
                     '90': '#e9f66e',
                     '95': '#b8e13d',
                     '98': '#66af5f',
                     '99': '#7cb3f1',
                     '100': '#b2b8c8'}
        try:
            color = color_map[quantile]
            return color
        except KeyError:
            logging.warning('Unexpected quantile: {}'.format(quantile))
            return None

    @property
    def yaxis(self):
        return [{'label': 'ms',
                 'position': 'left',
                 'type': 'area' if self.ticks > 1 else 'column',
                 'graphs': self.graphs_settings
                 }]

    @property
    def xaxis_type(self):
        if self.ticks == 1:
            return 'category'
        else:
            return 'linear'


class QuantilesVsInstanses(DistPlot):
    title = 'Квантили vs Потоки'
    graphs = '100', '99', '98', '95', '90', '85', '80', '75', '50'

    def get_chunk(self):
        xaxis_points = sorted(set(value[0] for value in self.data_values))
        chunk = OrderedDict()
        for quantile in self.graphs:
            chunk['{}%'.format(quantile)] = {
                'values': [],
                'color': self.color_mapping(quantile),
                'type': 'area',
                'name': '{}%'.format(quantile),
            }
        i = 0
        data_values_len = len(self.data_values)
        grouped = {point: {} for point in xaxis_points}
        # группируем за один проход для всех threads
        for point in xaxis_points:
            while i < data_values_len and self.data_values[i][0] == point:
                for time_to in self.data_values[i][1]:  # перебираем корзинки в этой секунде
                    if time_to in list(grouped[point].keys()):
                        grouped[point][time_to] += self.data_values[i][2][self.data_values[i][1].index(time_to)]
                    else:
                        grouped[point][time_to] = self.data_values[i][2][self.data_values[i][1].index(time_to)]
                i += 1
        for point in list(grouped.keys()):
            for q in self.graphs:
                pdata = list(Counter(grouped[point]))
                chunk['{}%'.format(q)]['values'].append(round(float(np.percentile(pdata, int(q))), 3))
        return chunk

    @memoized_property
    def data_values(self):
        """
        returns tuple of tuples
        """
        sql = '''
                select thr, b, c from
                (
                select time, groupArray(toUInt32(bin)) as b, groupArray(toUInt32(cnt)) as c
                from loaddb.rt_microsecond_histograms_buffer
                where job_id={job}
                and job_date=toDate({job_date})
                '''
        if self.job_obj.multitag and self.tag:
            sql += 'and tag in ({cases_with_tag}) '
        else:
            sql += '''and tag='{tag}' '''
        sql += '''
                group by time
                ) all inner join
                (select time, threads as thr
                from loaddb.rt_microsecond_details_buffer
                where job_id={job}
                and job_date=toDate({job_date})
                '''
        if self.job_obj.multitag and self.tag:
            sql += 'and tag in ({cases_with_tag}) '
        else:
            sql += '''and tag='{tag}' '''
        sql += '''
                  )
                  using time
                '''
        query_params = self.query_params.copy()
        return sorted(self.ch_client.select(sql, query_params=query_params), key=lambda x: x[0])

    @property
    def graphs_settings(self):
        """
        settings for highcharts series
        """
        settings = [{'values': quantile + '%',
                     'title': quantile + '%',
                     'disabled': True if self.ticks > 1 else False,
                     'type': 'line' if self.ticks > 1 else 'column',
                     'color': self.color_mapping(quantile)} for quantile in ('100', '99')]
        settings += [{'values': quantile + '%',
                      'title': quantile + '%',
                      'color': self.color_mapping(quantile)} for quantile in ('98', '95', '90', '85', '80', '75', '50')]
        return settings

    @staticmethod
    def color_mapping(quantile):
        color_map = {'50': '#f85750',
                     '75': '#fe8233',
                     '80': '#fca43f',
                     '85': '#f2ce65',
                     '90': '#e9f66e',
                     '95': '#b8e13d',
                     '98': '#66af5f',
                     '99': '#7cb3f1',
                     '100': '#b2b8c8'}
        try:
            color = color_map[quantile]
            return color
        except KeyError:
            logging.warning('Unexpected quantile: {}'.format(quantile))
            return None

    @property
    def yaxis(self):
        return [{'label': 'ms',
                 'position': 'left',
                 'type': 'area' if self.ticks > 1 else 'column',
                 'graphs': self.graphs_settings
                 }]

    @property
    def xaxis_type(self):
        if self.ticks == 1:
            return 'category'
        else:
            return 'linear'


class AvgTimesVsRps(DistPlot):
    title = 'Средние времена vs RPS'
    graphs = 'connect_time', 'send_time', 'latency', 'receive_time'

    @staticmethod
    def color_mapping(time):
        color_map = {'connect_time': 'rgb(114,137,184)',
                     'send_time': 'rgb(117,193,79)',
                     'latency': 'rgb(255,94,82)',
                     'receive_time': 'rgb(255,230,132)'}
        try:
            color = color_map[time]
            return color
        except KeyError:
            logging.warning('Unexpected rt_fraction: {}'.format(time))
            return None

    def get_chunk(self):
        chunk = OrderedDict()
        for graph in self.graphs:
            index = self.graphs.index(graph) + 1
            chunk[graph] = {
                'values': [float(value[index]) if value[index] else 0 for value in self.data_values],
                'color': self.color_mapping(graph),
                'type': 'area',
                'name': graph,
            }
        return chunk

    @memoized_property
    def data_values(self):
        """
        returns tuple of tuples
        """
        sql = '''select
                resps,
                round(avg(connect_time_sum/1000/resps), 3),
                round(avg(send_time_sum/1000/resps), 3),
                round(avg(latency_sum/1000/resps), 3),
                round(avg(receive_time_sum/1000/resps), 3)
                from loaddb.rt_microsecond_details_buffer
                where job_id={job}
                and job_date=toDate({job_date})
                '''
        if self.job_obj.multitag and self.tag:
            sql += 'and tag in ({cases_with_tag}) '
        else:
            sql += '''and tag='{tag}'  '''
        sql += '''
                group by resps
                order by resps;'''
        fetched_data = self.ch_client.select(sql, query_params=self.query_params)
        return fetched_data

    @property
    def graphs_settings(self):
        """
        settings for highcharts series
        """
        return [{'values': time,
                 'title': time,
                 'color': self.color_mapping(time)} for time in self.graphs]

    @property
    def yaxis(self):
        return [{'stacking': 'normal',
                 'label': 'ms',
                 'position': 'left',
                 'type': 'area' if self.ticks > 1 else 'column',
                 'graphs': self.graphs_settings
                 }]

    @property
    def xaxis_type(self):
        if self.ticks == 1:
            return 'category'
        else:
            return 'linear'


class AvgTimesVsInstanses(DistPlot):
    title = 'Средние времена vs Потоки'
    graphs = 'connect_time', 'send_time', 'latency', 'receive_time'

    @staticmethod
    def color_mapping(time):
        color_map = {'connect_time': 'rgb(114,137,184)',
                     'send_time': 'rgb(117,193,79)',
                     'latency': 'rgb(255,94,82)',
                     'receive_time': 'rgb(255,230,132)'
                     }
        try:
            color = color_map[time]
            return color
        except KeyError:
            logging.warning('Unexpected time: {}'.format(time))
            return None

    def get_chunk(self):
        chunk = OrderedDict()
        for graph in self.graphs:
            index = self.graphs.index(graph) + 1
            chunk[graph] = {
                'values': [float(value[index]) if value[index] else 0 for value in self.data_values],
                'color': self.color_mapping(graph),
                'type': 'area',
                'name': graph,
            }
        return chunk

    @memoized_property
    def data_values(self):
        """
        returns tuple of tuples
        """
        sql = '''select
                threads,
                round(avg(connect_time_sum/1000/resps), 3),
                round(avg(send_time_sum/1000/resps), 3),
                round(avg(latency_sum/1000/resps), 3),
                round(avg(receive_time_sum/1000/resps), 3)
                from loaddb.rt_microsecond_details_buffer
                where job_id={job}
                and job_date=toDate({job_date})
                '''
        if self.job_obj.multitag and self.tag:
            sql += 'and tag in ({cases_with_tag}) '
        else:
            sql += '''and tag='{tag}' '''
        sql += '''
                group by threads
                order by threads;'''
        fetched_data = self.ch_client.select(sql, query_params=self.query_params)
        return fetched_data

    @property
    def graphs_settings(self):
        """
        settings for highcharts series
        """
        return [{'values': time,
                 'title': time,
                 'color': self.color_mapping(time)} for time in self.graphs]

    @property
    def yaxis(self):
        return [{'stacking': 'normal',
                 'label': 'ms',
                 'position': 'left',
                 'type': 'area' if self.ticks > 1 else 'column',
                 'graphs': self.graphs_settings
                 }]

    @property
    def xaxis_type(self):
        if self.ticks == 1:
            return 'category'
        else:
            return 'linear'
