package org.implab.gradle.common.sources;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.gradle.api.Action;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Task;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.Directory;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Copy;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.language.base.plugins.LifecycleBasePlugin;

@NonNullByDefault
public final class ArtifactAssemblyRegistry {
    private final ObjectFactory objects;
    private final TaskContainer tasks;
    private final Map<String, ArtifactAssembly> assemblies = new LinkedHashMap<>();

    public ArtifactAssemblyRegistry(ObjectFactory objects, TaskContainer tasks) {
        this.objects = objects;
        this.tasks = tasks;
    }

    public ArtifactAssembly register(
            String name,
            String taskName,
            Provider<Directory> outputDirectory,
            Action<? super ConfigurableFileCollection> configureSources) {

        var sources = objects.fileCollection();
        configureSources.execute(sources);

        var task = tasks.register(taskName, Copy.class, copy -> {
            copy.setGroup(LifecycleBasePlugin.BUILD_GROUP);
            copy.into(outputDirectory);
            copy.from(sources);
        });

        return register(name, task, t -> outputDirectory);
    }

    public <T extends Task> ArtifactAssembly register(
            String name,
            TaskProvider<T> task,
            Function<? super T, ? extends Provider<Directory>> mapOutputDirectory) {
        if (assemblies.containsKey(name)) {
            throw new InvalidUserDataException("Artifact assembly '" + name + "' is already registered");
        }
        var outputDirectory = task.flatMap(t -> mapOutputDirectory.apply(t));

        var output = objects.fileCollection()
                .from(outputDirectory)
                .builtBy(task);

        var assembly = new ArtifactAssembly(name, outputDirectory, task, output);
        assemblies.put(name, assembly);
        return assembly;
    }

    public Optional<ArtifactAssembly> find(String name) {
        return Optional.ofNullable(assemblies.get(name));
    }

    public ArtifactAssembly require(String name) {
        return find(name)
                .orElseThrow(() -> new InvalidUserDataException("Artifact assembly '" + name + "' isn't registered"));
    }
}
