# -*- coding: utf-8 -*-
"""
KISS rule is being violated here.
"""

import re
import math
import itertools as itt


class LoadSchemeError(Exception):
    pass


class WrongDurationFormat(Exception):
    pass


def parse_duration(duration):
    """
    Parse duration string, such as '3h2m3s' into milliseconds

    >>> parse_duration('3h2m3s')
    10923000

    >>> parse_duration('0.3s')
    300

    >>> parse_duration('5')
    5000
    """
    _re_token = re.compile("([0-9.]+)([dhms]?)")

    def parse_token(time, multiplier):
        multipliers = {
            'h': 3600,
            'm': 60,
            's': 1,
        }
        if multiplier:
            if multiplier in multipliers:
                return int(float(time) * multipliers[multiplier] * 1000)
            else:
                raise WrongDurationFormat(
                    'Failed to parse duration: %s' % duration)
        else:
            return int(float(time) * 1000)

    return sum(parse_token(*token) for token in _re_token.findall(duration))


class LoadSchemeIterator:
    """
    Iterate through loadscheme and return LoadScheme objects

    >>> lsi = LoadSchemeIterator(['line(1,10,15m)', 'step(1,10,2,5)', 'const(2,30)'], "XXX-1251")
    >>> list(lsi)
    [{'load_from': 1, 'sec_to': 900, 'up': 'XXX-1251', 'load_to': 10, 'load_type': 3, 'sec_from': 0,
    'dsc': 'line(1,10,15m)'}, {'load_from': 1, 'sec_to': 905, 'up': 'XXX-1251', 'load_to': 1, 'load_type': 2,
    'sec_from': 900, 'dsc': 'step(1,10,2,5)'}, {'load_from': 3, 'sec_to': 910, 'up': 'XXX-1251', 'load_to': 3,
    'load_type': 2, 'sec_from': 905, 'dsc': 'step(1,10,2,5)'}, {'load_from': 5, 'sec_to': 915, 'up': 'XXX-1251',
    'load_to': 5, 'load_type': 2, 'sec_from': 910, 'dsc': 'step(1,10,2,5)'}, {'load_from': 7, 'sec_to': 920, 'up':
    'XXX-1251', 'load_to': 7, 'load_type': 2, 'sec_from': 915, 'dsc': 'step(1,10,2,5)'},
    {'load_from': 9, 'sec_to': 925, 'up': 'XXX-1251', 'load_to': 9, 'load_type': 2, 'sec_from': 920,
    'dsc': 'step(1,10,2,5)'}, {'load_from': 2, 'sec_to': 955, 'up': 'XXX-1251', 'load_to': 2, 'load_type': 1,
    'sec_from': 925, 'dsc': 'const(2,30)'}]
    """

    rx = re.compile(r'(\w+)')

    def __init__(self, loadscheme, job):
        self.loadscheme = loadscheme
        self.job = job
        self.time_from = 0
        self.lso_producers = {
            'const': self._const,
            'step': self._step,
            'line': self._line,
            'once': self._const,
        }
        self.lt_codes = {
            'const': 1,
            'step': 2,
            'line': 3,
            'once': 4,
        }

    def _const(self, dsc, lt, rps, duration):
        lso = {
            'up': self.job,
            'load_type': lt,
            'sec_from': self.time_from,
            'sec_to': self.time_from + duration,
            'load_from': int(math.ceil(rps)),
            'load_to': int(math.ceil(rps)),
            'dsc': dsc,
        }
        self.time_from += duration
        yield lso

    def _step(self, dsc, lt, minrps, maxrps, increment, duration):
        if maxrps < minrps:
            increment = -increment
        n_steps = int((maxrps - minrps) / increment)
        return itt.chain(*(
            self._const(dsc, lt, minrps + i * increment, duration)
            for i in range(0, n_steps + 1)
        ))

    def _line(self, dsc, lt, minrps, maxrps, duration):
        lso = {
            'up': self.job,
            'load_type': lt,
            'sec_from': self.time_from,
            'sec_to': self.time_from + duration,
            'load_from': int(math.ceil(minrps)),
            'load_to': int(math.ceil(maxrps)),
            'dsc': dsc,
        }
        self.time_from += duration
        yield lso

    def _parse_lso_elem(self, elem):
        params = LoadSchemeIterator.rx.findall(elem)
        load_type = params[0]
        if load_type in self.lso_producers:
            params[-1] = parse_duration(params[-1]) / 1000
            return self.lso_producers[load_type](
                elem,
                self.lt_codes[load_type],
                *map(lambda x: int(x), params[1:])
            )
        else:
            raise LoadSchemeError("Unknown load type: %s" % load_type)

    def __iter__(self):
        return itt.chain(*map(self._parse_lso_elem, self.loadscheme))


class PseudoLoadscheme(object):
    """
    Пустышка для лоадсхемы, вытащенной из конфигинфо
    """

    def __init__(self, up, load_type, sec_from, sec_to, load_from, load_to, dsc):
        self.up = up
        self.load_type = load_type
        self.sec_from = sec_from
        self.sec_to = sec_to
        self.load_from = load_from
        self.load_to = load_to
        self.dsc = dsc
