package net.ranides.assira.events;

import net.ranides.assira.functional.special.AnyFunction;
import net.ranides.assira.reflection.IClass;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * TaskBroker is a class which allows you to schedule method calls to "service"
 * and wait for results.
 *
 * Calling code will use interface "S" to make ordinary calls.
 *
 * Service implementing "S" interfaces is hidden.
 *
 * TaskRouter is used to schedule method calls: it could use thread-pool for example, to execute
 * many requests in paralell.
 *
 * TaskRouter sends result back and interface "S" returns value to calling client.
 *
 * @param <S> S
 */
public class TaskBroker<S> {

    private final IClass<S> type;

    private final TaskRouter router;

    private final Map<Method, AnyFunction<Object>> reflective;

    /**
     * Creates new broker, which is able to create clients of type "S".
     * Provided "service" will be called in the background, to handle requests.
     *
     * Service should be thread-safe, preferably stateless.
     *
     * Internally, EventProactor is used for task scheduling.
     *
     * @param type type
     * @param service service
     */
    public TaskBroker(IClass<S> type, S service) {
        this(type, service, new TaskRouter(EventProactor.newInstance(service.getClass().getName())));
    }

    /**
     * Creates new broker, which is able to create clients of type "S".
     * Provided "service" will be called in the background, to handle requests.
     *
     * Service should be thread-safe, preferably stateless.
     *
     * @param type type
     * @param service service
     * @param router router
     */
    public TaskBroker(IClass<S> type, S service, TaskRouter router) {
        this.type = type;
        this.router = router;
        this.reflective = type.methods().collect(Collectors.toMap(m -> m.reflective(), m -> m.bind(service)));
    }

    /**
     * Returns client object, of type "S" which can be used to make a calls to service.
     *
     * Client code won't see that some paralell scheduling is done internally.
     *
     * @return S
     */
    public S client() {
        ClassLoader c = type.raw().getClassLoader();
        Class[] i = {type.raw()};
        Object o = Proxy.newProxyInstance(c, i, (p, method, args) -> router.await(() -> reflective.get(method).apply(args) ));
        return type.cast(o);
    }

}
