package net.sssubtlety.leaves_us_in_peace.text;

import com.google.common.collect.ImmutableMap;

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class StringSubstituter {
    private static final String ILLEGAL_CHARACTERS = "${}";
    private static final Pattern ILLEGAL_CHARACTER_PATTERN = Pattern.compile(Pattern.quote(ILLEGAL_CHARACTERS));

    private final ImmutableMap<String, String> substitutions;
    private final ImmutableMap<Pattern, String> patternSubstitutions;
    private final Pattern namesPattern;

    public StringSubstituter(Map<String, String> substitutions) {
        this.substitutions = makeSubstitutionMap(substitutions);
        this.patternSubstitutions = makePatternSubstitutionMap();
        this.namesPattern = makeNamesPatternAndGroups2Values();
    }

    private static ImmutableMap<String, String> makeSubstitutionMap(Map<String, String> replacements) {
        final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
        replacements.forEach((name, value) -> {
            final Matcher matcher = ILLEGAL_CHARACTER_PATTERN.matcher(name);
            if (matcher.find()) throw new IllegalArgumentException(
                "Name \"" + name + "\" contains illegal character: '" + matcher.group() + "'");

            builder.put("${" + name + "}", value);
        });

        return builder.build();
    }

    private ImmutableMap<Pattern, String> makePatternSubstitutionMap() {
        final ImmutableMap.Builder<Pattern, String> builder = ImmutableMap.builder();
        this.substitutions.forEach((key, value) -> builder.put(Pattern.compile(Pattern.quote(key)), Matcher.quoteReplacement(value)));
        return builder.build();
    }

    private Pattern makeNamesPatternAndGroups2Values() {
        final StringBuilder namesPatternBuilder = new StringBuilder(Pattern.quote("${")).append("(");
        int i = 1;
        final var replacementsItr = substitutions.entrySet().iterator();
        Map.Entry<String, String> entry;
        while (replacementsItr.hasNext()) {
            entry = replacementsItr.next();
            if (i > 1) namesPatternBuilder.append("|");
            namesPatternBuilder.append(Pattern.quote(entry.getKey()));
            i++;
        }
        return Pattern.compile(namesPatternBuilder.append(")").toString());
    }

    public String substitute1(String string) {
        for (var entry : substitutions.entrySet()) {
            string = string.replace(entry.getKey(), entry.getValue());
        }
        return string;
    }

    public String substitute2(String string) {
        for (var entry : patternSubstitutions.entrySet()) {
            string = entry.getKey().matcher(string).replaceAll(Matcher.quoteReplacement(entry.getValue()));
        }
        return string;
    }

    public String substitute3(String string) {
        final Matcher matcher = namesPattern.matcher(string);
        return matcher.replaceAll(matchResult -> substitutions.get(matchResult.group()));
    }
}