#!/usr/bin/python3
# coding=utf-8

import protocol

exported_functions = {}

def export_rp (name, function):
    exported_functions [name] = function

# Je vais soulever cette exception (à transmettre au client) si le client
# appelle une fonction distante avec un nom erroné, qui n'est pas dans la
# table exported_functions.
class InvalidRP (RuntimeError):
   def __init__(self, * args):
      self.args = args

# Je vais soulever cette exception (à transmettre au client) si la requête
# est mal formée.
class InvalidRequest (RuntimeError):
   def __init__(self, * args):
      self.args = args

def serve_request (socket):
    # J'initialise les variables locales don't j'ai besoin dans le corps de
    # cette fonction ici.  En les déclarant directement dans les blocs
    # try..except les variables seraient locales à ces blocs, mais je veux
    # les utiliser partout dans ce corps de fonction.
    request = None
    name = None
    parameters = None
    function = None
    success = True
    result = None
    exception = None
    # Réception de la requête.
    try:
        request = protocol.receive (socket)
    except Exception as e:
        print ('Failed receiving request')
        raise e
    # Décomposition de la requête en nom de fonction et paramètres.
    try:
        # Je vais être très prudent ici.  J'échoue si la requête n'est pas un
        # n-uplet de exactement deux éléments, une chaîne et et une n-uplet.
        if type (request) != tuple or len (request) != 2:
            raise Exception ()
        if type (request [0]) != str or type (request [1]) != tuple:
            raise Exception ()
        name = request [0]
        parameters = request [1]
    except Exception:
        success = False
        exception = InvalidRequest (request)
    # Si la requête contenait un nom de fonction, je cherche la fonction dans le
    # tableau de fonctions exportées.
    if success:
        try:
            function = exported_functions [name]
        except KeyError:
            # Le nom de la fonction n'est pas dans la table.
            # On va transmettre une exception au client.
            success = False
            exception = InvalidRP (name)
    # J'appelle la fonction, si elle est valide...
    if success:
        # ... Mais bien sûr, l'exécution de la fonction peut échouer aussi.
        try:
            result = function (* parameters)
        except Exception as e:
            success = False
            exception = e
    # J'envoie la réponse, qui peut être un succès ou un échec, selon ce qu'on
    # a vu auparavant.  L'envoi de la réponse peut aussi échouer.
    try:
        if success:
            protocol.send (socket, ('success', result))
        else:
            protocol.send (socket, ('error', exception))
    except Exception as e:
        print ('Failed sending response')
        raise e

def serve_requests (socket):
    try:
        while True:
            serve_request (socket)
    except Exception as e:
        print ('Closing after an exception: ', e, 'of type', type (e), 'serving', socket)
    # On est sorti, à cause d'une exception.  Fermons le socket.
    try:
        socket.shutdown (socket.SHUT_RDWR)
        socket.close ()
    except:
        # En cas d'échec on ne fait rien.
        pass


# Côté « client »
###############################################

def call_rp (socket, name, parameters):
    request = (name, parameters)
    protocol.send (socket, request)
    response = protocol.receive (socket)
    if (response [0] == 'success'):
        return response [1]
    else:
        raise response [1]

def rp (socket, name):
    return lambda * parameters: call_rp (socket, name, parameters)
