package org.implab.gradle.variants.sources;

import java.util.Objects;
import java.util.function.Predicate;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.gradle.api.Action;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Named;
import org.implab.gradle.common.core.lang.Closures;
import org.implab.gradle.common.core.lang.Strings;
import org.implab.gradle.common.sources.GenericSourceSet;
import org.implab.gradle.variants.core.Layer;
import org.implab.gradle.variants.core.Variant;
import org.implab.gradle.variants.core.VariantsView;

import groovy.lang.Closure;

@NonNullByDefault
public interface VariantSourcesExtension {

    default void layer(String layerName, Action<? super GenericSourceSet> action) {
        // protect external DSL
        Strings.argumentNotNullOrBlank(layerName, "layerName");
        Objects.requireNonNull(action, "action can't be null");

        whenFinalized(ctx -> ctx.configureLayer(resolveLayer(ctx.getVariants(), layerName), action));
    }

    default void layer(String layerName, Closure<?> closure) {
        layer(layerName, Closures.action(closure));
    }

    default void variant(String variantName, Action<? super GenericSourceSet> action) {
        Strings.argumentNotNullOrBlank(variantName, "variantName");
        Objects.requireNonNull(action, "action can't be null");

        whenFinalized(ctx -> ctx.configureVariant(resolveVariant(ctx.getVariants(), variantName), action));
    }

    default void variant(String variantName, Closure<?> closure) {
        variant(variantName, Closures.action(closure));
    }

    default void unit(String variantName, String layerName, Action<? super GenericSourceSet> action) {
        Strings.argumentNotNullOrBlank(layerName, "layerName");
        Strings.argumentNotNullOrBlank(variantName, "variantName");
        Objects.requireNonNull(action, "action can't be null");

        whenFinalized(ctx -> ctx.configureUnit(resolveCompileUnit(ctx, variantName, layerName), action));
    }

    /**
     * Invoked when finalized variants-derived source context becomes available.
     *
     * Replayable:
     * <ul>
     * <li>if called before variants finalization, action is queued
     * <li>if called after variants finalization, action is invoked immediately
     * </ul>
     */
    void whenFinalized(Action<? super VariantSourcesContext> action);

    default void whenFinalized(Closure<?> closure) {
        whenFinalized(Closures.action(closure));
    }

    private static Layer resolveLayer(VariantsView variants, String name) {
        return variants.getLayers().stream()
                .filter(named(name))
                .findAny()
                .orElseThrow(() -> new IllegalArgumentException("Layer '" + name + "' isn't declared"));
    }

    private static Variant resolveVariant(VariantsView variants, String name) {
        return variants.getVariants().stream()
                .filter(named(name))
                .findAny()
                .orElseThrow(() -> new IllegalArgumentException("Variant '" + name + "' is't declared"));
    }

    private static CompileUnit resolveCompileUnit(VariantSourcesContext ctx, String variantName, String layerName) {
        return ctx.getCompileUnits().findUnit(
                resolveVariant(ctx.getVariants(), variantName),
                resolveLayer(ctx.getVariants(), layerName))
                .orElseThrow(() -> new InvalidUserDataException(
                        "The CompileUnit isn't declared for variant '" + variantName + "', layer '" + layerName + "'"));
    }

    private static Predicate<Named> named(String name) {
        return named -> named.getName().equals(name);
    }
}