package site.higgs.limiter.expression;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.ObjectUtils;
import site.higgs.limiter.Limiter;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


public class LimiterOperationExpressionEvaluator {

    private final SpelExpressionParser parser = new SpelExpressionParser();

    private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();


    private final Map<ExpressionKey, Expression> keyCache = new ConcurrentHashMap<>(64);


    public EvaluationContext createEvaluationContext(Limiter limiter, Method method, Object[] args, Object target, Class<?> targetClass, Method targetMethod,
                                                     Map<String, Object> injectArgs, BeanFactory beanFactory) {

        LimiterExpressionRootObject rootObject = new LimiterExpressionRootObject(limiter, method, args, target, targetClass);
        MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(rootObject, targetMethod, args, this.parameterNameDiscoverer);
        for (String key : injectArgs.keySet()) {
            evaluationContext.setVariable(key, injectArgs.get(key));
        }

        if (beanFactory != null) {
            evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
        }
        return evaluationContext;
    }

    public Object key(String keyExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
        return getExpression(methodKey, keyExpression).getValue(evalContext);
    }

    protected Expression getExpression(AnnotatedElementKey elementKey, String expression) {

        ExpressionKey expressionKey = new ExpressionKey(elementKey, expression);
        Expression expr = keyCache.get(expressionKey);
        if (expr == null) {
            expr = this.parser.parseExpression(expression);
            keyCache.put(expressionKey, expr);
        }
        return expr;
    }


    protected static class ExpressionKey implements Comparable<ExpressionKey> {

        private final AnnotatedElementKey element;

        private final String expression;

        protected ExpressionKey(AnnotatedElementKey element, String expression) {

            this.element = element;
            this.expression = expression;
        }

        @Override
        public boolean equals(Object other) {
            if (this == other) {
                return true;
            }
            if (!(other instanceof ExpressionKey)) {
                return false;
            }
            ExpressionKey otherKey = (ExpressionKey) other;
            return (this.element.equals(otherKey.element) &&
                    ObjectUtils.nullSafeEquals(this.expression, otherKey.expression));
        }

        @Override
        public int hashCode() {
            return this.element.hashCode() * 29 + this.expression.hashCode();
        }

        @Override
        public String toString() {
            return this.element + " with expression \"" + this.expression + "\"";
        }

        @Override
        public int compareTo(ExpressionKey other) {
            int result = this.element.toString().compareTo(other.element.toString());
            if (result == 0) {
                result = this.expression.compareTo(other.expression);
            }
            return result;
        }
    }


}