#!/usr/pkg/bin/python3.11
# License: GPLv3 Copyright: 2009, Kovid Goyal <kovid at kovidgoyal.net>

# A simplified logging system

import io
import sys
import traceback
from contextlib import suppress
from functools import partial
from threading import Lock

from calibre.prints import prints
from polyglot.builtins import as_unicode


DEBUG = 0
INFO  = 1
WARN  = 2
ERROR = 3


class Stream:

    def __init__(self, stream=None):
        if stream is None:
            stream = io.StringIO()
        self.stream = stream
        self.encoding = getattr(self.stream, 'encoding', None) or 'utf-8'

    def write(self, text):
        self._prints(text, end='')

    def flush(self):
        with suppress(BrokenPipeError):
            # Don't fail if we were logging to a pipe and it got closed
            self.stream.flush()

    def prints(self, level, *args, **kwargs):
        self._prints(*args, **kwargs)

    def _prints(self, *args, **kwargs):
        prints(*args, **kwargs, file=self.stream)


stdout_sentinel = object()


class ANSIStream(Stream):

    def __init__(self, stream=stdout_sentinel):
        if stream is stdout_sentinel:
            stream = sys.stdout
        Stream.__init__(self, stream)
        self.color = {
            DEBUG: 'green',
            INFO: None,
            WARN: 'yellow',
            ERROR: 'red',
        }

    def prints(self, level, *args, **kwargs):
        from calibre.utils.terminal import ColoredStream
        with ColoredStream(self.stream, self.color[level]):
            self._prints(*args, **kwargs)


class FileStream(Stream):

    def __init__(self, stream=None):
        Stream.__init__(self, stream)

    def prints(self, level, *args, **kwargs):
        self._prints(*args, **kwargs)


class HTMLStream(Stream):

    color = {
        DEBUG: '<span style="color:green">',
        INFO: '<span>',
        WARN: '<span style="color:blue">',
        ERROR: '<span style="color:red">'
    }
    normal = '</span>'

    def __init__(self, stream=stdout_sentinel):
        if stream is stdout_sentinel:
            stream = sys.stdout
        Stream.__init__(self, stream)

    def prints(self, level, *args, **kwargs):
        self._prints(self.color[level], end='')
        self._prints(*args, **kwargs)
        self._prints(self.normal, end='')


class UnicodeHTMLStream(HTMLStream):

    def __init__(self):
        self.clear()

    def flush(self):
        pass

    def prints(self, level, *args, **kwargs):
        col = self.color[level]
        if col != self.last_col:
            if self.data:
                self.data.append(self.normal)
            self.data.append(col)
            self.last_col = col

        sep  = kwargs.get('sep', ' ')
        end  = kwargs.get('end', '\n')

        for arg in args:
            arg = as_unicode(arg)
            self.data.append(arg+sep)
            self.plain_text.append(arg+sep)
        self.data.append(end)
        self.plain_text.append(end)

    def clear(self):
        self.data = []
        self.plain_text = []
        self.last_col = self.color[INFO]

    @property
    def html(self):
        end = self.normal if self.data else ''
        return ''.join(self.data) + end

    def dump(self):
        return [self.data, self.plain_text, self.last_col]

    def load(self, dump):
        self.data, self.plain_text, self.last_col = dump

    def append_dump(self, dump):
        d, p, lc = dump
        self.data.extend(d)
        self.plain_text.extend(p)
        self.last_col = lc


class Log:

    DEBUG = DEBUG
    INFO  = INFO
    WARN  = WARN
    ERROR = ERROR

    def __init__(self, level=INFO):
        self.filter_level = level
        default_output = ANSIStream()
        self.outputs = [default_output]

        self.debug = partial(self.print_with_flush, DEBUG)
        self.info  = partial(self.print_with_flush, INFO)
        self.warn  = self.warning = partial(self.print_with_flush, WARN)
        self.error = partial(self.print_with_flush, ERROR)

    def prints(self, level, *args, **kwargs):
        if level < self.filter_level:
            return
        for output in self.outputs:
            output.prints(level, *args, **kwargs)

    def print_with_flush(self, level, *args, **kwargs):
        if level < self.filter_level:
            return
        for output in self.outputs:
            output.prints(level, *args, **kwargs)
        self.flush()

    def exception(self, *args, **kwargs):
        limit = kwargs.pop('limit', None)
        self.print_with_flush(ERROR, *args, **kwargs)
        self.print_with_flush(DEBUG, traceback.format_exc(limit))

    def __call__(self, *args, **kwargs):
        self.info(*args, **kwargs)

    def __enter__(self):
        self.orig_filter_level = self.filter_level
        self.filter_level = self.ERROR + 100

    def __exit__(self, *args):
        self.filter_level = self.orig_filter_level

    def flush(self):
        for o in self.outputs:
            if hasattr(o, 'flush'):
                o.flush()

    def close(self):
        for o in self.outputs:
            if hasattr(o, 'close'):
                o.close()


class DevNull(Log):

    def __init__(self):
        Log.__init__(self, level=Log.ERROR)
        self.outputs = []


class ThreadSafeLog(Log):
    exception_traceback_level = Log.DEBUG

    def __init__(self, level=Log.INFO):
        Log.__init__(self, level=level)
        self._lock = Lock()

    def prints(self, *args, **kwargs):
        with self._lock:
            Log.prints(self, *args, **kwargs)

    def print_with_flush(self, *args, **kwargs):
        with self._lock:
            Log.print_with_flush(self, *args, **kwargs)

    def exception(self, *args, **kwargs):
        limit = kwargs.pop('limit', None)
        with self._lock:
            Log.print_with_flush(self, ERROR, *args, **kwargs)
            Log.print_with_flush(self, self.exception_traceback_level, traceback.format_exc(limit))


class ThreadSafeWrapper(Log):

    def __init__(self, other_log):
        Log.__init__(self, level=other_log.filter_level)
        self.outputs = list(other_log.outputs)
        self._lock = Lock()

    def prints(self, *args, **kwargs):
        with self._lock:
            Log.prints(self, *args, **kwargs)

    def print_with_flush(self, *args, **kwargs):
        with self._lock:
            Log.print_with_flush(self, *args, **kwargs)


class GUILog(ThreadSafeLog):

    '''
    Logs in HTML and plain text as unicode. Ideal for display in a GUI context.
    '''

    def __init__(self):
        ThreadSafeLog.__init__(self, level=self.DEBUG)
        self.outputs = [UnicodeHTMLStream()]

    def clear(self):
        self.outputs[0].clear()

    @property
    def html(self):
        return self.outputs[0].html

    @property
    def plain_text(self):
        return ''.join(self.outputs[0].plain_text)

    def dump(self):
        return self.outputs[0].dump()

    def load(self, dump):
        return self.outputs[0].load(dump)

    def append_dump(self, dump):
        return self.outputs[0].append_dump(dump)


default_log = Log()
