""" DIRAC API Base Class """

from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import six
import pprint
import sys

from DIRAC import gLogger, gConfig, S_OK, S_ERROR
from DIRAC.Core.Security.ProxyInfo import getProxyInfo, formatProxyInfoAsString
from DIRAC.Core.Utilities.Version import getCurrentVersion
from DIRAC.ConfigurationSystem.Client.Helpers.Registry import getDNForUsername
from DIRAC.ConfigurationSystem.Client.Helpers.Resources import getSites

__RCSID__ = '$Id$'

COMPONENT_NAME = 'API'


def _printFormattedDictList(dictList, fields, uniqueField, orderBy):
  """ Will print ordered the supplied field of a list of dictionaries

      :param list dictList: list of dictionaries
      :param list fields: fields
      :param str uniqueField: unique field
      :param str orderBy: ordered
  """
  orderDict = {}
  fieldWidths = {}
  dictFields = {}
  for myDict in dictList:
    for field in fields:
      fieldValue = myDict[field]
      if field not in fieldWidths:
        fieldWidths[field] = len(str(field))
      if len(str(fieldValue)) > fieldWidths[field]:
        fieldWidths[field] = len(str(fieldValue))
    orderValue = myDict[orderBy]
    if orderValue not in orderDict:
      orderDict[orderValue] = []
    orderDict[orderValue].append(myDict[uniqueField])
    dictFields[myDict[uniqueField]] = myDict
  headString = "%s" % fields[0].ljust(fieldWidths[fields[0]] + 5)
  for field in fields[1:]:
    headString = "%s %s" % (headString, field.ljust(fieldWidths[field] + 5))
  print(headString)
  for orderValue in sorted(orderDict):
    uniqueFields = orderDict[orderValue]
    for uniqueField in sorted(uniqueFields):
      myDict = dictFields[uniqueField]
      outStr = "%s" % str(myDict[fields[0]]).ljust(fieldWidths[fields[0]] + 5)
      for field in fields[1:]:
        outStr = "%s %s" % (outStr, str(myDict[field]).ljust(fieldWidths[field] + 5))
      print(outStr)


# TODO: some of these can just be functions, and moved out of here

class API(object):
  """ An utilities class for APIs
  """

  #############################################################################

  def __init__(self):
    """ c'tor
    """
    self._printFormattedDictList = _printFormattedDictList
    self.log = gLogger.getSubLogger(COMPONENT_NAME)
    self.section = COMPONENT_NAME
    self.pPrint = pprint.PrettyPrinter()
    # Global error dictionary
    self.errorDict = {}
    self.setup = gConfig.getValue('/DIRAC/Setup', 'Unknown')
    self.diracInfo = getCurrentVersion()['Value']
    self._siteSet = set(getSites().get('Value', []))

  #############################################################################

  def __getstate__(self):
    """ Return a copied dictionary containing all the attributes of the API.
        Called when pickling the object. Also used in copy.deepcopy.

    :return: dictionary of attributes
    """
    from DIRAC.FrameworkSystem.private.standardLogging.Logging import Logging
    state = dict(self.__dict__)
    # Replace the Logging instance by its name because it is not copyable
    # because of the thread locks
    if isinstance(state['log'], Logging):
      state['log'] = state['log'].getSubName()
    # In Python 3 PrettyPrinter can't be pickled
    del state['pPrint']
    return state

  def __setstate__(self, state):
    """ Parameter the Job with an attributes dictionary.
        Called when un-pickling the object.

    :params state: attributes dictionary
    """
    self.__dict__.update(state)
    # Build the Logging instance again because it can not be in the dictionary
    # due to the thread locks
    if isinstance(state['log'], six.string_types):
      self.log = gLogger.getSubLogger(state['log'])
    self.pPrint = pprint.PrettyPrinter()

  #############################################################################

  def _errorReport(self, error, message=None):
    """ Internal function to return errors and exit with an S_ERROR()

        :param str error: error
        :param str message: message

        :return: S_ERROR(str)
    """
    if not message:
      message = error

    self.log.warn(error)
    return S_ERROR(message)

  #############################################################################

  def _prettyPrint(self, myObject):
    """ Helper function to pretty print an object.

        :param object myObject: an object to pring
    """
    print(self.pPrint.pformat(myObject))

  #############################################################################

  def _checkSiteIsValid(self, site):
    """Internal function to check that a site name is valid.
    """
    if isinstance(site, (list, set, dict)):
      site = set(site) - self._siteSet
      if not site:
        return S_OK()
    elif site in self._siteSet:
      return S_OK()
    return self._reportError('Specified site %s is not in list of defined sites' % str(site))

  #############################################################################

  def _getCurrentUser(self):
    """ Get current user

        :return: S_OK(dict)/S_ERROR()
    """
    res = getProxyInfo(False, False)
    if not res['OK']:
      return self._errorReport('No proxy found in local environment', res['Message'])
    proxyInfo = res['Value']
    gLogger.debug(formatProxyInfoAsString(proxyInfo))
    if 'group' not in proxyInfo:
      return self._errorReport('Proxy information does not contain the group', res['Message'])
    res = getDNForUsername(proxyInfo['username'])
    if not res['OK']:
      return self._errorReport('Failed to get proxies for user', res['Message'])
    return S_OK(proxyInfo['username'])

  #############################################################################

  def _reportError(self, message, name='', **kwargs):
    """ Internal Function. Gets caller method name and arguments, formats the
        information and adds an error to the global error dictionary to be
        returned to the user.

        :param str message: message
        :param str name: name

        :return: S_ERROR(str)
    """
    className = name
    if not name:
      className = __name__
    methodName = sys._getframe(1).f_code.co_name
    arguments = []
    for key in kwargs:
      if kwargs[key]:
        arguments.append('%s = %s ( %s )' % (key, kwargs[key], type(kwargs[key])))
    finalReport = """Problem with %s.%s() call:
Arguments: %s
Message: %s
""" % (className, methodName, '/'.join(arguments), message)
    if methodName in self.errorDict:
      tmp = self.errorDict[methodName]
      tmp.append(finalReport)
      self.errorDict[methodName] = tmp
    else:
      self.errorDict[methodName] = [finalReport]
    self.log.verbose(finalReport)
    return S_ERROR(finalReport)
