# -*- coding: utf-8 -*-

"""
Модуль проверяет значения определяя, попадают ли они в заданный интервал.
Интервал задается двумя значениями:
    - M{d} - ширина интервала вниз от значения в процентах от самого значения,
    - M{u} - аналогичная ширина интервала вверх.

Вычисляются два значения:

M{down = pv - d * pv, down >= 0} (L{down_value})

M{up = pv + u * pv} (L{up_value})

Здесь M{pv} - предыдущее значение котировки.

Новое значение должно попадвать в интервал (down, up), то есть должно
выполняться условие M{down < v < up}.

FIXME: если за один раз считывается несколько значений, то проверка может не
       пройти (так как значений ещё нет в базе данных)
"""

__author__ = "Zasimov Alexey"
__email__ = "zasimov-a@yandex-team.ru"


from stocks3.core.checker import Checker
from stocks3.core.factories import checkers
from stocks3.core.config import str_to_bool


def down_value(down, value):
    assert type(down) == float
    assert type(value) == float
    return (1.0 - down/100.0) * value

def up_value(up, value):
    assert type(up) == float
    assert type(value) == float
    return (1.0 + up/100.0) * value


class UpDownChecker(Checker):
    """
    Проверяет новое значение на пригодность.

    Для каждой котировки может быть задан доверительный интервал:

    C{<checkers up="up" down="down">}

    C{<check quote="QuoteName" up="up" down="down"/>}

    C{</checkers>}

    Если какую-то котировку не надо проверять, следует установить флаг nocheck="True":

    C{<check quote="QuoteWithoutCheck" nocheck="True"/>}

    Интервал может быть задан глобально (в теге checkers) и локально для каждой
    котировки.
    """
    def makeConfig(self):
        self.up = self.readFloat("", "up")
        self.down = self.readFloat("", "down")
        self._load_ids_checks()
        return self

    def _load_ids_checks(self):
        """
        Загружает ограничения для конкретных котировок.
        Обрабатывает элементы checkers/checker.
        """
        self._ids_checks = {}
        for checkNode in self.node.findall("check"):
            active = str_to_bool(checkNode.attrib.get("active", "True"))
            self._ids_checks[checkNode.attrib["quote"]] = \
                    (active, float(checkNode.attrib["up"]), float(checkNode.attrib["down"]))

    def check_value(self, up, down, current, previous):
        """
        Проверяет, попадает ли значение current в заданный доверительный
        интервал.
        """
        d = down_value(down, previous)
        if d < 0:
            d = 0
        u = up_value(up, previous)
        if d < current < u:
            return True
        else:
            return False

    def get_restriction(self, price):
        """
        Для котировки price возвращает границы установленного в конфигурации
        доверительного интервала.
        """
        quoteName = self.source.default.quotes_config.getQuoteName(price.quote.quote_id)
        if quoteName in self._ids_checks:
            return self._ids_checks[quoteName]
        else:
            return True, self.up, self.down

    def check(self, price):
        """
        Получает предыдущее значение котировки и, если оно есть, выполняет
        проверку.
        """
        prev = self.saver.db.select_previous_value(self.source, price)
        if prev is not None:
            active, up, down = self.get_restriction(price)
            if not active:
                return True
            if self.check_value(up, down, price.buy.value, prev.buy.value):
                if price.is_dual_price and prev.is_dual_price:
                    return self.check_value(up, down, price.sell.value, prev.sell.value)
                else:
                    return True
            else:
                return False
        else:
            return True

checkers.register("stocks3.checkers.UpDown", UpDownChecker)

