"""
Logging Root
"""

from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
__RCSID__ = "$Id$"

import logging
import time
import sys
import six
from DIRAC.FrameworkSystem.private.standardLogging.LogLevels import LogLevels
from DIRAC.FrameworkSystem.private.standardLogging.Logging import Logging
from DIRAC.Resources.LogBackends.StdoutBackend import StdoutBackend
from DIRAC.Core.Utilities import DIRACSingleton


@six.add_metaclass(DIRACSingleton.DIRACSingleton)
class LoggingRoot(Logging):
  """
  LoggingRoot is a particular Logging object: the first parent of the chain.

  - It is the one that initializes the root logger of the standard logging library and it configures it
  - As it defines the default behaviour of the Logging objects, it needs a specific class
  - It is unique, there is one and only one parent at the top of the chain: this justifies the usage of a Singleton

  """
  # Boolean preventing the LoggingRoot to be configured once more
  __configuredLogging = False

  def __init__(self):
    """
    Initialization of the LoggingRoot object.
    LoggingRoot :

    - initialize the UTC time
    - set the correct level defines by the user, or the default
    - add the custom level to logging: verbose, notice, always
    - register a default backend (stdout): all messages will be displayed here
    - update the format according to the command line argument

    """
    super(LoggingRoot, self).__init__()

    # this line removes some useless information from log records and improves the performances
    logging._srcfile = None  # pylint: disable=protected-access

    # initialize the root logger, which turns out to be a child of root, and disable propagation
    # to avoid any conflicts with external libs that would use "logging" too
    self._logger = logging.getLogger('dirac')
    self._logger.propagate = False

    # here we redefine the custom name to the empty string to remove the "\" in the display
    self._customName = ""

    # initialization of levels
    levels = LogLevels.getLevels()
    for level in levels:
      logging.addLevelName(levels[level], level)

    # root Logger level is set to NOTICE by default
    self._logger.setLevel(LogLevels.NOTICE)

    # initialization of the UTC time
    # Actually, time.gmtime is equal to UTC time: it has its DST flag to 0 which means there is no clock advance
    logging.Formatter.converter = time.gmtime

    # initialization of the default backend
    # use the StdoutBackend directly to avoid dependancy loop with ObjectLoader
    self._addBackend(StdoutBackend)

    # configuration of the level and update the format
    self.__configureLevel()

  def initialize(self, systemName, cfgPath, forceInit=False):
    """
    Configure the root Logging.
    It can be possible to :

    - attach it some backends : LogBackends = stdout,stderr,file,server
    - attach backend options : BackendOptions { FileName = /tmp/file.log }
    - add colors and the path of the call : LogColor = True, LogShowLine = True
    - precise a level : LogLevel = DEBUG

    :param str systemName: <system name>/<component name>
    :param str cfgPath: configuration path
    :param bool forceInit: Force the initialization even if it had already happened.
                           This should not be used !! The only case is LocalConfiguration.enableCS
                           In order to take into account extensions' backends
    """
    # we have to put the import line here to avoid a dependancy loop
    from DIRAC import gConfig

    self._lockConfig.acquire()
    try:
      if not LoggingRoot.__configuredLogging or forceInit:
        Logging._componentName = systemName

        # prepare to remove all the backends from the root Logging as in the old gLogger.
        # store them in a list handlersToRemove.
        # we will remove them later, because some components as ObjectLoader need a backend.
        # this can be useful to have logs only in a file for instance.
        handlersToRemove = []
        for backend in self._backendsList:
          handlersToRemove.append(backend.getHandler())
        del self._backendsList[:]

        # get the backends, the backend options and add them to the root Logging
        desiredBackends = self.__getBackendsFromCFG(cfgPath)
        for backend in desiredBackends:
          desiredOptions = self.__getBackendOptionsFromCFG(cfgPath, backend)
          self.registerBackend(desiredOptions.get('Plugin', backend), desiredOptions)

        # Format options
        self._options['color'] = gConfig.getValue("%s/LogColor" % cfgPath, False)

        # Remove the old backends
        for handler in handlersToRemove:
          self._logger.removeHandler(handler)

        levelName = gConfig.getValue("%s/LogLevel" % cfgPath, None)
        if levelName is not None:
          self.setLevel(levelName)

        LoggingRoot.__configuredLogging = True
    finally:
      self._lockConfig.release()

  def __getBackendsFromCFG(self, cfgPath):
    """
    Get backends from the configuration and register them in LoggingRoot.
    This is the new way to get the backends providing a general configuration.

    :param str cfgPath: configuration path
    """
    # We have to put the import line here to avoid a dependancy loop
    from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
    from DIRAC import gConfig

    # get the second last string representing the component type in the configuration
    # example : 'Agents', 'Services'
    component = cfgPath.split("/")[-2]
    operation = Operations()

    # Search desired backends in the component
    desiredBackends = gConfig.getValue("%s/%s" % (cfgPath, 'LogBackends'), [])
    if not desiredBackends:
      # Search desired backends in the operation section according to the
      # component type
      desiredBackends = operation.getValue(
          "Logging/Default%sBackends" % component, [])
      if not desiredBackends:
        # Search desired backends in the operation section
        desiredBackends = operation.getValue("Logging/DefaultBackends", [])
        if not desiredBackends:
          # Default value
          desiredBackends = ['stdout']

    return desiredBackends

  def __getBackendOptionsFromCFG(self, cfgPath, backend):
    """
    Get backend options from the configuration.

    :param cfgPath: configuration path
    :param backend: backend identifier: stdout, file, f04
    """
    # We have to put the import lines here to avoid a dependancy loop
    from DIRAC import gConfig
    from DIRAC.ConfigurationSystem.Client.Helpers.Resources import getBackendConfig

    backendOptions = {}
    # Search backends config in the resources section
    retDictRessources = getBackendConfig(backend)
    if retDictRessources['OK']:
      backendOptions = retDictRessources['Value']

    # Search backends config in the component to update some options
    retDictConfig = gConfig.getOptionsDict(
        "%s/%s/%s" % (cfgPath, 'LogBackendsConfig', backend))
    if retDictConfig['OK']:
      backendOptions.update(retDictConfig['Value'])

    return backendOptions

  def __configureLevel(self):
    """
    Configure the log level of the root Logging according to the argv parameter
    It can be : -d, -dd, -ddd
    Work only for clients, scripts and tests
    Configuration/Client/LocalConfiguration manages services,agents and executors
    """
    debLevs = 0
    for arg in sys.argv:
      if arg.find("-d") == 0:
        debLevs += arg.count("d")
    if debLevs == 1:
      self.setLevel('verbose')
    elif debLevs == 2:
      self.setLevel('verbose')
      self.showHeaders(True)
    elif debLevs >= 3:
      self.setLevel('debug')
      self.showHeaders(True)
      self.showThreadIDs(True)

  def enableLogsFromExternalLibs(self):
    """
    Enable the display of the logs coming from external libraries
    """
    self.__enableLogsFromExternalLibs()

  def disableLogsFromExternalLibs(self):
    """
    Disable the display of the logs coming from external libraries
    """
    self.__enableLogsFromExternalLibs(False)

  @staticmethod
  def __enableLogsFromExternalLibs(isEnabled=True):
    """
    Configure the root logger from 'logging' for an external library use.
    By default the root logger is configured with:

    - debug level,
    - stderr output
    - custom format close to the DIRAC format

    :param bool isEnabled: allows logs from external libs
    """
    rootLogger = logging.getLogger()
    rootLogger.handlers = []
    if isEnabled:
      logging.basicConfig(level=logging.DEBUG,
                          format='%(asctime)s UTC ExternalLibrary/%(name)s %(levelname)s: %(message)s',
                          datefmt='%Y-%m-%d %H:%M:%S')
    else:
      rootLogger.addHandler(logging.NullHandler())
