""" This helper looks in the /Operations section of the CS, considering its specific nature:
    the /Operations section is designed in a way that each configuration can be specific to a Setup,
    while maintaining a default.

    So, for example, given the following /Operations section::

      Operations/
          Defaults/
              someSection/
                  someOption = someValue
                  aSecondOption = aSecondValue
          Production/
              someSection/
                  someOption = someValueInProduction
                  aSecondOption = aSecondValueInProduction
          Certification/
              someSection/
                  someOption = someValueInCertification

    The following calls would give different results based on the setup::

      Operations().getValue('someSection/someOption')
        - someValueInProduction if we are in 'Production' setup
        - someValueInCertification if we are in 'Certification' setup

      Operations().getValue('someSection/aSecondOption')
        - aSecondValueInProduction if we are in 'Production' setup
        - aSecondValue if we are in 'Certification' setup    <- looking in Defaults
                                                                since there's no Certification/someSection/aSecondOption


    At the same time, for multi-VO installations, it is also possible to specify different options per-VO,
    like the following::

      Operations/
          aVOName/
              Defaults/
                  someSection/
                      someOption = someValue
                      aSecondOption = aSecondValue
              Production/
                  someSection/
                      someOption = someValueInProduction
                      aSecondOption = aSecondValueInProduction
              Certification/
                  someSection/
                      someOption = someValueInCertification
          anotherVOName/
              Defaults/
                  someSectionName/
                      someOptionX = someValueX
                      aSecondOption = aSecondValue
              setupName/
                  someSection/
                      someOption = someValueInProduction
                      aSecondOption = aSecondValueInProduction

    For this case it becomes then important for the Operations() objects to know the VO name
    for which we want the information, and this can be done in the following ways.

    1. by specifying the VO name directly::

         Operations(vo=anotherVOName).getValue('someSectionName/someOptionX')

    2. by give a group name::

         Operations(group=thisIsAGroupOfVO_X).getValue('someSectionName/someOptionX')

    3. if no VO nor group is provided, the VO will be guessed from the proxy,
    but this works iff the object is instantiated by a proxy (and not, e.g., using a server certificate)

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

import six
from six.moves import _thread as thread
import os
from diraccfg import CFG
from DIRAC import S_OK, S_ERROR, gConfig
from DIRAC.ConfigurationSystem.Client.Helpers import Registry, CSGlobals
from DIRAC.ConfigurationSystem.Client.ConfigurationData import gConfigurationData
from DIRAC.Core.Security.ProxyInfo import getVOfromProxyGroup
from DIRAC.Core.Utilities import LockRing
from DIRAC.Core.Utilities.DErrno import ESECTION


class Operations(object):
    """Operations class

    The /Operations CFG section is maintained in a cache by an Operations object
    """

    __cache = {}
    __cacheVersion = 0
    __cacheLock = LockRing.LockRing().getLock()

    def __init__(self, vo=False, group=False, setup=False):
        """c'tor

        Setting some defaults
        """
        self.__uVO = vo
        self.__uGroup = group
        self.__uSetup = setup
        self.__vo = False
        self.__setup = False
        self.__discoverSettings()

    def __discoverSettings(self):
        """Discovers the vo and the setup"""
        # Set the VO
        globalVO = CSGlobals.getVO()
        if globalVO:
            self.__vo = globalVO
        elif self.__uVO:
            self.__vo = self.__uVO
        elif self.__uGroup:
            self.__vo = Registry.getVOForGroup(self.__uGroup)
            if not self.__vo:
                self.__vo = False
        else:
            result = getVOfromProxyGroup()
            if result["OK"]:
                self.__vo = result["Value"]
        # Set the setup
        self.__setup = False
        if self.__uSetup:
            self.__setup = self.__uSetup
        else:
            self.__setup = CSGlobals.getSetup()

    def __getCache(self):
        Operations.__cacheLock.acquire()
        try:
            currentVersion = gConfigurationData.getVersion()
            if currentVersion != Operations.__cacheVersion:
                Operations.__cache = {}
                Operations.__cacheVersion = currentVersion

            cacheKey = (self.__vo, self.__setup)
            if cacheKey in Operations.__cache:
                return Operations.__cache[cacheKey]

            mergedCFG = CFG()

            for path in self.__getSearchPaths():
                pathCFG = gConfigurationData.mergedCFG[path]
                if pathCFG:
                    mergedCFG = mergedCFG.mergeWith(pathCFG)

            Operations.__cache[cacheKey] = mergedCFG

            return Operations.__cache[cacheKey]
        finally:
            try:
                Operations.__cacheLock.release()
            except thread.error:
                pass

    def __getSearchPaths(self):
        paths = ["/Operations/Defaults", "/Operations/%s" % self.__setup]
        if not self.__vo:
            globalVO = CSGlobals.getVO()
            if not globalVO:
                return paths
            self.__vo = CSGlobals.getVO()
        paths.append("/Operations/%s/Defaults" % self.__vo)
        paths.append("/Operations/%s/%s" % (self.__vo, self.__setup))
        return paths

    def getValue(self, optionPath, defaultValue=None):
        return self.__getCache().getOption(optionPath, defaultValue)

    def __getCFG(self, sectionPath):
        cacheCFG = self.__getCache()
        section = cacheCFG.getRecursive(sectionPath)
        if not section:
            return S_ERROR(ESECTION, "%s in Operations does not exist" % sectionPath)
        sectionCFG = section["value"]
        if isinstance(sectionCFG, six.string_types):
            return S_ERROR("%s in Operations is not a section" % sectionPath)
        return S_OK(sectionCFG)

    def getSections(self, sectionPath, listOrdered=False):
        result = self.__getCFG(sectionPath)
        if not result["OK"]:
            return result
        sectionCFG = result["Value"]
        return S_OK(sectionCFG.listSections(listOrdered))

    def getOptions(self, sectionPath, listOrdered=False):
        result = self.__getCFG(sectionPath)
        if not result["OK"]:
            return result
        sectionCFG = result["Value"]
        return S_OK(sectionCFG.listOptions(listOrdered))

    def getOptionsDict(self, sectionPath):
        result = self.__getCFG(sectionPath)
        if not result["OK"]:
            return result
        sectionCFG = result["Value"]
        data = {}
        for opName in sectionCFG.listOptions():
            data[opName] = sectionCFG[opName]
        return S_OK(data)

    def getPath(self, option, vo=False, setup=False):
        """
        Generate the CS path for an option:

          - if vo is not defined, the helper's vo will be used for multi VO installations
          - if setup evaluates False (except None) -> The helpers setup will  be used
          - if setup is defined -> whatever is defined will be used as setup
          - if setup is None -> Defaults will be used

        :param option: path with respect to the Operations standard path
        :type option: string
        """

        for path in self.__getSearchPaths():
            optionPath = os.path.join(path, option)
            value = gConfig.getValue(optionPath, "NoValue")
            if value != "NoValue":
                return optionPath
        return ""
