package org.apache.seatunnel.transform.script.java.support;

import com.google.common.io.Files;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.Charsets;
import org.apache.commons.codec.digest.Md5Crypt;
import org.apache.commons.lang3.StringUtils;
import org.apache.seatunnel.common.utils.JsonUtils;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


@Slf4j
public class JavaEvalUtil {

	private static void dump(String javaSource) throws IOException {
        String className = getClassName(javaSource);

        String basePath = new File("").getAbsolutePath();
        String finalPath = String.join(File.separator, new String[]{basePath, "eval-output", className + ".java"});

        // 创建上级目录
        File file = new File(finalPath);
        Files.createParentDirs(file);

        log.info("java eval output:{}", file.getAbsolutePath());
        log.info("\n{}", javaSource);
        Files.write(javaSource.getBytes(Charsets.UTF_8), file);
    }

    /**
     * 编译并生成class
     *
     * @param sourceCode 源码内容
     * @return
     */
    private static Class<?> compile(String sourceCode) {
        String className = getClassName(sourceCode);

        DynamicCompiler dynamicCompiler = new DynamicCompiler(JavaEvalUtil.class.getClassLoader());
        dynamicCompiler.addSource(className, sourceCode);

        Map<String, Class<?>> build = dynamicCompiler.build();
        if (build.isEmpty()) {
            throw new RuntimeException("java eval compile error");
        }
        return build.values().iterator().next();
    }

    private static String getClassName(String sourceCode) {
        return StringUtils.trim(StringUtils.substringBetween(sourceCode, "public class ", "{"));
    }

    /**
     * 反射调用
     *
     * @param methtClass
     * @return
     * @throws Exception
     */
    private static Object call(Class<?> methtClass, Object... args) throws Exception {
        Method[] declaredMethods = methtClass.getDeclaredMethods();

        for (Method declaredMethod : declaredMethods) {
            if (Modifier.isPublic(declaredMethod.getModifiers()) && Modifier.isStatic(declaredMethod.getModifiers())) {
            	//是否数组参数类型
            	if(declaredMethod.getParameterTypes() != null && declaredMethod.getParameterTypes().length > 0 && declaredMethod.getParameterTypes()[0].isArray()) {
            		args = new Object[] {args};
            	}
            	Object result = declaredMethod.invoke(null, args);
                log.info("JavaEval return: {}", JsonUtils.toJsonString(result));
                return result;
            }
        }

        throw new RuntimeException("NO method is [public static], cannot eval");
    }

    private static Method getMethod(Class<?> methtClass) {
        Method[] declaredMethods = methtClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            if (Modifier.isPublic(declaredMethod.getModifiers()) && Modifier.isStatic(declaredMethod.getModifiers())) {
                return declaredMethod;
            }
        }
        throw new RuntimeException("NO method is [public static], cannot eval");
    }

    final static Map<String, Method> clzMap = new ConcurrentHashMap<>();
    
    /**
     * 对外API，执行文件得结果
     *
     * @param content
     * @return
     * @throws Exception
     */
    public static Object eval(String content, Object... args) throws Exception {
        // 类dump
//        dump(content);
        // 编译生成内存二进制，并加载为class
        String key = Md5Crypt.md5Crypt(content.getBytes(StandardCharsets.UTF_8));
        Method method = clzMap.get(key);
        if(method == null) {
            Class<?> clazz = compile(content);
            method = getMethod(clazz);
            clzMap.put(key, method);
        }
        //是否数组参数类型
        if(method.getParameterTypes() != null && method.getParameterTypes().length > 0 && method.getParameterTypes()[0].isArray()) {
            args = new Object[] {args};
        }
        // 反射调用class
        Object result = method.invoke(null, args);
        log.info("JavaEval return: {}", JsonUtils.toJsonString(result));
        return result;
    }
}
