# coding: utf-8

from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals

import logging
from functools import partial
import datetime

from cached_property import cached_property
from infra.yasm.yasmapi import GolovanRequest, RtGolovanRequest
from saas.library.python.yasm.constants import YasmPeriod

from saas.library.python.yasm.common import get_interval_begin_end
import numpy


class YasmSignal(object):
    LOGGER = logging.getLogger(__name__)

    def __init__(self, signal, itype, **tags):
        self.signal = signal
        self._itype = itype
        self._tags = tags

    @cached_property
    def tags_list(self):
        tags_list = ['{}={}'.format(k, v) for k, v in self._tags.items()]
        tags_list.append('itype={}'.format(self._itype))
        return sorted(tags_list)

    @cached_property
    def tags_str(self):
        return ';'.join(self.tags_list)

    @classmethod
    def from_str(cls, yasm_str):
        tags_str, signal_name = yasm_str.split(':')
        tags_tuple_list = [t.split('=') for t in tags_str.split(';')]
        tags_dict = dict(tags_tuple_list)
        itype = tags_dict.pop('itype')
        return cls(signal_name, itype, **tags_dict)

    def iterate_rt(self, host='ASEARCH'):
        for p in RtGolovanRequest({host: {self.tags_str: [self.signal, ]}}):
            yield p.ts, p.values[host][self.tags_str][self.signal]

    def iterate_interval(self, interval_begin, interval_end, period=YasmPeriod.five_seconds, host='ASEARCH', normalize=False):
        selector = str(self)
        period, interval_begin, interval_end = get_interval_begin_end(period, interval_begin, interval_end)
        for ts, values in GolovanRequest(host, period.value, interval_begin, interval_end, [selector, ]):
            value = values[selector] if values[selector] else 0
            yield ts, float(value) if not value else float(value)/(period.value if normalize else 1)

    @property
    def signal_iterator(self):
        return YasmSignalIterator(self.iterate_interval)

    def __str__(self):
        return '{tags_str}:{signal}'.format(tags_str=self.tags_str, signal=self.signal)

    def __repr__(self):
        tags = ', '.join(['{k}="{v}"'.format(k=k, v=v) for k, v in self._tags.items()]) if self._tags else '**{}'
        return 'YasmSignal("{signal}", "{itype}", {tags})'.format(
            signal=self.signal, itype=self._itype, tags=tags)

    def __eq__(self, other):
        if isinstance(other, YasmSignal):
            return self.signal == other.signal and self._itype == other._itype and self._tags == other._tags
        else:
            return NotImplemented

    def __hash__(self):
        return hash(self.__str__())

    def __gt__(self, other):
        if isinstance(other, YasmSignal):
            if self.signal > other.signal:
                return True
            elif self.signal < other.signal:
                return False
            else:
                if self._itype > other._itype:
                    return True
                elif self._itype < other._itype:
                    return False
                else:
                    return self.tags_list > other.tags_list

    def __lt__(self, other):
        if isinstance(other, YasmSignal):
            if self.signal < other.signal:
                return True
            elif self.signal > other.signal:
                return False
            else:
                if self._itype < other._itype:
                    return True
                elif self._itype > other._itype:
                    return False
                else:
                    return self.tags_list < other.tags_list


class YasmSignalIterator(object):
    LOGGER = logging.getLogger(__name__)

    def __init__(self, iterator):
        self.iterator = iterator

    def __getattr__(self, item):
        if 'last' in item:
            items = item.split("_")
            items.remove('last')
            time_period = "_".join(items)
            if YasmPeriod.contains(time_period):
                interval_end = datetime.datetime.now()
                interval_begin = interval_end - datetime.timedelta(seconds=YasmPeriod[time_period].value)
                iterator_copy = YasmSignalIterator(partial(self.iterator, interval_begin, interval_end))
                return iterator_copy
        raise AttributeError('A instance has no attribute {}'.format(item))

    def mean(self, *args, **kwargs):
        summ = 0
        periods_count = 0
        for _, value in self.iterator(*args, **kwargs):
            summ += value
            periods_count += 1

        if periods_count == 0:
            self.LOGGER.error('No values in interval')
            return 0
        else:
            return summ/periods_count

    def max(self, *args, **kwargs):
        maximum = None
        for _, value in self.iterator(*args, **kwargs):
            if maximum is None or value > maximum:
                maximum = value

        if maximum is None:
            self.LOGGER.error('No values in interval')
            return 0
        else:
            return maximum

    def min(self, *args, **kwargs):
        minimum = None
        for _, value in self.iterator(*args, **kwargs):
            if minimum is None or value < minimum:
                minimum = value

        if minimum is None:
            self.LOGGER.error('No values in interval')
            return 0
        else:
            return minimum

    def percentile(self, perc, *args, **kwargs):
        return float(numpy.percentile(self.values(*args, **kwargs), perc))

    def median(self, *args, **kwargs):
        return float(numpy.median(self.values(*args, **kwargs)))

    def std(self, *args, **kwargs):
        return float(numpy.median(self.values(*args, **kwargs)))

    def corrcoef(self, other_array, *args, **kwargs):
        return numpy.corrcoef(self.values(*args, **kwargs), other_array)

    def cov(self, other_array, *args, **kwargs):
        return numpy.cov(self.values(*args, **kwargs), other_array)

    def values(self, *args, **kwargs):
        return [el[1] for el in self(*args, **kwargs)]

    def __call__(self, *args, **kwargs):
        return self.iterator(*args, **kwargs)
