# coding: utf-8
""" Реализация классов логирования

Метки в журнале о уровне сообщения:
  "`": Debug
  ".": Info
  "*": Warning
  "!": Error
  "#": Alert

"""

from time import monotonic
from datetime import timedelta
from traceback import extract_tb, extract_stack
from typing import Optional, Any, Iterable
from sys import exc_info


class Timing:
    """\
    Организация работы с таймерами
    """
    def __init__(self, name: Optional[str] = None):
        if name is None:
            self.prefix = ''

        else:
            self.prefix = f'{name} :: '

        self.ts_all = monotonic()
        self.ts = self.ts_all

    def get_time(self):
        return monotonic() - self.ts

    def reset(self):
        self.ts = monotonic()
        self.ts_all = self.ts

    def __str__(self):
        ts = monotonic()
        return self.prefix + '%s(%.4f)' % (timedelta(seconds=(ts - self.ts_all)), ts - self.ts)

    def __call__(self, msg):
        _buf = f'{self} | {msg}'
        self.ts = monotonic()
        return _buf


class AbstractLogBase:
    def __init__(self, prefix: str = 'main'):
        self.prefix = prefix

    def _write_helper(self, mark: str, msg: Any) -> Iterable[str]:
        for l in str(msg).splitlines():
            yield f'{mark} {self.prefix} | {l}\n'

    def _write(self, mark: str, msg: Any):
        raise NotImplemented(f'Метод write не определён для класса "{type(self).__name__}"')

    def __call__(self, msg):
        self._write('.', msg)

    def err(self, msg):
        self._write('!', msg)

    def warn(self, msg):
        self._write('*', msg)

    def alert(self, msg):
        self._write('#', msg)

    def debug(self, msg):
        self._write('`', msg)

    @staticmethod
    def get_timing(name: Optional[str] = None):
        return Timing(name)

    @staticmethod
    def format_msg(msg: str, **kwa) -> str:
        """\
        Создаём сообщение, более дружелюбное к средствам разбора логов.
        Ориентир на ``victoria_logs`` и её ``unpack_logfmt``

        :param msg: Основное сообщение
        :param kwa: Набор ключ-значение, для добавления в сообщения в качестве
           разбираемых системой элементов, пригодных для фильтрации
        :returns: Строку, которую необходимо отправить в лог методом с нужным уровнем важности.
        """
        data = ''
        for k, v in kwa.items():
            _v = str(v)
            if v.find(' ') != -1:
                v = f'"{v}"'

            data += f' {k}={v}'

        if not data:
            return msg

        else:
            return f'{msg} |{data}'

    def sub_log(self, name: str):
        return self.__class__(f'{self.prefix}/{name}')

    def excpt(self, msg, e_class=None, e_obj=None, e_tb=None, stack_skip=0):
        if e_class is None:
            e_class, e_obj, e_tb = exc_info()

        tb_data_tb = list(extract_tb(e_tb))[::-1]
        tb_data_stack = list(extract_stack())[::-1][(2 + stack_skip):]
        self.alert(msg)
        self.alert('--- EXCEPTION ---')
        self.alert(f' {e_class.__name__} ({e_obj})')
        self.alert('--- TRACEBACK ---')
        for _tb_file, _tb_line, _tb_func, _tb_text in tb_data_tb:
            self.alert(f'File: {_tb_file}, line {_tb_line} in {_tb_func}')
            self.alert(f'   {_tb_text}')

        self.alert('>>> Exception Handler <<<')
        for _tb_file, _tb_line, _tb_func, _tb_text in tb_data_stack:
            self.alert(f'File: {_tb_file}, line {_tb_line} in {_tb_func}')
            self.alert(f'   {_tb_text}')

        self.alert('--- END EXCEPTION ---')


class NullLog(AbstractLogBase):
    def _write(self, mark: str, msg: Any):
        pass

    def __init__(self, *a, **kwa):
        super().__init__('n')

    def sub_log(self, name: str):
        return self

    @classmethod
    def make(cls, *a, **kwa):
        return cls()

    def flush(self, *a, **kwa):
        pass

    @staticmethod
    def init_syslog(ident):
        pass
