""" RPCClient object is used to create RPC connection to services
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

__RCSID__ = "$Id$"


from DIRAC.Core.DISET.private.InnerRPCClient import InnerRPCClient


class _MagicMethod(object):
    """This object allows to bundle together a function calling
    an RPC and the remote function name.
    When this object is called (__call__), the call is performed.
    """

    def __init__(self, doRPCFunc, remoteFuncName):
        """Constructor

        :param doRPCFunc: the function actually performing the RPC call
        :param remoteFuncName: name of the remote function
        """
        self.__doRPCFunc = doRPCFunc
        self.__remoteFuncName = remoteFuncName

    def __getattr__(self, remoteFuncName):
        """I really do not understand when this would be called.
        I can only imagine it being called by dir, or things like that.
        In any case, it recursively return a MagicMethod object
        where the new remote function name is the old one to which
        we append the new called attribute.
        """
        return _MagicMethod(self.__doRPCFunc, "%s.%s" % (self.__remoteFuncName, remoteFuncName))

    def __call__(self, *args, **kwargs):
        """Triggers the call.
        it uses the RPC calling function given by RPCClient,
        and gives as argument the remote function name and whatever
        arguments given.
        """
        return self.__doRPCFunc(self.__remoteFuncName, args, **kwargs)

    def __str__(self):
        return "<RPCClient method %s>" % self.__remoteFuncName


class RPCClient(object):
    """This class contains the mechanism to convert normal calls to RPC calls.

    When instanciated, it creates a :class:`~DIRAC.Core.DISET.private.InnerRPCClient.InnerRPCClient`
    as an attribute. Any attribute which is accessed is then either redirected to InnerRPCClient if it has it,
    or creates a MagicMethod object otherwise. If the attribute is a function, MagicMethod will
    trigger the RPC call, using the InnerRPCClient.

    The typical workflow looks like this::

      rpc = RPCClient('DataManagement/FileCatalog')

      # Here, func is the ping function, which we call remotely.
      # We go through RPCClient.__getattr__ which returns us a MagicMethod object
      func = rpc.ping

      # Here we call the method __call__ of the MagicMethod
      func()

    """

    def __init__(self, *args, **kwargs):
        """
        Constructor
        The arguments are just passed on to InnerRPCClient.
        In practice:

          * args: has to be the service name or URL
          * kwargs: all the arguments InnerRPCClient and BaseClient accept as configuration
        """
        self.__innerRPCClient = InnerRPCClient(*args, **kwargs)

    def __doRPC(self, sFunctionName, args, **kwargs):
        """
        Execute the RPC action. This is given as an attribute
        to MagicMethod

        :param sFunctionName: name of the remote function
        :param args: arguments to pass to the function
        """
        return self.__innerRPCClient.executeRPC(sFunctionName, args, **kwargs)

    def __getattr__(self, attrName):
        """Function for emulating the existence of functions.

          In literature this is usually called a "stub function".
        If the attribute exists in InnerRPCClient, return it,
        otherwise we create a _MagicMethod instance

        """
        if attrName in dir(self.__innerRPCClient):
            return getattr(self.__innerRPCClient, attrName)
        return _MagicMethod(self.__doRPC, attrName)


def executeRPCStub(rpcStub):
    """
    Playback a stub
    """
    # Generate a RPCClient with the same parameters
    rpcClient = RPCClient(rpcStub[0][0], **rpcStub[0][1])
    # Get a functor to execute the RPC call
    rpcFunc = getattr(rpcClient, rpcStub[1])
    # Reproduce the call
    return rpcFunc(*rpcStub[2])
