package net.ranides.assira.observable;

import net.ranides.assira.collection.maps.OpenMap;
import net.ranides.assira.events.EventRouter;
import net.ranides.assira.observable.impl.CGModel;
import net.ranides.assira.reflection.*;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

public class ObservableBeanFactory {

    private static final IArguments ARGS_ROUTER = IArguments.typeinfo(EventRouter.class);

    private static final ObservableBeanFactory SYSTEM = new ObservableBeanFactory(IClassLoader.getSystem());

    private static final Map<String, ObservableBeanFactory> FACTORIES = new ConcurrentHashMap<>();

    static {

        FACTORIES.put(SYSTEM.loader().name(), SYSTEM);

    }

    private final IClassLoader loader;

    private final Map<IClass<?>, IClass> generated = new OpenMap<>();

    private final Map<IClass<?>, Function> constructors = new OpenMap<>();

    private ObservableBeanFactory(IClassLoader loader) {
        this.loader = loader;
    }

    public static ObservableBeanFactory getSystem() {
        return SYSTEM;
    }

    public static ObservableBeanFactory getInstance(IClassLoader loader) {
        return FACTORIES.computeIfAbsent(loader.name(), k -> new ObservableBeanFactory(loader));
    }

    public static ObservableBeanFactory getInstance(String name) {
        return getInstance(IClassLoader.getInstance(name));
    }

    public IClassLoader loader() {
        return loader;
    }

    public <T> T newObservable(Class<T> type, EventRouter router) {
        return newObservable(IClass.typeinfo(type), router);
    }

    public <T> T newObservable(IClass<T> type, EventRouter router) {
        return generateCtor(type).apply(router);
    }

    private <T> Function<EventRouter, T> generateCtor(IClass<T> type) {
        return constructors.computeIfAbsent(type, this::generateCtor0);
    }

    private Function<EventRouter, Object> generateCtor0(IClass<?> type) {
        IClass<?> gtype = generateType(type);
        IConstructor<?> ctor = gtype.constructors().require(ARGS_ROUTER)
            .first()
            .orElseThrow(() -> new IReflectiveException("Generated class doesn't have constructor: " + gtype + " for " + type));
        return ctor::call;
    }

    private <T> IClass<T> generateType(IClass<T> type) {
        return generated.computeIfAbsent(type, this::generateType0);
    }

    private <T> IClass<T> generateType0(IClass<T> type) {
        return new CGModel<>(loader, type).generate();
    }


}
