import logging
from contextlib import contextmanager


def setup_console(l, verbosity_level):
    """Setup console output for logging calls"""
    l.setLevel(logging.DEBUG) # level-logic is instead in the handler

    existing_consoles = [h for h in l.handlers if isinstance(h, ConsoleHandler)]
    if existing_consoles:
        if len(existing_consoles) == 1:
            # re-use existing console handler
            h = existing_consoles[0]
            assert h.verbosity_level == verbosity_level, \
                'already has console with different verbosity level'
        else:
            raise RuntimeError, 'more than one console installed. not possible.'
    else:
        # create a new console handler
        h = ConsoleHandler(verbosity_level)
        h.setFormatter(ConsoleFormatter())
        l.addHandler(h)

class ConsoleHandler(logging.StreamHandler):
    """Send messages to console

    INFO messages are sent to stdout. Other levels to stderr.

    By default, INFO/WARN/ERROR messages are sent as-it-is to console .. while
    EXCEPTION messages are pruned and shown as error unless verbosity level is
    greater than zero. If verbosity level is greater than one, then DEBUG
    messages are also shown.
    """

    def __init__(self, verbosity_level):
        logging.StreamHandler.__init__(self)
        self.stream = None # reset it; we are not going to use it anyway
        self.verbosity_level = verbosity_level

    def emit(self, record):
        if record.levelno == logging.INFO:
            self.__emit(record, sys.stdout)
        elif record.levelno == logging.WARN:
            self.__emit(record, sys.stderr)
        elif record.levelno == logging.DEBUG:
            # show DEBUG messages with verbosity_level >= 2
            if self.verbosity_level > 1:
                self.__emit(record, sys.stderr)
        elif record.levelno >= logging.ERROR:
            if record.exc_info and self.verbosity_level < 1:
                # supress full traceback with verbosity_level <= 0
                with new_record_exc_info(record, None):
                    self.__emit(record, sys.stderr)
            else:
                self.__emit(record, sys.stderr)
        else:
            raise NotImplementedError, \
                "don't know about level: {0}".format(record.levelno)

    def __emit(self, record, strm):
        # override handler stream with ours (which could stdout or stderr)
        self.stream = strm
        logging.StreamHandler.emit(self, record)

    def flush(self):
        # Workaround a bug in logging module
        # See:
        #   http://bugs.python.org/issue6333
        if self.stream and hasattr(self.stream, 'flush') and not self.stream.closed:
            logging.StreamHandler.flush(self)


def _clear_record_traceback_cache(record):
    """Clear the traceback cache stored in `record` (LogRecord)

    Workaround for: http://bugs.python.org/issue6435
    """
    record.exc_text = None

@contextmanager
def new_record_exc_info(record, exc_info):
    """Temporarily assign `exc_info` to `record`"""
    _clear_record_traceback_cache(record)
    old_exc_info = record.exc_info
    record.exc_info = exc_info
    try:
        yield
    finally:
        record.exc_info = old_exc_info
        _clear_record_traceback_cache(record)

class ConsoleFormatter(logging.Formatter):
    """A formatter that attaches 'error:' prefix to error/critical messages"""

    def format(self, record):
        # attach 'error:' prefix to error/critical messages
        s = logging.Formatter.format(self, record)
        if record.levelno >= logging.ERROR:
            return 'error: {0}'.format(s)
        else:
            return s
