package cd.casic.plugin.register.group;

import cd.casic.plugin.BasePlugin;
import cd.casic.plugin.PluginClassLoaderCache;
import cd.casic.plugin.PluginInfo;
import cd.casic.plugin.register.BasePluginHandler;
import cd.casic.plugin.register.group.filter.PluginClassFilter;
import cd.casic.plugin.register.group.filter.impl.*;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginWrapper;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

/**
 * 加载插件中所有的类,并且分组存放到pluginInfo的map中,便于后面的handler进行处理
 */
@Slf4j
public class ClassGroupHandler implements BasePluginHandler {
    private final List<PluginClassFilter> classFilters = new ArrayList<>();

    @Override
    public void initialize() throws Exception {
        classFilters.add(new BasicBeanFilter());
        classFilters.add(new ControllerFilter());
        classFilters.add(new DataBaseEntityFilter());
        classFilters.add(new MapperFilter());
        classFilters.add(new PluginConfigurationFilter());
        classFilters.add(new WebSocketFilter());
    }

    /**
     * 将类分组存到PluginInfo的map中,方便后面其他Handler进行处理
     * @param plugin PluginInfo
     */
    @Override
    public void registry(PluginInfo plugin) throws Exception {
        List<Class<?>> classList = new ArrayList<>();
        Set<String> classPackageName = scanClassPackageName(plugin.getBasePlugin().scanPackage(), plugin.getBasePlugin().getWrapper());
        for (String packageName : classPackageName) {
            ClassLoader loader = PluginClassLoaderCache.getPlugin(plugin.getPluginId());
            log.warn("Load class {} using classloader {} for plugin {}", packageName, PluginClassLoaderCache.getPlugin(plugin.getPluginId()), plugin.getPluginId());
            Class<?> clazz = loader.loadClass(packageName);
            if (!BasePlugin.class.isAssignableFrom(clazz)) {
                classList.add(clazz);
            }
        }
        plugin.setClassList(classList);
        /*这里分组其实只是为了分组而分组，目前没有特别明确的用途*/
        List<Class<?>> pluginClassList = plugin.getClassList().stream().filter(item -> !item.isInterface()).collect(Collectors.toList());
        if (!pluginClassList.isEmpty()) {
            for (Class<?> clazz : pluginClassList) {
                boolean grouped = false;
                for(PluginClassFilter filter : classFilters){
                    if(filter.filter(clazz)){
                        log.info("将类 {} 添加到 {} 分组", clazz, filter.groupName());
                        plugin.addGroupClass(filter.groupName(), clazz);
                        grouped = true;
                    }
                }
                if(!grouped){
                    log.info("类 {} 不属于任何已知分组", clazz);
                    plugin.addGroupClass("unknown", clazz);
                }
            }
        }
    }

    /**
     * 卸载插件时,将插件的类列表清空
     * @param plugin PluginInfo
     */
    @Override
    public void unRegistry(PluginInfo plugin) throws Exception {
        plugin.getClassList().clear();
    }

    /**
     * 扫描插件所在包,获取插件中所有的类名
     * @param basePackage 插件所在包名
     * @param pluginWrapper 插件对象包装类
     * @return 所有的类名集合
     */
    private Set<String> scanClassPackageName(String basePackage, PluginWrapper pluginWrapper) throws IOException {
        Path pluginPath = pluginWrapper.getPluginPath();
        if(pluginPath == null || !Files.exists(pluginPath)){
            throw new RuntimeException("错误的插件路径");
        }
        File pluginFile = pluginPath.toFile();
        Set<String> classPackageNames = new HashSet<>();

        try (JarFile jar = new JarFile(pluginFile)) {
            Enumeration<JarEntry> jarEntries = jar.entries();
            while (jarEntries.hasMoreElements()) {
                JarEntry entry = jarEntries.nextElement();
                String jarEntryName = entry.getName();
                if (jarEntryName.contains(".class") && jarEntryName.replaceAll("/", ".").startsWith(basePackage)) {
                    String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replace("/", ".");
                    classPackageNames.add(className);
                }
            }
        }
        return classPackageNames;
    }
}
