package org.implab.gradle.variants.sources.internal;

import java.text.MessageFormat;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.gradle.api.InvalidUserDataException;
import org.implab.gradle.common.core.lang.Strings;
import org.implab.gradle.variants.sources.CompileUnit;

public interface CompileUnitNamer {

    String resolveName(CompileUnit unit);

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

    static class Builder {
        private final Set<CompileUnit> units = new HashSet<>();
        private NameCollisionPolicy nameCollisionPolicy = NameCollisionPolicy.FAIL;

        private Builder() {
        }

        public Builder addUnits(Collection<CompileUnit> other) {
            units.addAll(other);
            return this;
        }

        public Builder nameCollisionPolicy(NameCollisionPolicy policy) {
            nameCollisionPolicy = policy;
            return this;
        }

        public CompileUnitNamer build() {
            Map<String, Integer> seen = new HashMap<>();

            if (nameCollisionPolicy == NameCollisionPolicy.FAIL) {
                var collisions = units.stream()
                        .collect(Collectors.groupingBy(this::projectName))
                        .entrySet().stream()
                        .filter(pair -> pair.getValue().size() > 1)
                        .map(pair -> MessageFormat.format(
                                "({0}: {1})",
                                pair.getKey(),
                                pair.getValue().stream()
                                        .map(Object::toString)
                                        .collect(Collectors.joining(","))))
                        .collect(Collectors.joining(","));
                if (!collisions.isEmpty())
                    throw new InvalidUserDataException(
                            "The same source set names are produced by different compile units: " + collisions);
            }

            var unitNames = units.stream()
                    .sorted(Comparator
                            .comparing((CompileUnit unit) -> unit.variant().getName())
                            .thenComparing(unit -> unit.layer().getName()))
                    .collect(Collectors.toUnmodifiableMap(Function.identity(), unit -> {
                        var baseName = projectName(unit);

                        var c = seen.compute(baseName, (key, count) -> count == null ? 1 : count + 1);
                        return c == 1 ? baseName : baseName + String.valueOf(c);
                    }));

            return new CompileUnitNamer() {

                @Override
                public String resolveName(CompileUnit unit) {
                    return Optional.ofNullable(unitNames.get(unit)).orElseThrow(
                            () -> new IllegalArgumentException(MessageFormat.format(
                                    "Compile unit {0} doesn't have an associated name",
                                    unit)));
                }

            };
        }

        private String projectName(CompileUnit unit) {
            return unit.variant().getName() + Strings.capitalize(unit.layer().getName());
        }
    }
}
