package org.implab.gradle.variants.artifacts;

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.InvalidUserDataException;
import org.gradle.api.Task;
import org.gradle.api.file.Directory;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.FileSystemLocation;
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 class ArtifactAssemblyRegistry implements ArtifactAssemblies {
    private final ObjectFactory objects;
    private final TaskContainer tasks;
    private final Map<ArtifactSlot, ArtifactAssembly> assemblies = new LinkedHashMap<>();

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

    public ArtifactAssembly register(
            ArtifactSlot slot,
            String taskName,
            Provider<Directory> outputDirectory,
            FileCollection sources) {

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

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

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

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

        var assembly = new Assembly(outputArtifact, task, output);
        assemblies.put(slot, assembly);
        return assembly;
    }

    @Override
    public Optional<ArtifactAssembly> find(ArtifactSlot slot) {
        return Optional.ofNullable(assemblies.get(slot));
    }

    static class Assembly implements ArtifactAssembly {

        private final Provider<? extends FileSystemLocation> artifact;
        private final TaskProvider<? extends Task> task;
        private final FileCollection fileCollection;

        Assembly(Provider<? extends FileSystemLocation> artifact, TaskProvider<? extends Task> task,
                FileCollection fileCollection) {
            this.artifact = artifact;
            this.task = task;
            this.fileCollection = fileCollection;
        }

        @Override
        public Provider<? extends FileSystemLocation> getArtifact() {
            return artifact;
        }

        @Override
        public TaskProvider<? extends Task> getAssemblyTask() {
            return task;
        }

        @Override
        public FileCollection getFileCollection() {
            return fileCollection;
        }

    }
}
