package org.implab.gradle.variants.core;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.gradle.api.InvalidUserDataException;

@NonNullByDefault
public class VariantsView {
    private final Set<Layer> layers;
    private final Set<Role> roles;
    private final Set<Variant> variants;
    private final Set<VariantRoleLayer> entries;

    private final Map<Variant, Set<VariantRoleLayer>> entriesByVariant;
    private final Map<Role, Set<VariantRoleLayer>> entriesByRole;
    private final Map<Layer, Set<VariantRoleLayer>> entriesByLayer;

    private VariantsView(Set<Layer> layers, Set<Role> roles, Set<Variant> variants, Set<VariantRoleLayer> entries) {
        this.layers = layers;
        this.roles = roles;
        this.variants = variants;
        this.entries = entries;
        this.entriesByVariant = entries.stream()
                .collect(Collectors.groupingBy(VariantRoleLayer::variant, Collectors.toSet()));
        this.entriesByRole = entries.stream()
                .collect(Collectors.groupingBy(VariantRoleLayer::role, Collectors.toSet()));
        this.entriesByLayer = entries.stream()
                .collect(Collectors.groupingBy(VariantRoleLayer::layer, Collectors.toSet()));
    }

    public Set<Layer> getLayers() {
        return layers;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public Set<Variant> getVariants() {
        return variants;
    }

    public Set<VariantRoleLayer> getEntries() {
        return entries;
    }

    public Set<VariantRoleLayer> getEntriesForVariant(Variant variant) {
        return entriesByVariant.getOrDefault(variant, Set.of());
    }

    public Set<VariantRoleLayer> getEntriesForLayer(Layer layer) {
        return entriesByLayer.getOrDefault(layer, Set.of());
    }

    public Set<VariantRoleLayer> getEntriesForRole(Role role) {
        return entriesByRole.getOrDefault(role, Set.of());
    }

    public record VariantRoleLayer(Variant variant, Role role, Layer layer) {
    }

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {

        private final Map<String, Layer> layers = new LinkedHashMap<>();
        private final Map<String, Role> roles = new LinkedHashMap<>();
        private final Map<String, Variant> variants = new LinkedHashMap<>();
        private final List<VariantDefinition> definitions = new ArrayList<>();

        private Builder() {
        }

        public Builder addRole(Role role) {
            Objects.requireNonNull(role, "role can't be null");
            roles.put(role.getName(), role);
            return this;
        }

        public Builder addLayer(Layer layer) {
            Objects.requireNonNull(layer, "layer can't be null");
            layers.put(layer.getName(), layer);
            return this;
        }

        public Builder addVariant(Variant variant) {
            Objects.requireNonNull(variant, "variant can't be null");
            variants.put(variant.getName(), variant);
            return this;
        }

        public Builder addDefinition(VariantDefinition definition) {
            Objects.requireNonNull(definition, "definition can't be null");
            definitions.add(definition);
            return this;
        }

        public VariantsView build() {

            var entries = definitions.stream()
                    .flatMap(def -> def.getRoleBindings().get().stream()
                            .map(layerRole -> createVariantRoleLayer(
                                    def.getName(), // variantName
                                    layerRole.roleName(),
                                    layerRole.layerName())))
                    .collect(Collectors.toSet());

            return new VariantsView(
                    Set.copyOf(layers.values()),
                    Set.copyOf(roles.values()),
                    Set.copyOf(variants.values()),
                    entries);
        }

        private VariantRoleLayer createVariantRoleLayer(String variantName, String roleName, String layerName) {
            return new VariantRoleLayer(
                    resolveVariant(variantName,
                            "Variant '" + variantName + "' isn't declared"),
                    resolveRole(roleName,
                            "Role '" + roleName + "' isn't declared, referenced in '" + variantName + "' variant"),
                    resolveLayer(layerName,
                            "Layer '" + layerName + "' isn't declared, referenced in '" + variantName
                                    + "' variant with '" + roleName + "' role"));
        }

        private Layer resolveLayer(String layerName, String errorMessage) {
            return Optional.ofNullable(layers.get(layerName))
                    .orElseThrow(() -> new InvalidUserDataException(errorMessage));
        }

        private Variant resolveVariant(String variantName, String errorMessage) {
            return Optional.ofNullable(variants.get(variantName))
                    .orElseThrow(() -> new InvalidUserDataException(errorMessage));
        }

        private Role resolveRole(String roleName, String errorMessage) {
            return Optional.ofNullable(roles.get(roleName))
                    .orElseThrow(() -> new InvalidUserDataException(errorMessage));
        }
    }
}
