package ameba.lib;

import ameba.lib.el.MapBasedFunctionMapper;
import ameba.lib.el.SimpleContext;
import ameba.util.bean.BeanInvoker;
import ameba.util.bean.BeanMap;

import javax.el.*;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author icode
 */
public class El {
    private static final ExpressionFactory expressionFactory = ExpressionFactory.newInstance();

    private El() {
    }

    public static String parse(String text, ELContext context, Object root, String prefix, Class... funcClasses) {
        addVariables(context, new PropBeanMap<>(root));
        Stream.of(funcClasses).forEach(fn -> addFunctions(context, prefix, fn));
        return parse(text, context);
    }

    public static void addVariables(ELContext elContext, Map<String, Object> variables) {
        VariableMapper mapper = elContext.getVariableMapper();
        for (Map.Entry<String, Object> entry : variables.entrySet()) {
            Object value = entry.getValue();
            ValueExpression valueExpression = expressionFactory
                    .createValueExpression(value, value != null ? value.getClass() : Object.class);
            mapper.setVariable(entry.getKey(), valueExpression);
        }
    }

    public static void addFunctions(ELContext elContext, String prefix, Map<String, Method> functions) {
        FunctionMapper mapper = elContext.getFunctionMapper();
        if (mapper instanceof MapBasedFunctionMapper && functions != null && !functions.isEmpty()) {
            MapBasedFunctionMapper functionMapper = (MapBasedFunctionMapper) mapper;
            functions.forEach((s, method) -> functionMapper.setFunction(prefix, s, method));
        }
    }

    public static void addFunctions(ELContext elContext, String prefix, Class funcClass) {
        if (funcClass != null) {
            addFunctions(
                    elContext,
                    prefix,
                    getFunctions(funcClass)
            );
        }
    }

    public static Map<String, Method> getFunctions(Class funcClass) {
        return Stream.of(funcClass.getMethods())
                .filter(method -> Modifier.isStatic(method.getModifiers()))
                .collect(Collectors.toMap(Method::getName, m -> m));
    }

    public static String parse(String text, Object root, String prefix, Class... funcClasses) {
        return parse(text, createContext(), root, prefix, funcClasses);
    }

    public static String parse(String text, Object root) {
        return parse(text, createContext(), root, null);
    }

    public static String parse(String text, ELContext context, Object root,
                               String prefix, Map<String, Method> functions) {
        addVariables(context, new PropBeanMap<>(root));
        addFunctions(context, prefix, functions);
        return parse(text, context);
    }

    public static String parse(String text, ELContext context, Object root, Map<String, Method> functions) {
        return parse(text, context, root, "", functions);
    }

    public static String parse(String text, Object root, String prefix, Map<String, Method> functions) {
        ELContext context = createContext();
        addVariables(context, new PropBeanMap<>(root));
        addFunctions(context, prefix, functions);
        return parse(text, context);
    }

    public static String parse(String text, Object root, Map<String, Method> functions) {
        return parse(text, createContext(), root, "", functions);
    }

    public static String parse(String text, ELContext context) {
        return (String) expressionFactory
                .createValueExpression(context, text, String.class)
                .getValue(context);
    }

    public static ELContext createContext() {
        return new SimpleContext(expressionFactory);
    }

    private static class PropBeanMap<T> extends BeanMap<T> {
        public PropBeanMap(T bean) {
            super(bean);
        }

        @Override
        protected Object transform(BeanInvoker invoker) throws Throwable {
            return invoker.invoke();
        }
    }
}

@@ -650,33 +650,30 @@
 ontext, 
-new PropBean
+toVariables
 Map
-%3C%3E
 (root));
@@ -2816,33 +2816,30 @@
 ontext, 
-new PropBean
+toVariables
 Map
-%3C%3E
 (root));
@@ -3291,25 +3291,22 @@
 xt, 
-new PropBean
+toVariables
 Map
-%3C%3E
 (roo
@@ -3827,24 +3827,24 @@
 Context() %7B%0A
-
         retu
@@ -3887,24 +3887,133 @@
 ry);%0A    %7D%0A%0A
+    public static Map%3CString, Object%3E toVariablesMap(Object o) %7B%0A        return new PropBeanMap%3C%3E(o);%0A    %7D%0A%0A
     private 

update `El` api