# -*- coding: utf-8 -*-
"""
Created on Jul 25, 2013

@author: noob
"""

from common.models import Job
from common.util.clients import ClickhouseClient, CacheClient
from common.util.decorators import Memoize
from hashlib import md5
from itertools import izip
import logging
import os


class Plot(object):
    """
    Plots and Tables Superclass
    """

    def __init__(self, job, slider_start, slider_end, compress_ratio, case, custom_metric=None):
        """
        :param job: Job NUMBER
        :param slider_start: string
        :param slider_end: string
        :param compress_ratio: int
        :param case: string
        :param custom_metric: if not None looks like this "customg:group_poop" or "customs:nogroup_soup"
        """

        # input params
        self.job = job
        self.slider_start = int(slider_start) if slider_start else slider_start
        self.slider_end = int(slider_end) if slider_end else slider_end
        self.compress_ratio = int(compress_ratio)

        self.raw_case = case
        self.custom_metric = custom_metric

        # meta attributes
        self.cache = CacheClient()
        self.ch_client = ClickhouseClient()

        self.subtitle = ''  # можно использовать в качестве сообщения для пользователя.

        self.series_limit = 10

    @property
    @Memoize
    def md5_cases(self):
        return dict(izip([md5(c.encode('utf-8')).hexdigest() for c in self.job_obj.tags if c],
                         [c for c in self.job_obj.tags if c]))

    @property
    @Memoize
    def case(self):
        try:
            case = self.md5_cases[self.raw_case]
        except KeyError:
            case = self.raw_case if self.raw_case.replace('<', '&lt;').replace('>', '&gt;') in self.job_obj.tags else ''
        return case.replace('&lt;', '<').replace('&gt;', '>')

    @property
    def tag(self):
        """
        alias
        """
        return self.case

    @property
    @Memoize
    def job_obj(self):
        try:
            job_obj = Job.check_job(Job.objects.get(n=self.job))
            return job_obj
        except Job.Deleted:
            logging.exception('Job had been deleted')
            return None
        except:
            logging.exception("Could not get job OBJECT for plot due to:")
            return None

    @property
    def query_params(self):
        query_params = self.job_obj.basic_query_params.copy()
        query_params['start'] = self.slider_start
        query_params['end'] = self.slider_end
        query_params['compress_ratio'] = self.compress_ratio
        query_params['cases_with_tag'] = ','.join(
            ["'" + case + "'" for case in self.job_obj.cases if self.tag in case.split('|')])
        query_params['tag'] = self.tag
        return 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
        """
        return {}

    @property
    def xaxis_type(self):
        """
        returns string
        None by default
        can be "datetime", "category"
        """
        return None


class TimelinePlot(Plot):
    """
    Upper class for all timeline plots
    """

    table = False
    xaxis_type = 'datetime'
    graphs = []
    times = []
    data = {}
    scheme_data = []

    @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(TimelinePlot, self).overrides
        if 'xAxis' not in overrides.keys():
            overrides['xAxis'] = {}
        overrides['xAxis']['dateTimeLabelFormats'] = {'millisecond': '%H:%M:%S'}
        return overrides

    @property
    @Memoize
    def scheme_type(self):
        color_map = {
            'rps': '#800000',
            'instances': '#ff00ff',
        }
        return {'type': self.job_obj.scheme_type, 'color': color_map[self.job_obj.scheme_type]}

    def get_data(self):
        """
        Main function
        compresses data and times
        """
        try:
            self.get_times_and_scheme()
            data = {"point": self.times}
            for graph in self.graphs:
                data[graph] = self.data.get(graph, [0] * len(self.times))
            logging.debug("Got case %s data for job %s", self.tag, self.job)
            return data
        except:
            logging.exception("Could not get data for case %s for job %s due to:", self.tag, self.job)
            return None

    def get_times_and_scheme(self):
        """
        gets
        uncompressed times for data
        compressed times for xaxis points
        and scheme data (made in one method not to rape db)
        calls "compress_data", "compress_threads" and "compress_times" methods
        """
        cache_key = 'job_%s_test_data_times_and_scheme_case_%s_start_%s_end_%s' % \
                       (self.job, md5(self.tag.encode('utf-8')).hexdigest(), self.slider_start, self.slider_end)
        if os.path.exists('/etc/testing'):
            self.cache.delete(cache_key)
        # getting raw data
        try:
            fetched_data = self.cache.get(cache_key)
            if fetched_data:
                logging.info('Found test_data times and scheme for %s', cache_key)
            else:
                logging.info('No test_data times and scheme for %s', cache_key)
                raise KeyError
        except (KeyError, UnicodeEncodeError):
            sql = 'select intDiv(toUInt32(time), %(compress_ratio)s)*%(compress_ratio)s as t, '
            if self.scheme_type['type'] == 'rps':
                sql += 'avg(reqps) '
            else:
                sql += 'avg(threads) '
            sql += '''from loaddb.rt_microsecond_details_buffer
                    where job_id=%(job)s
                    and job_date=toDate(%(job_date)s) 
                    and tag=''
                    and time >= toDateTime(%(start)s) 
                    and time <= toDateTime(%(end)s)
                    group by t
                    order by t'''
            query_params = self.job_obj.basic_query_params.copy()
            query_params.update({
                'start': self.slider_start,
                'end': self.slider_end,
                'compress_ratio': self.compress_ratio,
            })
            fetched_data = self.ch_client.select(sql, query_params=query_params)
            try:
                self.cache.set(cache_key, fetched_data)
            except UnicodeEncodeError:
                pass
                # processing raw data
        try:
            self.times = [value[0] for value in fetched_data]
            if self.scheme_type['type'] == 'instances':
                self.scheme_data = [value[1] if value[1] else None for value in fetched_data]
            else:
                self.scheme_data = [value[1] if value[1] else 0 for value in fetched_data]
            logging.debug('Got times and scheme_data for %s job %s. their lengths are: %s %s', self.tag, self.job,
                          len(self.times), len(self.scheme_data))
        except:
            logging.exception('Could not get times and scheme_data for %s job %s due to:', self.tag, self.job)
            self.times, self.times, self.scheme_data = None, None, None


class DistPlot(Plot):
    """
    Upper class for all vs plots
    """

    scheme_type = None
    table = False
    data_values = []
    xaxis_type = 'linear'

    @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(DistPlot, self).overrides
        if 'xAxis' not in overrides.keys():
            overrides['xAxis'] = {}
        overrides['xAxis']['type'] = 'linear'
        overrides['xAxis']['categories'] = sorted(set([value[0] for value in self.data_values]))
        overrides['xAxis']['gridLineWidth'] = 1
        if self.ticks > 15:
            overrides['xAxis']['tickInterval'] = self.ticks / 15
        return overrides

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

    @property
    def query_params(self):
        query_params = self.job_obj.basic_query_params.copy()
        query_params.update({
            'start': self.slider_start,
            'end': self.slider_end,
            'cases_with_tag': ','.join(
                ["'" + case + "'" for case in self.job_obj.cases if self.tag in case.split('|')]),
            'tag': self.tag,
        })
        return query_params


class Table(Plot):
    """
    Upper class for all tables
    """

    table = True
    yaxis = None
    rows = None
    overrides = {'chart': {'borderWidth': 0}}

    def get_columns(self):
        raise NotImplementedError()

    def get_data(self):
        columns = self.get_columns()
        data = {'header': [c['title'] for c in columns],
                'rows': self.rows,
                'columns': columns}
        logging.debug('Got TABLE data %s', data)
        return data

    @staticmethod
    def count_percentage(count_data):
        """
        returns list
        :param count_data: list of count
        """
        summa = float(sum(count_data))
        percent_values = [str(round(float(value) * 100 / summa, 3)) + '%'
                          for value in count_data]
        return percent_values
