package com.taobao.arthas.grpcweb.grpc.service.advisor;

import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.taobao.arthas.common.concurrent.ConcurrentWeakKeyHashMap;
import com.taobao.arthas.core.advisor.AdviceListener;
import com.taobao.arthas.core.shell.system.ExecStatus;
import com.taobao.arthas.core.shell.system.Process;
import com.taobao.arthas.core.shell.system.ProcessAware;
import com.taobao.arthas.grpcweb.grpc.DemoBootstrap;

import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * 
 * TODO line 的记录 listener方式？ 还是有string为key，不过 classname|method|desc|num 这样子？
 * 判断是否已插入了，可以在两行中间查询，有没有 SpyAPI 的invoke?
 * 
 * TODO trace的怎么搞？ trace 只记录一次就可以了 classname|method|desc|trace ? 怎么避免 trace 到
 * SPY的invoke ？直接忽略？
 * 
 * TODO trace命令可以动态的增加 新的函数进去不？只要关联上同一个 Listener应该是可以的。
 * 
 * TODO 在SPY里放很多的 Object数组，然后动态的设置进去？ 比如有新的 Listener来的时候。 这样子连查表都不用了。 甚至可以动态生成
 * 存放这些 Listener数组的类？ 这样子的话，只要有 Binding那里，查询到一个具体分配好的类， 这样子就可以了？
 * 甚至每个ClassLoader里都动态生成这样子的 存放类，那么这样子不可以避免查 ClassLoader了么？
 * 
 * 动态为每一个增强类，生成一个新的类，新的类里，有各种的 ID 数组，保存每一个类的每一种 trace 点的信息？？
 * 
 * 多个 watch命令 对同一个类，现在的逻辑是，每个watch都有一个自己的 TransForm，但不会重复增强，因为做了判断。
 * watch命令停止时，也没有去掉增强的代码。 只有reset时 才会去掉。
 * 
 * 其实用户想查看局部变量，并不是想查看哪一行！ 而是想看某个函数里子调用时的 局部变量的值！ 所以实际上是想要一个新的命令，比如 watchinmethod
 * ， 可以 在某个子调用里，
 * 
 * TODO 现在的trace 可以输出行号，可能不是很精确，但是可以对应上的。 这个在新的方式里怎么支持？ 增加一个 linenumber binding？
 * 从mehtodNode，向上查找到最近的行号？
 * 
 * TODO 防止重复增强，最重要的应该还是动态增加 annotation，这个才是真正可以做到某一行，某一个子 invoke 都能识别出来的！ 无论是
 * transform多少次！ 字节码怎么动态加 annotation ？ annotation里签名用 url ?的key/value方式表达！
 * 这样子可以有效还原信息
 * 
 * TODO 是否考虑一个 trace /watch命令之后，得到一个具体的 Listener ID， 允许在另外的窗口里，再次
 * trace/watch时指定这个ID，就会查找到，并处理。 这样子的话，真正达到了动态灵活的，一层一层增加的trace ！
 * 
 * 
 * @author hengyunabc 2020-04-24
 *
 */
public class AdviceListenerManager {
    private static final Logger logger = LoggerFactory.getLogger(AdviceListenerManager.class);
    private static final FakeBootstrapClassLoader FAKEBOOTSTRAPCLASSLOADER = new FakeBootstrapClassLoader();

    static {
        // 清理失效的 AdviceListener
        DemoBootstrap.getRunningInstance().getScheduledExecutorService().scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                try {
                    for (Entry<ClassLoader, ClassLoaderAdviceListenerManager> entry : adviceListenerMap.entrySet()) {
                        ClassLoaderAdviceListenerManager adviceListenerManager = entry.getValue();
                        synchronized (adviceListenerManager) {
                            for (Entry<String, List<AdviceListener>> eee : adviceListenerManager.map.entrySet()) {
                                List<AdviceListener> listeners = eee.getValue();
                                List<AdviceListener> newResult = new ArrayList<AdviceListener>();
                                for (AdviceListener listener : listeners) {
                                    if (listener instanceof ProcessAware) {
                                        ProcessAware processAware = (ProcessAware) listener;
                                        Process process = processAware.getProcess();
                                        if (process == null) {
                                            continue;
                                        }
                                        ExecStatus status = process.status();
                                        if (!status.equals(ExecStatus.TERMINATED)) {
                                            newResult.add(listener);
                                        }
                                    }
                                }

                                if (newResult.size() != listeners.size()) {
                                    adviceListenerManager.map.put(eee.getKey(), newResult);
                                }

                            }
                        }
                    }
                } catch (Throwable e) {
                    try {
                        logger.error("clean AdviceListener error", e);
                    } catch (Throwable t) {
                        // ignore
                    }
                }
            }
        }, 3, 3, TimeUnit.SECONDS);
    }

    private static final ConcurrentWeakKeyHashMap<ClassLoader, ClassLoaderAdviceListenerManager> adviceListenerMap = new ConcurrentWeakKeyHashMap<ClassLoader, ClassLoaderAdviceListenerManager>();

    static class ClassLoaderAdviceListenerManager {
        private ConcurrentHashMap<String, List<AdviceListener>> map = new ConcurrentHashMap<String, List<AdviceListener>>();

        private String key(String className, String methodName, String methodDesc) {
            return className + methodName + methodDesc;
        }

        private String keyForTrace(String className, String owner, String methodName, String methodDesc) {
            return className + owner + methodName + methodDesc;
        }

        public void registerAdviceListener(String className, String methodName, String methodDesc,
                AdviceListener listener) {
            synchronized (this) {
                className = className.replace('/', '.');
                String key = key(className, methodName, methodDesc);

                List<AdviceListener> listeners = map.get(key);
                if (listeners == null) {
                    listeners = new ArrayList<AdviceListener>();
                    map.put(key, listeners);
                }
                if (!listeners.contains(listener)) {
                    listeners.add(listener);
                }
            }
        }

        public List<AdviceListener> queryAdviceListeners(String className, String methodName, String methodDesc) {
            className = className.replace('/', '.');
            String key = key(className, methodName, methodDesc);

            List<AdviceListener> listeners = map.get(key);

            return listeners;
        }

        public void registerTraceAdviceListener(String className, String owner, String methodName, String methodDesc,
                AdviceListener listener) {

            className = className.replace('/', '.');
            String key = keyForTrace(className, owner, methodName, methodDesc);

            List<AdviceListener> listeners = map.get(key);
            if (listeners == null) {
                listeners = new ArrayList<AdviceListener>();
                map.put(key, listeners);
            }
            if (!listeners.contains(listener)) {
                listeners.add(listener);
            }
        }

        public List<AdviceListener> queryTraceAdviceListeners(String className, String owner, String methodName,
                String methodDesc) {
            className = className.replace('/', '.');
            String key = keyForTrace(className, owner, methodName, methodDesc);

            List<AdviceListener> listeners = map.get(key);

            return listeners;
        }
    }

    public static void registerAdviceListener(ClassLoader classLoader, String className, String methodName,
            String methodDesc, AdviceListener listener) {
        classLoader = wrap(classLoader);
        className = className.replace('/', '.');

        ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);

        if (manager == null) {
            manager = new ClassLoaderAdviceListenerManager();
            adviceListenerMap.put(classLoader, manager);
        }
        manager.registerAdviceListener(className, methodName, methodDesc, listener);
    }

    public static void updateAdviceListeners() {

    }

    public static List<AdviceListener> queryAdviceListeners(ClassLoader classLoader, String className,
            String methodName, String methodDesc) {
        classLoader = wrap(classLoader);
        className = className.replace('/', '.');
        ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);

        if (manager != null) {
            return manager.queryAdviceListeners(className, methodName, methodDesc);
        }

        return null;
    }

    public static void registerTraceAdviceListener(ClassLoader classLoader, String className, String owner,
            String methodName, String methodDesc, AdviceListener listener) {
        classLoader = wrap(classLoader);
        className = className.replace('/', '.');

        ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);

        if (manager == null) {
            manager = new ClassLoaderAdviceListenerManager();
            adviceListenerMap.put(classLoader, manager);
        }
        manager.registerTraceAdviceListener(className, owner, methodName, methodDesc, listener);
    }

    public static List<AdviceListener> queryTraceAdviceListeners(ClassLoader classLoader, String className,
            String owner, String methodName, String methodDesc) {
        classLoader = wrap(classLoader);
        className = className.replace('/', '.');
        ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);

        if (manager != null) {
            return manager.queryTraceAdviceListeners(className, owner, methodName, methodDesc);
        }

        return null;
    }

    private static ClassLoader wrap(ClassLoader classLoader) {
        if (classLoader != null) {
            return classLoader;
        }
        return FAKEBOOTSTRAPCLASSLOADER;
    }

    private static class FakeBootstrapClassLoader extends ClassLoader {

    }
}
