package org.implab.gradle.variants;

import org.gradle.api.Action;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.model.ObjectFactory;
import org.implab.gradle.common.core.lang.Deferred;
import org.implab.gradle.variants.core.Layer;
import org.implab.gradle.variants.core.Role;
import org.implab.gradle.variants.core.Variant;
import org.implab.gradle.variants.core.VariantDefinition;
import org.implab.gradle.variants.core.VariantsExtension;
import org.implab.gradle.variants.core.VariantsView;

/**
 * <ul>
 * <li> {@link Variant} defines compilation semantics
 * <li> {@link Layer} defines compilation partition
 * <li> {@link Role} defines result grouping / publication intent
 * </ul>
 *
 */
public abstract class VariantsPlugin implements Plugin<Project> {
    @Override
    public void apply(Project target) {

        var objectFactory = target.getObjects();

        var variantsExtension = new DefaultVariantsExtension(objectFactory);
        target.getExtensions().add(VariantsExtension.class, "variants", variantsExtension);
        target.afterEvaluate(t -> variantsExtension.finalizeExtension());

    }

    static class DefaultVariantsExtension implements VariantsExtension {

        private final NamedDomainObjectContainer<Layer> layers;
        private final NamedDomainObjectContainer<Role> roles;
        private final NamedDomainObjectContainer<VariantDefinition> variantDefinitions;
        private final Deferred<VariantsView> finalizedResult = new Deferred<>();
        private final ObjectFactory objectFactory;
        private boolean finalized = false;

        public DefaultVariantsExtension(ObjectFactory objectFactory) {
            this.objectFactory = objectFactory;
            this.layers = objectFactory.domainObjectContainer(Layer.class);
            this.roles = objectFactory.domainObjectContainer(Role.class);
            this.variantDefinitions = objectFactory.domainObjectContainer(VariantDefinition.class);
        }

        @Override
        public NamedDomainObjectContainer<Layer> getLayers() {
            return layers;
        }

        @Override
        public NamedDomainObjectContainer<Role> getRoles() {
            return roles;
        }

        @Override
        public NamedDomainObjectContainer<VariantDefinition> getVariantDefinitions() {
            return variantDefinitions;
        }

        @Override
        public void whenFinalized(Action<? super VariantsView> action) {
            finalizedResult.whenResolved(action::execute);
        }

        void finalizeExtension() {
            if (finalized)
                return;

            finalized = true;

            // freeze defined variants
            variantDefinitions.forEach(VariantDefinition::finalizeVariant);

            // build a snapshot
            var viewBuilder = VariantsView.builder();

            // calculate and add variants
            variantDefinitions.stream()
                    .map(def -> objectFactory.named(Variant.class, def.getName()))
                    .forEach(viewBuilder::addVariant);
            // add layers
            layers.forEach(viewBuilder::addLayer);
            // add roles
            roles.forEach(viewBuilder::addRole);
            // add definitions
            variantDefinitions.forEach(viewBuilder::addDefinition);
            // assemble the view
            var view = viewBuilder.build();
            // set the result and call hooks
            finalizedResult.resolve(view);
        }
    }

}
