# SPDX-License-Identifier: LGPL-2.1-or-later

# ***************************************************************************
# *   Copyright (c) 2016 sliptonic <shopinthewoods@gmail.com>               *
# *                                                                         *
# *   This program is free software; you can redistribute it and/or modify  *
# *   it under the terms of the GNU Lesser General Public License (LGPL)    *
# *   as published by the Free Software Foundation; either version 2 of     *
# *   the License, or (at your option) any later version.                   *
# *   for detail see the LICENCE text file.                                 *
# *                                                                         *
# *   This program is distributed in the hope that it will be useful,       *
# *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
# *   GNU Library General Public License for more details.                  *
# *                                                                         *
# *   You should have received a copy of the GNU Library General Public     *
# *   License along with this program; if not, write to the Free Software   *
# *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
# *   USA                                                                   *
# *                                                                         *
# ***************************************************************************

import FreeCAD
import os
import traceback


class Level:
    """Enumeration of log levels, used for setLevel and getLevel."""

    RESET = -1
    ERROR = 0
    WARNING = 1
    NOTICE = 2
    INFO = 3
    DEBUG = 4

    _names = {
        ERROR: "ERROR",
        WARNING: "WARNING",
        NOTICE: "NOTICE",
        INFO: "INFO",
        DEBUG: "DEBUG",
    }

    @classmethod
    def toString(cls, level):
        return cls._names.get(level, "UNKNOWN")


_defaultLogLevel = Level.NOTICE
_moduleLogLevel = {}
_useConsole = True
_trackModule = {}
_trackAll = False


def logToConsole(yes):
    """(boolean) - if set to True (default behaviour) log messages are printed to the console. Otherwise they are printed to stdout."""
    global _useConsole
    _useConsole = yes


def setLevel(level, module=None):
    """(level, module = None)
    if no module is specified the default log level is set.
    Otherwise the module specific log level is changed (use RESET to clear)."""
    global _defaultLogLevel
    global _moduleLogLevel
    if module:
        if level == Level.RESET:
            if _moduleLogLevel.get(module, -1) != -1:
                del _moduleLogLevel[module]
        else:
            _moduleLogLevel[module] = level
    else:
        if level == Level.RESET:
            _defaultLogLevel = Level.NOTICE
            _moduleLogLevel = {}
        else:
            _defaultLogLevel = level


def getLevel(module=None):
    """(module = None) - return the global (None) or module specific log level."""
    if module:
        return _moduleLogLevel.get(module, _defaultLogLevel)
    return _defaultLogLevel


def thisModule():
    """returns the module id of the caller, can be used for setLevel, getLevel and trackModule."""
    return _caller()[0]


def _caller():
    """internal function to determine the calling module."""
    filename, line, func, text = traceback.extract_stack(limit=3)[0]
    return os.path.splitext(os.path.basename(filename))[0], line, func


def _log(level, module_line_func, msg):
    """internal function to do the logging"""
    module, line, func = module_line_func

    if getLevel(module) >= level:
        message = "%s.%s: %s" % (module, Level.toString(level), msg)
        if _useConsole:
            message += "\n"
            if level == Level.NOTICE:
                FreeCAD.Console.PrintLog(message)
            elif level == Level.WARNING:
                FreeCAD.Console.PrintWarning(message)
            elif level == Level.ERROR:
                FreeCAD.Console.PrintError(message)
            else:
                FreeCAD.Console.PrintMessage(message)
        else:
            print(message)
        return message
    return None


def debug(msg):
    """(message)"""
    caller_info = _caller()
    _, line, _ = caller_info
    msg = "({}) - {}".format(line, msg)
    return _log(Level.DEBUG, caller_info, msg)


def info(msg):
    """(message)"""
    return _log(Level.INFO, _caller(), msg)


def notice(msg):
    """(message)"""
    return _log(Level.NOTICE, _caller(), msg)


def warning(msg):
    """(message)"""
    return _log(Level.WARNING, _caller(), msg)


def error(msg):
    """(message)"""
    return _log(Level.ERROR, _caller(), msg)


def trackAllModules(boolean):
    """(boolean) - if True all modules will be tracked, otherwise tracking is up to the module setting."""
    global _trackAll
    _trackAll = boolean


def untrackAllModules():
    """In addition to stop tracking all modules it also clears the tracking flag for all individual modules."""
    global _trackAll
    global _trackModule
    _trackAll = False
    _trackModule = {}


def trackModule(module=None):
    """(module = None) - start tracking given module, current module if not set."""
    global _trackModule
    if module:
        _trackModule[module] = True
    else:
        mod, line, func = _caller()
        _trackModule[mod] = True


def untrackModule(module=None):
    """(module = None) - stop tracking given module, current module if not set."""
    global _trackModule
    if module and _trackModule.get(module, None):
        del _trackModule[module]
    elif not module:
        mod, line, func = _caller()
        if _trackModule.get(mod, None):
            del _trackModule[mod]


def track(*args):
    """(....) - call with arguments of current function you want logged if tracking is enabled."""
    module, line, func = _caller()
    if _trackAll or _trackModule.get(module, None):
        message = "%s(%d).%s(%s)" % (
            module,
            line,
            func,
            ", ".join([str(arg) for arg in args]),
        )
        if _useConsole:
            FreeCAD.Console.PrintMessage(message + "\n")
        else:
            print(message)
        return message
    return None
