package net.northking.cctp.element.core.scanner;

import net.northking.cctp.element.core.Library;
import net.northking.cctp.element.core.exception.FactoriesConfigException;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.*;

/**
 * 加载库服务类
 *
 * @author fengxiang.zhang
 * @since 1.0
 */
public class FactoriesLoader {

    private static org.slf4j.Logger logger = LoggerFactory.getLogger(FactoriesLoader.class);

    /**
     * factories文件位置
     */
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/cctp-class.factories";

    public static Set<Class<?>> loadFactories(ClassLoader classLoader) throws Exception {
        Set<Class<?>> result = new HashSet<>();
        logger.debug("查找读取{}资源文件.", FACTORIES_RESOURCE_LOCATION);
        InputStream in = classLoader.getResourceAsStream(FACTORIES_RESOURCE_LOCATION);
        if (in == null) {
            throw new FactoriesConfigException(String.format("未找到 %s 配置文件", FACTORIES_RESOURCE_LOCATION));
        }
        Iterator<String> providers = parse(in);
        Class<?> cls;
        while (providers.hasNext()) {
            String provider = providers.next();
            logger.debug("找到库服务类:{}", provider);
            cls = classLoader.loadClass(provider);
            if (Library.class.isAssignableFrom(cls)) {
                result.add(cls);
            }
        }
        if (result.size() == 0) {
            throw new FactoriesConfigException("非标准的nk-atp组件资源库文件");
        }
        return result;
    }

    public static Set<Class<?>> loadMultiFactories(ClassLoader classLoader) throws Exception {
        Set<Class<?>> result = new HashSet<>();
        logger.debug("查找读取{}资源文件.", FACTORIES_RESOURCE_LOCATION);
        for (Enumeration<URL> enumeration = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); enumeration.hasMoreElements(); ) {
            URL url = enumeration.nextElement();
            logger.debug("读取资源文件:{}", url);
            InputStream in = url.openStream();
            Iterator<String> providers = parse(in);
            Class<?> cls;
            while (providers.hasNext()) {
                String provider = providers.next();
                logger.debug("找到库服务类：{}", provider);
                cls = classLoader.loadClass(provider);
                if (Library.class.isAssignableFrom(cls)) {
                    result.add(cls);
                }
            }
        }
        return result;
    }

    private static Iterator<String> parse(InputStream in) throws Exception {
        BufferedReader r = null;
        ArrayList<String> names = new ArrayList<>();
        try {
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            while ((lc = parseLine(r, lc, names)) >= 0)
                ;
        } catch (IOException x) {
            fail("读取配置文件时出错", x);
        } finally {
            try {
                if (r != null) {
                    r.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException y) {
                fail("关闭配置文件时出错", y);
            }
        }
        return names.iterator();
    }

    private static int parseLine(BufferedReader r, int lc, List<String> names) throws Exception {
        String ln = r.readLine();
        if (ln == null) {
            return -1;
        }
        int ci = ln.indexOf('#');
        if (ci >= 0) {
            ln = ln.substring(0, ci);
        }
        ln = ln.trim();
        int n = ln.length();
        if (n != 0) {
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) {
                fail(lc, "非法配置文件语法");
            }
            int cp = ln.codePointAt(0);
            if (!Character.isJavaIdentifierStart(cp)) {
                fail(lc, "非法的java程序类名: " + ln);
            }
            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) {
                    fail(lc, "非法的java程序类名: " + ln);
                }
            }
            if (!names.contains(ln)) {
                names.add(ln);
            }
        }
        return lc + 1;
    }

    private static void fail(int line, String msg) throws Exception {
        throw new FactoriesConfigException(FACTORIES_RESOURCE_LOCATION + ":" + line + ": " + msg);
    }

    private static void fail(String msg, Throwable cause) throws Exception {
        throw new FactoriesConfigException(msg, cause);
    }
}