package function.builtin.predicate;

import static function.builtin.GENSYM.GENSYM_PREFIX;
import static sexpression.Nil.NIL;
import static sexpression.Symbol.T;

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

import function.ArgumentValidator;
import function.FunctionNames;
import function.LispFunction;
import sexpression.Cons;
import sexpression.SExpression;

@FunctionNames({ "GENSYM-EQUAL", "GENSYM-EQUAL?" })
public class GENSYM_EQUAL extends LispFunction {

    private ArgumentValidator argumentValidator;

    public GENSYM_EQUAL(String name) {
        this.argumentValidator = new ArgumentValidator(name);
        this.argumentValidator.setExactNumberOfArguments(2);
    }

    @Override
    public SExpression call(Cons argumentList) {
        argumentValidator.validate(argumentList);

        Cons rest = (Cons) argumentList.getRest();
        SExpression firstArgument = argumentList.getFirst();
        SExpression secondArgument = rest.getFirst();

        return gensymEqual(firstArgument, secondArgument);
    }

    private SExpression gensymEqual(SExpression firstArgument, SExpression secondArgument) {
        String firstEqualized = equalizeGensyms(firstArgument);
        String secondEqualized = equalizeGensyms(secondArgument);

        return firstEqualized.equals(secondEqualized) ? T : NIL;
    }

    private String equalizeGensyms(SExpression expression) {
        GensymEqualizer equalizer = new GensymEqualizer(expression.toString());

        return equalizer.equalize();
    }

    private static class GensymEqualizer {

        private static final String GENSYM_REGEX = Pattern.quote(GENSYM_PREFIX) + "[0-9]+";

        Map<String, String> gensymAliases;
        Matcher matcher;
        String expression;
        int counter;

        public GensymEqualizer(String expression) {
            this.gensymAliases = new HashMap<>();
            this.matcher = Pattern.compile(GENSYM_REGEX).matcher(expression);
            this.expression = expression;
            this.counter = 0;
        }

        public String equalize() {
            createGensymAliases();

            return equalizeGensyms();
        }

        private void createGensymAliases() {
            while (matcher.find())
                createAliasForGensym();
        }

        private void createAliasForGensym() {
            String gensym = matcher.group();

            if (isNewGensym(gensym))
                gensymAliases.put(gensym, GENSYM_PREFIX + (counter++));
        }

        private boolean isNewGensym(String gensym) {
            return !gensymAliases.containsKey(gensym);
        }

        private String equalizeGensyms() {
            String equalizedExpression = expression;

            for (Entry<String, String> entry : gensymAliases.entrySet())
                equalizedExpression = equalizedExpression.replace(entry.getKey(), entry.getValue());

            return equalizedExpression;
        }
    }

}
