# -*- coding: utf-8 -*-
# vim: sw=4:ts=4:expandtab

"""
pygogo.formatters
~~~~~~~~~~~~~~~~~

Log formatters

Examples:
    Add a console formatter::

        >>> import sys

        >>> logger = logging.getLogger('console_logger')
        >>> hdlr = logging.StreamHandler(sys.stdout)
        >>> hdlr.setFormatter(console_formatter)
        >>> logger.addHandler(hdlr)
        >>> logger.info('hello world')
        console_logger: INFO     hello world

    Add a structured formatter::

        >>> from io import StringIO
        >>> from json import loads

        >>> s = StringIO()
        >>> logger = logging.getLogger('structured_logger')
        >>> hdlr = logging.StreamHandler(s)
        >>> hdlr.setFormatter(structured_formatter)
        >>> extra = {'key': 'value'}
        >>> logger.addHandler(hdlr)
        >>> logger.info('hello world', extra=extra)
        >>> result = loads(s.getvalue())
        >>> keys = sorted(result.keys())
        >>> keys == ['key', 'level', 'message', 'msecs', 'name', 'time']
        True
        >>> [result[k] for k in keys if k not in {'msecs', 'time'}] == [
        ...     'value', 'INFO', 'hello world', 'structured_logger']
        True

Attributes:
    BASIC_FORMAT (str): A basic format

    CONSOLE_FORMAT (str): A format for displaying in a console

    FIXED_FORMAT (str): A fixed width format

    CSV_FORMAT (str): A csv format

    JSON_FORMAT (str): A json format

    DATEFMT (str): Standard date format
"""

import logging
import sys
import traceback
import itertools as it

from .utils import CustomEncoder

BASIC_FORMAT = "%(message)s"
BOM_FORMAT = "\ufeff%(message)s"
CONSOLE_FORMAT = "%(name)-12s: %(levelname)-8s %(message)s"
FIXED_FORMAT = "%(asctime)s.%(msecs)-3d %(name)-12s %(levelname)-8s %(message)s"
CSV_FORMAT = '%(asctime)s.%(msecs)d,%(name)s,%(levelname)s,"%(message)s"'
JSON_FORMAT = (
    '{"time": "%(asctime)s.%(msecs)d", "name": "%(name)s", "level":'
    ' "%(levelname)s", "message": "%(message)s"}'
)

DATEFMT = "%Y-%m-%d %H:%M:%S"

module_hdlr = logging.StreamHandler(sys.stdout)
module_logger = logging.getLogger(__name__)
module_logger.addHandler(module_hdlr)


class StructuredFormatter(logging.Formatter):
    """A logging formatter that creates a json string from log details

    Args:
        fmt (string): Log message format.

        datefmt (dict): Log date format.

    Returns:
        New instance of :class:`StructuredFormatter`

    Examples:
        >>> from io import StringIO
        >>> from json import loads

        >>> s = StringIO()
        >>> logger = logging.getLogger()
        >>> formatter = StructuredFormatter(BASIC_FORMAT, datefmt=DATEFMT)
        >>> hdlr = logging.StreamHandler(s)
        >>> hdlr.setFormatter(formatter)
        >>> logger.addHandler(hdlr)
        >>> logger.info('hello world')
        >>> result = loads(s.getvalue())
        >>> keys = sorted(result.keys())
        >>> keys == ['level', 'message', 'msecs', 'name', 'time']
        True
        >>> [result[k] for k in keys if k not in {'msecs', 'time'}] == [
        ...     'INFO', 'hello world', 'root']
        True
    """

    def __init__(self, fmt=None, datefmt=None):
        """Initialization method.

        Args:
            fmt (string): Log message format.

            datefmt (dict): Log date format.

        Returns:
            New instance of :class:`StructuredFormatter`

        Examples:
            >>> StructuredFormatter("%(message)s")  # doctest: +ELLIPSIS
            <pygogo.formatters.StructuredFormatter object at 0x...>
        """
        empty_record = logging.makeLogRecord({})
        filterer = lambda k: k not in empty_record.__dict__ and k != "asctime"
        self.filterer = filterer
        super(StructuredFormatter, self).__init__(fmt, datefmt)

    def format(self, record):
        """ Formats a record as a dict string

        Args:
            record (object): The event to format.

        Returns:
            str: The formatted content

        Examples:
            >>> from json import loads

            >>> formatter = StructuredFormatter(BASIC_FORMAT, datefmt='%Y')
            >>> logger = logging.getLogger()
            >>> args = (logging.INFO, '.', 0, 'hello world', [], None)
            >>> record = logger.makeRecord('root', *args)
            >>> result = loads(formatter.format(record))
            >>> keys = sorted(result.keys())
            >>> keys == ['level', 'message', 'msecs', 'name', 'time']
            True
            >>> [result[k] for k in keys if k not in {'msecs', 'time'}] == [
            ...     'INFO', 'hello world', 'root']
            True
        """
        extra = {
            "message": record.getMessage(),
            "time": self.formatTime(record, self.datefmt),
            "msecs": record.msecs,
            "name": record.name,
            "level": record.levelname,
        }

        keys = filter(self.filterer, record.__dict__)
        extra.update({k: record.__dict__[k] for k in keys})
        extra.pop("asctime", None)
        return str(CustomEncoder().encode(extra))

    def formatException(self, exc_info):
        """Formats an exception as a dict string

        Args:
            exc_info (tuple[type, value, traceback]): Exception tuple as
                returned by `sys.exc_info()`

        Returns:
            str: The formatted exception

        Examples:
            >>> from json import loads

            >>> formatter = StructuredFormatter(BASIC_FORMAT)
            >>> try:
            ...     1 / 0
            ... except:
            ...     result = loads(formatter.formatException(sys.exc_info()))
            >>> keys = sorted(result.keys())
            >>> keys == [
            ...     'filename', 'function', 'lineno', 'text', 'type', 'value']
            True
            >>> [result[k] for k in keys if k not in {'filename', 'type'}] == [
            ...     '<module>', 2, '1 / 0', 'division by zero']
            True
            >>> result['type'][-17:] == 'ZeroDivisionError'
            True
        """
        keys = ["type", "value", "filename", "lineno", "function", "text"]
        type_, value, trcbk = exc_info
        stype = str(type_).replace("type", "").strip(" '<>")
        values = it.chain([stype, value], *traceback.extract_tb(trcbk))
        return str(CustomEncoder().encode(dict(zip(keys, values))))


basic_formatter = logging.Formatter(BASIC_FORMAT)
bom_formatter = logging.Formatter(BOM_FORMAT)
console_formatter = logging.Formatter(CONSOLE_FORMAT)
fixed_formatter = logging.Formatter(FIXED_FORMAT, datefmt=DATEFMT)
csv_formatter = logging.Formatter(CSV_FORMAT, datefmt=DATEFMT)
json_formatter = logging.Formatter(JSON_FORMAT, datefmt=DATEFMT)
structured_formatter = StructuredFormatter(BASIC_FORMAT, datefmt=DATEFMT)