from fontTools.misc.loggingTools import (
    LevelFormatter,
    Timer,
    configLogger,
    ChannelsFilter,
    LogMixin,
)
from io import StringIO
import logging
import textwrap
import time
import re
import pytest


def logger_name_generator():
    basename = "fontTools.test#"
    num = 1
    while True:
        yield basename + str(num)
        num += 1


unique_logger_name = logger_name_generator()


@pytest.fixture
def logger():
    log = logging.getLogger(next(unique_logger_name))
    configLogger(logger=log, level="DEBUG", stream=StringIO())
    return log


def test_LevelFormatter():
    stream = StringIO()
    handler = logging.StreamHandler(stream)
    formatter = LevelFormatter(
        fmt={
            "*": "[%(levelname)s] %(message)s",
            "DEBUG": "%(name)s [%(levelname)s] %(message)s",
            "INFO": "%(message)s",
        }
    )
    handler.setFormatter(formatter)
    name = next(unique_logger_name)
    log = logging.getLogger(name)
    log.setLevel(logging.DEBUG)
    log.addHandler(handler)

    log.debug("this uses a custom format string")
    log.info("this also uses a custom format string")
    log.warning("this one uses the default format string")

    assert stream.getvalue() == textwrap.dedent(
        """\
        %s [DEBUG] this uses a custom format string
        this also uses a custom format string
        [WARNING] this one uses the default format string
        """
        % name
    )


class TimerTest(object):
    def test_split(self):
        timer = Timer()
        time.sleep(0.01)
        fist_lap = timer.split()
        assert timer.elapsed == fist_lap
        time.sleep(0.1)
        second_lap = timer.split()
        assert second_lap > fist_lap
        assert timer.elapsed == second_lap

    def test_time(self):
        timer = Timer()
        time.sleep(0.01)
        overall_time = timer.time()
        assert overall_time > 0

    def test_context_manager(self):
        with Timer() as t:
            time.sleep(0.01)
        assert t.elapsed > 0

    def test_using_logger(self, logger):
        with Timer(logger, "do something"):
            time.sleep(0.01)

        assert re.match(
            r"Took [0-9]\.[0-9]{3}s to do something",
            logger.handlers[0].stream.getvalue(),
        )

    def test_using_logger_calling_instance(self, logger):
        timer = Timer(logger)
        with timer():
            time.sleep(0.01)

        assert re.match(
            r"elapsed time: [0-9]\.[0-9]{3}s", logger.handlers[0].stream.getvalue()
        )

        # do it again but with custom level
        with timer("redo it", level=logging.WARNING):
            time.sleep(0.02)

        assert re.search(
            r"WARNING: Took [0-9]\.[0-9]{3}s to redo it",
            logger.handlers[0].stream.getvalue(),
        )

    def test_function_decorator(self, logger):
        timer = Timer(logger)

        @timer()
        def test1():
            time.sleep(0.01)

        @timer("run test 2", level=logging.INFO)
        def test2():
            time.sleep(0.02)

        test1()

        assert re.match(
            r"Took [0-9]\.[0-9]{3}s to run 'test1'",
            logger.handlers[0].stream.getvalue(),
        )

        test2()

        assert re.search(
            r"Took [0-9]\.[0-9]{3}s to run test 2", logger.handlers[0].stream.getvalue()
        )


def test_ChannelsFilter(logger):
    n = logger.name
    filtr = ChannelsFilter(n + ".A.B", n + ".C.D")
    handler = logger.handlers[0]
    handler.addFilter(filtr)
    stream = handler.stream

    logging.getLogger(n + ".A.B").debug("this record passes through")
    assert "this record passes through" in stream.getvalue()

    logging.getLogger(n + ".A.B.C").debug("records from children also pass")
    assert "records from children also pass" in stream.getvalue()

    logging.getLogger(n + ".C.D").debug("this one as well")
    assert "this one as well" in stream.getvalue()

    logging.getLogger(n + ".A.B.").debug("also this one")
    assert "also this one" in stream.getvalue()

    before = stream.getvalue()
    logging.getLogger(n + ".A.F").debug("but this one does not!")
    assert before == stream.getvalue()

    logging.getLogger(n + ".C.DE").debug("neither this one!")
    assert before == stream.getvalue()


def test_LogMixin():
    class Base(object):
        pass

    class A(LogMixin, Base):
        pass

    class B(A):
        pass

    a = A()
    b = B()

    assert hasattr(a, "log")
    assert hasattr(b, "log")
    assert isinstance(a.log, logging.Logger)
    assert isinstance(b.log, logging.Logger)
    assert a.log.name == "loggingTools_test.A"
    assert b.log.name == "loggingTools_test.B"
