package org.implab.gradle.variants;

import org.gradle.api.Action;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.implab.gradle.common.core.lang.Deferred;
import org.implab.gradle.variants.artifacts.OutgoingConfigurationSpec;
import org.implab.gradle.variants.artifacts.OutgoingVariantsContext;
import org.implab.gradle.variants.artifacts.VariantArtifactsExtension;
import org.implab.gradle.variants.artifacts.VariantArtifactsSpec;
import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyBinder;
import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyHandler;
import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyRegistry;
import org.implab.gradle.variants.artifacts.internal.DefaultOutgoingVariantsContext;
import org.implab.gradle.variants.artifacts.internal.MaterializationPolicyHandler;
import org.implab.gradle.variants.artifacts.internal.OutgoingRegistry;
import org.implab.gradle.variants.artifacts.internal.SingleSlotConvention;
import org.implab.gradle.variants.core.Variant;
import org.implab.gradle.variants.core.VariantsExtension;
import org.implab.gradle.variants.sources.VariantSourcesExtension;
import org.implab.gradle.variants.artifacts.OutgoingConfigurationSlotSpec;

public abstract class VariantArtifactsPlugin implements Plugin<Project> {
    public static final String VARIANT_ARTIFACTS_EXTENSION = "variantArtifacts";

    @Override
    public void apply(Project target) {
        var extensions = target.getExtensions();
        var objects = target.getObjects();
        var providers = target.getProviders();
        var plugins = target.getPlugins();
        var configurations = target.getConfigurations();
        var tasks = target.getTasks();
        var layout = target.getLayout();

        // Apply the main VariantsPlugin to ensure the core variant model is available.
        plugins.apply(VariantsPlugin.class);
        plugins.apply(VariantSourcesPlugin.class);
        // Access the VariantsExtension to configure variant sources.
        var variantsExtension = extensions.getByType(VariantsExtension.class);
        var sourcesExtension = extensions.getByType(VariantSourcesExtension.class);

        var deferred = new Deferred<OutgoingVariantsContext>();

        variantsExtension.whenFinalized(variants -> {

            var outgoing = new OutgoingRegistry(configurations, objects, variants.getVariants());
            var assemblies = new ArtifactAssemblyRegistry();

            // wire artifact assemblies to configuration variants
            var assembliesBridge = new ArtifactAssemblyBinder(assemblies);
            var primarySlotConvention = new SingleSlotConvention(providers);
            var materializationHandler = new MaterializationPolicyHandler(objects);

            // bind slot assemblies to outgoing variants
            outgoing.configureEach(assembliesBridge::execute);
            // apply primary slot convention when outgoing variant has a single slot
            outgoing.configureEach(primarySlotConvention::execute);
            // apply materialization policy hooks to outgoing variants
            outgoing.configureEach(materializationHandler::execute);

            sourcesExtension.whenAvailable(sources -> {
                var assemblyHandler = new ArtifactAssemblyHandler(
                        objects,
                        tasks,
                        assemblies,
                        sources.getCompileUnits(),
                        sources.getRoleProjections(),
                        sources.getSourceSets());
                assemblyHandler.getAssembliesDirectory().set(layout.getBuildDirectory().dir("variant-assemblies"));

                deferred.resolve(new DefaultOutgoingVariantsContext(
                        assemblies,
                        outgoing,
                        assemblyHandler,
                        materializationHandler));
            });

        });

        var variantArtifacts = new VariantArtifactsExtension() {

            @Override
            public void variant(String variantName, Action<? super VariantArtifactsSpec> action) {
                deferred.whenResolved(context -> {
                    var variant = objects.named(Variant.class, variantName);
                    context.configureVariant(variant, action);
                });
            }

            @Override
            public void whenAvailable(Action<? super OutgoingVariantsContext> action) {
                deferred.whenResolved(handler -> action.execute(handler));
            }

            @Override
            public void whenOutgoingConfiguration(Action<? super OutgoingConfigurationSpec> action) {
                deferred.whenResolved(registry -> registry.whenOutgoingConfiguration(action));

            }

            @Override
            public void whenOutgoingSlot(Action<? super OutgoingConfigurationSlotSpec> action) {
                deferred.whenResolved(registry -> registry.whenOutgoingSlot(action));
            }

        };

        extensions.add(VariantArtifactsExtension.class, VARIANT_ARTIFACTS_EXTENSION, variantArtifacts);

    }

}
