package org.implab.gradle.common.sources;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Optional;

import javax.inject.Inject;

import org.implab.gradle.common.core.lang.Closures;
import org.gradle.api.Action;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Named;
import org.gradle.api.model.ObjectFactory;

import groovy.lang.Closure;

public abstract class BuildVariant implements Named {
    private final String name;
    private final ObjectFactory objects;
    private boolean finalized;

    private final LinkedHashMap<String, BuildRole> roles = new LinkedHashMap<>();

    @Inject
    public BuildVariant(String name, ObjectFactory objects) {
        this.name = name;
        this.objects = objects;
    }

    @Override
    public String getName() {
        return name;
    }

    public Collection<BuildRole> getRoles() {
        return Collections.unmodifiableCollection(roles.values());
    }

    public void roles(Action<? super RolesSpec> action) {
        ensureMutable("configure roles");
        action.execute(new RolesSpec());
    }

    public void roles(Closure<?> configure) {
        roles(Closures.action(configure));
    }

    public BuildRole role(String name, Action<? super BuildRole> configure) {
        ensureMutable("configure roles");
        var role = roles.computeIfAbsent(name, this::newRole);
        configure.execute(role);
        return role;
    }

    public BuildRole role(String name, Closure<?> configure) {
        return role(name, Closures.action(configure));
    }

    public BuildRole role(String name) {
        return role(name, r -> {
        });
    }

    public Optional<BuildRole> findRole(String name) {
        return Optional.ofNullable(roles.get(name));
    }

    public BuildRole requireRole(String name) {
        return findRole(name)
                .orElseThrow(() -> new InvalidUserDataException(
                        "Variant '" + this.name + "' doesn't define role '" + name + "'"));
    }

    void finalizeModel() {
        if (finalized)
            return;

        for (var role : roles.values())
            role.finalizeModel();

        finalized = true;
    }

    private BuildRole newRole(String roleName) {
        return objects.newInstance(BuildRole.class, roleName);
    }

    private void ensureMutable(String operation) {
        if (finalized)
            throw new InvalidUserDataException("Variant '" + name + "' is finalized and cannot " + operation);
    }

    public final class RolesSpec {
        public BuildRole role(String name, Action<? super BuildRole> configure) {
            return BuildVariant.this.role(name, configure);
        }

        public BuildRole role(String name, Closure<?> configure) {
            return BuildVariant.this.role(name, configure);
        }

        public BuildRole role(String name) {
            return BuildVariant.this.role(name);
        }

        public Collection<BuildRole> getAll() {
            return BuildVariant.this.getRoles();
        }

        public Optional<BuildRole> find(String name) {
            return BuildVariant.this.findRole(name);
        }

        public BuildRole require(String name) {
            return BuildVariant.this.requireRole(name);
        }
    }
}
