# -*- coding: utf-8 -*-
"""
Генерирует экспортные файлы по шаблонам.
"""
from __future__ import with_statement, print_function
import os
import pystache
from datetime import datetime
from stocks3.core.config import Configurable, ConfigurationError, str_to_bool
from stocks3.core.stock import Quote
from stocks3.core.exception import S3Exception, throw_only
from stocks3.core.sharedregion import SHARED_REGION, fix_earth_region
from stocks3.share.savethroughtmp import save_through_tmp, with_temp
from stocks3.share.format_func import format_func
import stocks3.core.group
from stocks3.share.geobase import is_subregion
from stocks3.share import messages

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


class S3TemplaterError(S3Exception):
    STAGE = "templater"


class Templater(Configurable):
    TEMPFILE_SUFFIX = "stocks3_templater"

    def __init__(self, tree, node, exporter, calculator):
        self.flagName = None
        self.template = None
        self._template_content = None
        self.output_directory = None
        self.output_file = None
        self.filename_template = None
        self.no_region_filename_template = None
        self.skip_zero_delta = False
        self._quotes_info = {}
        self._normal_10000 = False
        self._bad_regions = None
        self._good_regions = None

        self.exporter = exporter
        self.calculator = calculator
        Configurable.__init__(self, tree, node)

    def makeConfig(self):
        """
        Есть такая штука - flag. Шаблонизатор ищет установленный флаг с
        указанным именем у котировки. Если флаг отсутствует или снят, котировка
        просто пропускается.
        """
        self.flagName = self.readString("", "flag")
        self.template = self.readString("", "template")
        self.output_directory = self.readString("", "output-directory")
        self.output_file = self.readString("", "output-file", "") or None
        self.filename_template = self.readString("", "filename-template", "") or None
        self.no_region_filename_template = self.readString("", "no-region-filename-template", "") or None
        if self.output_file is None and self.filename_template is None:
            raise ConfigurationError("Expected filename or filename template.")
        # Пропускать ли значения с нулевой дельтой?
        self.skip_zero_delta = self.readBool("", "skip-zero-delta", False)
        # Дополнительные настройки для котировок.
        self._quotes_info = {}
        for node in self.node.findall("quotes/quote"):
            quote_name = node.attrib["name"]
            quote_id = self.exporter.default.quotes_config.getQuote(quote_name)
            try:
                skip_zero_delta = str_to_bool(node.attrib["skip-zero-delta"])
            except KeyError:
                skip_zero_delta = None
            self._quotes_info[quote_id] = {"skip-zero-delta": skip_zero_delta}

        self._normal_10000 = self.readBool("", "normal-10000", False)

        # Для фильтрации значений по региону.
        self._bad_regions = self._load_regions("bad-regions")
        self._good_regions = self._load_regions("good-regions")

    def _load_regions(self, root):
        """
        Загружает список регионов из конфигурации.
        """
        regions = {}
        for quote in self.node.findall("%s/quote" % root):
            quote_name = quote.attrib["name"]
            quote_id = self.exporter.default.quotes_config.getQuote(quote_name).quote_id
            for region in quote.findall("region"):
                region_id = int(region.text)
                try:
                    regions[quote_id].append(region_id)
                except KeyError:
                    regions[quote_id] = [region_id]
        return regions

    def _get_skip_zero_delta_for_price(self, price):
        """
        Определяем значение флажка skip_zero_delta для этого значения.
        """
        try:
            skip_zero_delta = self._quotes_info[price.quote.quote_id]["skip-zero-delta"]
        except KeyError:
            skip_zero_delta = None
        if skip_zero_delta is None:
            return self.skip_zero_delta
        else:
            return skip_zero_delta

    @staticmethod
    def _is_bad_region(good_regions, bad_regions, region):
        """
        Если есть список хороших регионов, то наш регион должен быть в списке
        хороших, но его все равно не должно быть в списке плохих.
        """
        if len(good_regions) != 0:
            # Есть ли наш регион в списке хороших?
            if region not in good_regions:
                is_good = False
                for good in good_regions:
                    # Если регион является подрегионом хорошего региона - то он
                    # хороший.
                    if is_subregion(region, good):
                        is_good = True
                        break
                # Если региона нет в списке хороших, то он автоматом плохой.
                if not is_good:
                    return True

        if region in bad_regions:
            return True

        # Если регион является подрегионом плохого - то он плохой.
        for bad in bad_regions:
            if is_subregion(region, bad):
                return True

        # Регион хороший.
        return False

    def _filter_prices(self, quote, prices):
        """
        Отбрасываем ненужные регионы.
        """
        try:
            good_regions = self._good_regions[quote.quote_id]
        except KeyError:
            good_regions = []

        try:
            bad_regions = self._bad_regions[quote.quote_id]
        except KeyError:
            bad_regions = []

        res = {}

        for region, data in prices.items():
            if not self._is_bad_region(good_regions, bad_regions, region):
                res[region] = data
            else:
                messages.info("region %s for quote %s skipped" % (region, quote.quote_id))

        return res

    def need_skip_price(self, price):
        """
        Возвращает True, если значение не должно попасть в экспорт.

        Эта функция используется из filter_prices, которая МОЖЕТ быть вызвана
        из шаблона.
        """
        skip_zero_delta = self._get_skip_zero_delta_for_price(price)
        if price.is_dual_price:
            is_zero_delta = price.sell.absolute == 0 and price.buy.absolute == 0
        else:
            is_zero_delta = price.buy.absolute == 0
        return skip_zero_delta and is_zero_delta

    def filter_prices(self, prices):
        """
        Фильтрует данные, отбрасывая ненужные значения.
        """
        if len(prices) == 0:
            return prices
        return [prices[0]] + list(filter(lambda price: not self.need_skip_price(price), prices[1:]))

    def prepare_filename(self, filename_template, quote, region=None, suffix=None):
        """
        Возвращает имя выходного файла для котировки quote и региона
        region.
        """
        filename = filename_template.format(region=str(fix_earth_region(region)), id=str(quote.quote_id), suffix=str(suffix))
        if not self._normal_10000 and (region is None or region == SHARED_REGION):
            # Здесь мы просто срезаем все папки, оставляя только имя файла.
            if self.no_region_filename_template is None:
                return os.path.basename(filename)
            else:
                return self.no_region_filename_template.format(id=str(quote.quote_id), suffix=str(suffix))
        else:
            return filename

    def get_output_file_name(self, quote, region=None, suffix=None):
        if self.output_file is not None:
            return self.output_file
        else:
            return self.prepare_filename(self.filename_template, quote, region=region, suffix=suffix)

    def get_data(self, ns):
        if not self._template_content:
            with open(self.template) as f:
                self._template_content = f.read()
        return pystache.render(self._template_content, ns)

    def with_temp(self, filename, handler, *args):
        full_name = os.path.join(self.output_directory, filename)
        with_temp(full_name, handler, *args)

    def save_through_tmp(self, ns, output_filename):
        # Получаем данные от шаблонизатора и сохраняем их во временном
        # файле.
        data = self.get_data(ns)
        save_through_tmp(self.output_directory, output_filename, data, self.TEMPFILE_SUFFIX)

    @staticmethod
    def bool_to_flag(b):
        assert type(b) == bool, "bool_to_flag"
        return "1" if b else "0"

    def make_default_env(self):
        """
        Выдает словарь с окружением по умолчанию.
        """
        ns = {"calculator": self.calculator,
              "format": format_func,
              "filter_prices": self.filter_prices,
              "bool_to_flag": self.bool_to_flag
              }
        stocks3.core.group.update_env(ns)
        return ns

    def push(self, quote, prices):
        """
        Вызывается из калькулятора. Этот метод должен сохранить значения
        prices, привязанные к котировке quote.
        """
        assert isinstance(quote, Quote), "Expected Quote, but received %s" % quote.__class__

    def flush(self):
        """
        Вывод ранее сохраненных значений.
        """
        pass

    def is_my_quote(self, quote):
        return quote.has_flag(self.flagName)

    @throw_only(S3TemplaterError)
    def run_push(self, quote, prices):
        """
        Вызывается из qcalculator'a.
        """
        prices = self._filter_prices(quote, prices)
        if self.is_my_quote(quote):
            self.push(quote, prices)
        # Пробегаемся по псевдонимам
        if quote.alias_quote:
            if self.is_my_quote(quote.alias_quote):
                self.push(quote.alias_quote, prices)

    @throw_only(S3TemplaterError)
    def run_flush(self):
        return self.flush()

    def check_if_need_update_file(self, filename, prices, update_modify_time=True):
        """
        Проверяем, нужно ли переписывать файл на диске на основе данных о времени последнего изменения файла и котировки
        :param filename: string
        :param prices: [Price]
        :param update_modify_time: boolean
        :return: boolean
        """
        if len(prices) < 1:     # Но такого конечно быть не может
            return True
        file = os.path.join(self.output_directory, filename)
        if not os.path.exists(file):
            return True
        # print('file time:', int(os.path.getmtime(file)), 'stock time:', int(prices[0].updated))
        if prices[0].updated is None or int(os.path.getmtime(file)) < int(prices[0].updated):
            return True
        else:
            if update_modify_time:
                os.utime(file, (datetime.now().timestamp(), datetime.now().timestamp()))
            return False
