""" :mod: DErrno

    ==========================

    .. module: DErrno

    :synopsis: Error list and utilities for handling errors in DIRAC


    This module contains list of errors that can be encountered in DIRAC.
    It complements the errno module of python.

    It also contains utilities to manipulate these errors.

    Finally, it contains a DErrno class that contains an error number
    as well as a low level error message. It behaves like a string for
    compatibility reasons

    In order to add extension specific error, you need to create in your extension the file
    Core/Utilities/DErrno.py, which will contain the following dictionary:

      * extra_dErrName: keys are the error name, values the number of it
      * extra_dErrorCode: same as dErrorCode. keys are the error code, values the name
                          (we don't simply revert the previous dict in case we do not
                          have a one to one mapping)
      * extra_dStrError: same as dStrError, Keys are the error code, values the error description
      * extra_compatErrorString: same as compatErrorString. The compatible error strings are
                                 added to the existing one, and not replacing them.


    Example of extension file :

       * extra_dErrName = { 'ELHCBSPE' : 3001 }
       * extra_dErrorCode = { 3001 : 'ELHCBSPE'}
       * extra_dStrError = { 3001 : "This is a description text of the specific LHCb error" }
       * extra_compatErrorString = { 3001 : ["living easy, living free"],
                             DErrno.ERRX : ['An error message for ERRX that is specific to LHCb']}

"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import six
import os
import importlib
import sys

from DIRAC.Core.Utilities.Extensions import extensionsByPriority

# To avoid conflict, the error numbers should be greater than 1000
# We decided to group the by range of 100 per system

# 1000: Generic
# 1100: Core
# 1200: Framework
# 1300: Interfaces
# 1400: Config
# 1500: WMS + Workflow
# 1600: DMS + StorageManagement
# 1700: RMS
# 1800: Accounting + Monitoring
# 1900: TS + Production
# 2000: Resources + RSS

# ## Generic (10XX)
# Python related: 0X
ETYPE = 1000
EIMPERR = 1001
ENOMETH = 1002
ECONF = 1003
EVALUE = 1004
EEEXCEPTION = 1005
# Files manipulation: 1X
ECTMPF = 1010
EOF = 1011
ERF = 1012
EWF = 1013
ESPF = 1014

# ## Core (11XX)
# Certificates and Proxy: 0X
EX509 = 1100
EPROXYFIND = 1101
EPROXYREAD = 1102
ECERTFIND = 1103
ECERTREAD = 1104
ENOCERT = 1105
ENOCHAIN = 1106
ENOPKEY = 1107
ENOGROUP = 1108
# DISET: 1X
EDISET = 1110
ENOAUTH = 1111
# 3rd party security: 2X
E3RDPARTY = 1120
EVOMS = 1121
# Databases : 3X
EDB = 1130
EMYSQL = 1131
ESQLA = 1132
# Message Queues: 4X
EMQUKN = 1140
EMQNOM = 1141
EMQCONN = 1142
# Elasticsearch
EELNOFOUND = 1146

# config
ESECTION = 1400

# processes
EEZOMBIE = 1147
EENOPID = 1148

# ## WMS/Workflow
EWMSUKN = 1500
EWMSJDL = 1501
EWMSRESC = 1502
EWMSSUBM = 1503
EWMSJMAN = 1504
EWMSSTATUS = 1505
EWMSNOPILOT = 1550

# ## DMS/StorageManagement (16XX)
EFILESIZE = 1601
EGFAL = 1602
EBADCKS = 1603
EFCERR = 1604

# ## RMS (17XX)
ERMSUKN = 1700

# ## TS (19XX)
ETSUKN = 1900
ETSDATA = 1901

# ## Resources and RSS (20XX)
ERESGEN = 2000
ERESUNA = 2001
ERESUNK = 2002

# This translates the integer number into the name of the variable
dErrorCode = {
    # ## Generic (10XX)
    # 100X: Python related
    1000: "ETYPE",
    1001: "EIMPERR",
    1002: "ENOMETH",
    1003: "ECONF",
    1004: "EVALUE",
    1005: "EEEXCEPTION",
    # 101X: Files manipulation
    1010: "ECTMPF",
    1011: "EOF",
    1012: "ERF",
    1013: "EWF",
    1014: "ESPF",
    # ## Core
    # 110X: Certificates and Proxy
    1100: "EX509",
    1101: "EPROXYFIND",
    1102: "EPROXYREAD",
    1103: "ECERTFIND",
    1104: "ECERTREAD",
    1105: "ENOCERT",
    1106: "ENOCHAIN",
    1107: "ENOPKEY",
    1108: "ENOGROUP",
    # 111X: DISET
    1110: "EDISET",
    1111: "ENOAUTH",
    # 112X: 3rd party security
    1120: "E3RDPARTY",
    1121: "EVOMS",
    # 113X: Databases
    1130: "EDB",
    1131: "EMYSQL",
    1132: "ESQLA",
    # 114X: Message Queues
    1140: "EMQUKN",
    1141: "EMQNOM",
    1142: "EMQCONN",
    # Elasticsearch
    1146: "EELNOFOUND",
    # Config
    1400: "ESECTION",
    # Processes
    1147: "EEZOMBIE",
    1148: "EENOPID",
    # WMS/Workflow
    1500: "EWMSUKN",
    1501: "EWMSJDL",
    1502: "EWMSRESC",
    1503: "EWMSSUBM",
    1504: "EWMSJMAN",
    1505: "EWMSSTATUS",
    1550: "EWMSNOPILOT",
    # DMS/StorageManagement
    1601: "EFILESIZE",
    1602: "EGFAL",
    1603: "EBADCKS",
    1604: "EFCERR",
    # RMS
    1700: "ERMSUKN",
    # Resources and RSS
    2000: "ERESGEN",
    2001: "ERESUNA",
    2002: "ERESUNK",
    # TS
    1900: "ETSUKN",
    1901: "ETSDATA",
}


dStrError = {  # Generic (10XX)
    # 100X: Python related
    ETYPE: "Object Type Error",
    EIMPERR: "Failed to import library",
    ENOMETH: "No such method or function",
    ECONF: "Configuration error",
    EVALUE: "Wrong value passed",
    EEEXCEPTION: "runtime general exception",
    # 101X: Files manipulation
    ECTMPF: "Failed to create temporary file",
    EOF: "Cannot open file",
    ERF: "Cannot read from file",
    EWF: "Cannot write to file",
    ESPF: "Cannot set permissions to file",
    # ## Core
    # 110X: Certificates and Proxy
    EX509: "Generic Error with X509",
    EPROXYFIND: "Can't find proxy",
    EPROXYREAD: "Can't read proxy",
    ECERTFIND: "Can't find certificate",
    ECERTREAD: "Can't read certificate",
    ENOCERT: "No certificate loaded",
    ENOCHAIN: "No chain loaded",
    ENOPKEY: "No private key loaded",
    ENOGROUP: "No DIRAC group",
    # 111X: DISET
    EDISET: "DISET Error",
    ENOAUTH: "Unauthorized query",
    # 112X: 3rd party security
    E3RDPARTY: "3rd party security service error",
    EVOMS: "VOMS Error",
    # 113X: Databases
    EDB: "Database Error",
    EMYSQL: "MySQL Error",
    ESQLA: "SQLAlchemy Error",
    # 114X: Message Queues
    EMQUKN: "Unknown MQ Error",
    EMQNOM: "No messages",
    EMQCONN: "MQ connection failure",
    # 114X Elasticsearch
    EELNOFOUND: "Index not found",
    # Config
    ESECTION: "Section is not found",
    # processes
    EEZOMBIE: "Zombie process",
    EENOPID: "No PID of process",
    # WMS/Workflow
    EWMSUKN: "Unknown WMS error",
    EWMSJDL: "Invalid job description",
    EWMSRESC: "Job to reschedule",
    EWMSSUBM: "Job submission error",
    EWMSJMAN: "Job management error",
    EWMSSTATUS: "Job status error",
    EWMSNOPILOT: "No pilots found",
    # DMS/StorageManagement
    EFILESIZE: "Bad file size",
    EGFAL: "Error with the gfal call",
    EBADCKS: "Bad checksum",
    EFCERR: "FileCatalog error",
    # RMS
    ERMSUKN: "Unknown RMS error",
    # Resources and RSS
    ERESGEN: "Unknown Resource Failure",
    ERESUNA: "Resource not available",
    ERESUNK: "Unknown Resource",
    # TS
    ETSUKN: "Unknown Transformation System Error",
    ETSDATA: "Invalid Input Data definition",
}


def strerror(code):
    """This method wraps up os.strerror, and behave the same way.
    It completes it with the DIRAC specific errors.
    """

    if code == 0:
        return "Undefined error"

    errMsg = "Unknown error %s" % code

    try:
        errMsg = dStrError[code]
    except KeyError:
        # It is not a DIRAC specific error, try the os one
        try:
            errMsg = os.strerror(code)
            # On some system, os.strerror raises an exception with unknown code,
            # on others, it returns a message...
        except ValueError:
            pass

    return errMsg


def cmpError(inErr, candidate):
    """This function compares an error (in its old form (a string or dictionary) or in its int form
    with a candidate error code.

    :param inErr: a string, an integer, a S_ERROR dictionary
    :type inErr: str or int or S_ERROR
    :param int candidate: error code to compare with

    :return: True or False

    If an S_ERROR instance is passed, we compare the code with S_ERROR['Errno']
    If it is a Integer, we do a direct comparison
    If it is a String, we use strerror to check the error string
    """

    if isinstance(inErr, six.string_types):  # old style
        # Compare error message strings
        errMsg = strerror(candidate)
        return errMsg in inErr
    elif isinstance(inErr, dict):  # if the S_ERROR structure is given
        # Check if Errno defined in the dict
        errorNumber = inErr.get("Errno")
        if errorNumber:
            return errorNumber == candidate
        errMsg = strerror(candidate)
        return errMsg in inErr.get("Message", "")
    elif isinstance(inErr, int):
        return inErr == candidate
    else:
        raise TypeError("Unknown input error type %s" % type(inErr))


def includeExtensionErrors():
    """Merge all the errors of all the extensions into the errors of these modules
    Should be called only at the initialization of DIRAC, so by the parseCommandLine,
    dirac-agent.py, dirac-service.py, dirac-executor.py
    """
    for extension in reversed(extensionsByPriority()):
        if extension == "DIRAC":
            continue
        try:
            ext_derrno = importlib.import_module("%s.Core.Utilities.DErrno" % extension)
        except ImportError:
            pass
        else:
            # The next 3 dictionary MUST be present for consistency
            # Global name of errors
            sys.modules[__name__].__dict__.update(ext_derrno.extra_dErrName)
            # Dictionary with the error codes
            sys.modules[__name__].dErrorCode.update(ext_derrno.extra_dErrorCode)
            # Error description string
            sys.modules[__name__].dStrError.update(ext_derrno.extra_dStrError)

            # extra_compatErrorString is optional
            for err in getattr(ext_derrno, "extra_compatErrorString", []):
                sys.modules[__name__].compatErrorString.setdefault(err, []).extend(
                    ext_derrno.extra_compatErrorString[err]
                )
