import json

import requests
from tinyrpc.protocols.jsonrpc import JSONRPCErrorResponse, JSONRPCProtocol, JSONRPCSuccessResponse, RPCError


class Struct:
    def __init__(self, **entries):
        self.__dict__.update(entries)


class RPCClient(object):
    """Client for making RPC calls to connected servers.
    :param protocol: An :py:class:`~tinyrpc.RPCProtocol` instance.
    :param transport: A :py:class:`~tinyrpc.transports.ClientTransport`
                      instance.
    """
    JSON_RPC_VERSION = "2.0"
    _ALLOWED_REPLY_KEYS = sorted(['id', 'jsonrpc', 'error', 'result'])
    _ALLOWED_REQUEST_KEYS = sorted(['id', 'jsonrpc', 'method', 'params'])

    def parse_reply(self, data):
        try:
            rep = json.loads(data)
        except Exception as e:
            raise InvalidReplyError(e)

        for k in rep.keys():
            if not k in self._ALLOWED_REPLY_KEYS:
                raise InvalidReplyError('Key not allowed: %s' % k)

        if not 'jsonrpc' in rep:
            raise InvalidReplyError('Missing jsonrpc (version) in response.')

        if rep['jsonrpc'] != self.JSON_RPC_VERSION:
            raise InvalidReplyError('Wrong JSONRPC version')

        if not 'id' in rep:
            raise InvalidReplyError('Missing id in response')

        if ('error' in rep) == ('result' in rep):
            raise InvalidReplyError(
                'Reply must contain exactly one of result and error.'
            )

        if 'error' in rep:
            response = JSONRPCErrorResponse()
            error = rep['error']
            response.error = error['message']
            response._jsonrpc_error_code = error['code']
        else:
            response = JSONRPCSuccessResponse()
            response.result = rep.get('result', None)

        response.unique_id = rep['id']

        return response

    def __init__(self, url):
        self.protocol = JSONRPCProtocol()
        self.url = url

    def _send_and_handle_reply(self, req):
        #        print (req.serialize()) # show JSON string
        headers = {'content-type': 'application/json'}
        reply = requests.post(self.url, req.serialize(), headers=headers)
        #        print (reply.json());
        response = self.parse_reply(str(reply.json()).replace("'", '"'))

        if hasattr(response, 'error'):
            raise RPCError('Error calling remote procedure: %s' % \
                           response.error)

        return response

    def call(self, method, args, kwargs, one_way=False):
        """Calls the requested method and returns the result.
        If an error occured, an :py:class:`~tinyrpc.exc.RPCError` instance
        is raised.
        :param method: Name of the method to call.
        :param args: Arguments to pass to the method.
        :param kwargs: Keyword arguments to pass to the method.
        :param one_way: Whether or not a reply is desired.
        """
        req = self.protocol.create_request(method, args, kwargs, one_way)

        return self._send_and_handle_reply(req).result

    def get_proxy(self, prefix='', one_way=False):
        """Convenience method for creating a proxy.
        :param prefix: Passed on to :py:class:`~tinyrpc.client.RPCProxy`.
        :param one_way: Passed on to :py:class:`~tinyrpc.client.RPCProxy`.
        :return: :py:class:`~tinyrpc.client.RPCProxy` instance."""
        return RPCProxy(self, prefix, one_way)

    def batch_call(self, calls):
        """Experimental, use at your own peril."""
        req = self.protocol.create_batch_request()

        for call_args in calls:
            req.append(self.protocol.create_request(*call_args))

        return self._send_and_handle_reply(req)


class RPCProxy(object):
    """Create a new remote proxy object.
    Proxies allow calling of methods through a simpler interface. See the
    documentation for an example.
    :param client: An :py:class:`~tinyrpc.client.RPCClient` instance.
    :param prefix: Prefix to prepend to every method name.
    :param one_way: Passed to every call of
                    :py:func:`~tinyrpc.client.call`.
    """

    def __init__(self, client, prefix='', one_way=False):
        self.client = client
        self.prefix = prefix
        self.one_way = one_way

    def __getattr__(self, name):
        """Returns a proxy function that, when called, will call a function
        name ``name`` on the client associated with the proxy.
        """
        proxy_func = lambda *args, **kwargs: self.client.call(
            self.prefix + name,
            args,
            kwargs,
            one_way=self.one_way
        )
        return proxy_func

import json

import requests
from tinyrpc.protocols.jsonrpc import JSONRPCErrorResponse, JSONRPCProtocol, JSONRPCSuccessResponse, RPCError


class Struct:
    def __init__(self, **entries):
        self.__dict__.update(entries)


class RPCClient(object):
    """Client for making RPC calls to connected servers.
    :param protocol: An :py:class:`~tinyrpc.RPCProtocol` instance.
    :param transport: A :py:class:`~tinyrpc.transports.ClientTransport`
                      instance.
    """
    JSON_RPC_VERSION = "2.0"
    _ALLOWED_REPLY_KEYS = sorted(['id', 'jsonrpc', 'error', 'result'])
    _ALLOWED_REQUEST_KEYS = sorted(['id', 'jsonrpc', 'method', 'params'])

    def parse_reply(self, data):
        try:
            rep = json.loads(data)
        except Exception as e:
            raise InvalidReplyError(e)

        for k in rep.keys():
            if not k in self._ALLOWED_REPLY_KEYS:
                raise InvalidReplyError('Key not allowed: %s' % k)

        if not 'jsonrpc' in rep:
            raise InvalidReplyError('Missing jsonrpc (version) in response.')

        if rep['jsonrpc'] != self.JSON_RPC_VERSION:
            raise InvalidReplyError('Wrong JSONRPC version')

        if not 'id' in rep:
            raise InvalidReplyError('Missing id in response')

        if ('error' in rep) == ('result' in rep):
            raise InvalidReplyError(
                'Reply must contain exactly one of result and error.'
            )

        if 'error' in rep:
            response = JSONRPCErrorResponse()
            error = rep['error']
            response.error = error['message']
            response._jsonrpc_error_code = error['code']
        else:
            response = JSONRPCSuccessResponse()
            response.result = rep.get('result', None)

        response.unique_id = rep['id']

        return response

    def __init__(self, url):
        self.protocol = JSONRPCProtocol()
        self.url = url

    def _send_and_handle_reply(self, req):
        #        print (req.serialize()) # show JSON string
        headers = {'content-type': 'application/json'}
        reply = requests.post(self.url, req.serialize(), headers=headers)
        #        print (reply.json());
        response = self.parse_reply(str(reply.json()).replace("'", '"'))

        if hasattr(response, 'error'):
            raise RPCError('Error calling remote procedure: %s' % \
                           response.error)

        return response

    def call(self, method, args, kwargs, one_way=False):
        """Calls the requested method and returns the result.
        If an error occured, an :py:class:`~tinyrpc.exc.RPCError` instance
        is raised.
        :param method: Name of the method to call.
        :param args: Arguments to pass to the method.
        :param kwargs: Keyword arguments to pass to the method.
        :param one_way: Whether or not a reply is desired.
        """
        req = self.protocol.create_request(method, args, kwargs, one_way)

        return self._send_and_handle_reply(req).result

    def get_proxy(self, prefix='', one_way=False):
        """Convenience method for creating a proxy.
        :param prefix: Passed on to :py:class:`~tinyrpc.client.RPCProxy`.
        :param one_way: Passed on to :py:class:`~tinyrpc.client.RPCProxy`.
        :return: :py:class:`~tinyrpc.client.RPCProxy` instance."""
        return RPCProxy(self, prefix, one_way)

    def batch_call(self, calls):
        """Experimental, use at your own peril."""
        req = self.protocol.create_batch_request()

        for call_args in calls:
            req.append(self.protocol.create_request(*call_args))

        return self._send_and_handle_reply(req)


class RPCProxy(object):
    """Create a new remote proxy object.
    Proxies allow calling of methods through a simpler interface. See the
    documentation for an example.
    :param client: An :py:class:`~tinyrpc.client.RPCClient` instance.
    :param prefix: Prefix to prepend to every method name.
    :param one_way: Passed to every call of
                    :py:func:`~tinyrpc.client.call`.
    """

    def __init__(self, client, prefix='', one_way=False):
        self.client = client
        self.prefix = prefix
        self.one_way = one_way

    def __getattr__(self, name):
        """Returns a proxy function that, when called, will call a function
        name ``name`` on the client associated with the proxy.
        """
        proxy_func = lambda *args, **kwargs: self.client.call(
            self.prefix + name,
            args,
            kwargs,
            one_way=self.one_way
        )
        return proxy_func

Client for making RPC calls to connected servers.     :param protocol: An :py:class:`~tinyrpc.RPCProtocol` instance.     :param transport: A :py:class:`~tinyrpc.transports.ClientTransport`                       instance. #        print (req.serialize()) # show JSON string #        print (reply.json()); Calls the requested method and returns the result.         If an error occured, an :py:class:`~tinyrpc.exc.RPCError` instance         is raised.         :param method: Name of the method to call.         :param args: Arguments to pass to the method.         :param kwargs: Keyword arguments to pass to the method.         :param one_way: Whether or not a reply is desired. Convenience method for creating a proxy.         :param prefix: Passed on to :py:class:`~tinyrpc.client.RPCProxy`.         :param one_way: Passed on to :py:class:`~tinyrpc.client.RPCProxy`.         :return: :py:class:`~tinyrpc.client.RPCProxy` instance. Experimental, use at your own peril. Create a new remote proxy object.     Proxies allow calling of methods through a simpler interface. See the     documentation for an example.     :param client: An :py:class:`~tinyrpc.client.RPCClient` instance.     :param prefix: Prefix to prepend to every method name.     :param one_way: Passed to every call of                     :py:func:`~tinyrpc.client.call`. Returns a proxy function that, when called, will call a function         name ``name`` on the client associated with the proxy.