package cn.estsh.i3plus.pojo.aps.common;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class BeanRelationUtil {

    static class XMLReader extends DefaultHandler {
        private BeanInfo firstInfo = null;
        private Enum<?> firstHolder = null;
        private BeanInfo secondInfo = null;
        private Enum<?> secondHolder = null;
        private RELATION_TYPE type = RELATION_TYPE.INVALID;
        private boolean owner = false;
        public void startElement(String uri, String localName, String nodeName, Attributes attributes) throws SAXException {
            if (nodeName.equalsIgnoreCase("Class")) {
                final String name = attributes.getValue("name");
                if (name == null) {
                    throw new SAXException("Class节点的属性name未配置");
                }

                this.firstInfo = BeanInfo.getBeanInfo(name);
                if (this.firstInfo == null) {
                    throw new SAXException("未找到" + name + "的类定义");
                }
            } else if (nodeName.equalsIgnoreCase("Relation")) {
                if (this.firstInfo == null) {
                    throw new SAXException("未配置Class节点");
                }

                String firstSignName = attributes.getValue("field");
                if (firstSignName == null) {
                    throw new SAXException("Relation节点缺少field属性");
                }
                this.firstHolder = this.firstInfo.getHolder(firstSignName);
                if (this.firstHolder == null) {
                    throw new SAXException("未定义枚举标识" + firstSignName);
                }

                String secondFactoryName = attributes.getValue("name");
                if (secondFactoryName == null) {
                    throw new SAXException("Relation节点缺少name属性");
                }
                this.secondInfo = BeanInfo.getBeanInfo(secondFactoryName);
                if (this.secondInfo == null) {
                    throw new SAXException("未找到" + secondFactoryName + "的类定义");
                }

                String secondSignName = attributes.getValue("reverse");
                if (secondSignName != null) {
                    this.secondHolder = this.secondInfo.getHolder(secondSignName);
                    if (this.secondHolder == null) {
                        throw new SAXException(secondFactoryName + "类未定义枚举标识" + secondSignName);
                    }
                } else {
                    this.secondHolder = null;
                }

                String typeName = attributes.getValue("type");
                if (typeName == null) {
                    throw new SAXException("Relation节点缺少type属性");
                }
                this.type = RELATION_TYPE.valueOf(typeName);

                String ownerName = attributes.getValue("owner");
                if (ownerName == null) {
                    this.owner = false;
                } else {
                    this.owner = ownerName.equalsIgnoreCase("true") ? true : false;
                }

                RelationInfo firstData = this.getSignData(firstInfo, this.firstHolder);
                firstData.setBeanInfo(this.secondInfo);
                firstData.setType(this.type);
                firstData.setOwner(this.owner);
                firstData.setReverseHolder(this.secondHolder);

                if (this.secondHolder != null) {
                    RelationInfo secondData = this.getSignData(secondInfo, this.secondHolder);
                    secondData.setBeanInfo(this.firstInfo);
                    secondData.setOwner(false);
                    secondData.setReverseHolder(this.firstHolder);
                    switch (this.type) {
                        case MULTI_TO_MULTI:
                            secondData.setType(RELATION_TYPE.MULTI_TO_MULTI);
                            break;
                        case MULTI_TO_ONE:
                            secondData.setType(RELATION_TYPE.ONE_TO_MULTI);
                            break;
                        case INVALID:
                            secondData.setType(RELATION_TYPE.INVALID);
                            break;
                        case ONE_TO_MULTI:
                            secondData.setType(RELATION_TYPE.MULTI_TO_ONE);
                            break;
                        case ONE_TO_ONE:
                            secondData.setType(RELATION_TYPE.ONE_TO_ONE);
                            break;
                        default:
                            secondData.setType(RELATION_TYPE.INVALID);
                            break;
                    }
                }
            }
        }

        RelationInfo getSignData(BeanInfo beanInfo, Enum<?> holder) {
            RelationInfo data = beanInfo.getRelationInfo(holder);
            if (data == null) {
                data = new RelationInfo();
                data.setHolder(holder);
                beanInfo.addRelationInfo(holder, data);
            }

            return data;
        }
    }

    public static void initData(String holderPackage) {
        List<Class<?>> holderClses = loadClass(holderPackage);
        for (Class<?> enumCls : holderClses) {
            if (!Enum.class.isAssignableFrom(enumCls)) {
                continue;
            }
            String name = enumCls.getSimpleName();
            BeanInfo beanInfo = BeanInfo.getBeanInfo(name.substring(1));
            if (beanInfo == null) {
                continue;
            }
            beanInfo.initHolder((Class<Enum<? extends Enum<?>>>) enumCls);
        }
    }

    public static void loadConfig(String packName) {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        final String strFile = packName.replaceAll("\\.", "/");
        try {
            Enumeration<URL> urls = loader.getResources(strFile);
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if (url != null) {
                    String protocol = url.getProtocol();
                    String filePath = url.getPath();
                    if (protocol.equals("file")) {
                        loadFileImpl(filePath);
                    } else if (protocol.equals("jar")) {
                        loadJarImpl(packName, url);
                    }
                }
            }
        } catch (IOException e) {
            return;
        }
    }

    private static void loadFileImpl(String dirPath) {
        File dir = new File(dirPath);
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }

        File[] dirFiles = dir.listFiles(new FileFilter() {

            @Override
            public boolean accept(File file) {
                return file.isDirectory() || file.getName().endsWith(".xml");
            }
        });

        for (File file : dirFiles) {
            if (file.isDirectory()) {
                loadFileImpl(dirPath + "/" + file.getName());
            } else {
                try {
                    loadXMLConfigure(file.getCanonicalPath());
                } catch (IOException e) {
                }
            }
        }
    }

    private static void loadJarImpl(String packName, URL url) throws IOException {
        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
        JarFile jarFile = jarURLConnection.getJarFile();
        Enumeration<JarEntry> jarEntries = jarFile.entries();
        while (jarEntries.hasMoreElements()) {
            JarEntry jarEntry = jarEntries.nextElement();
            String jarEntryName = jarEntry.getName();
            if (jarEntryName.endsWith(".xml")) {
                String packNameNew =  packName.replace(".", "/");
                if (jarEntryName.contains(packNameNew)) {
                    InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(jarEntryName);
                    loadXMLConfigure(is);
                }
            }
        }
    }

    /**
     * 加载本地开发环境中的xml配置文件。
     * @param xmlPath
     */
    private static void loadXMLConfigure(String xmlPath) {
        SAXParserFactory sf = SAXParserFactory.newInstance();
        try {
            SAXParser sp = sf.newSAXParser();
            sp.parse(new InputSource(xmlPath), new XMLReader());
        } catch (ParserConfigurationException | SAXException | IOException e) {
        }
    }

    /**
     * 加载jar中的xml配置文件。
     * @param is
     */
    private static void loadXMLConfigure(InputStream is) {
        SAXParserFactory sf = SAXParserFactory.newInstance();
        try {
            SAXParser sp = sf.newSAXParser();
            sp.parse(new InputSource(is), new XMLReader());
        } catch (ParserConfigurationException | SAXException | IOException e) {
        }
    }

    /**
     * 将指定包下的类加载到内存中
     * @param packName
     */
    public static List<Class<?>> loadClass(String packName) {
        List<Class<?>> clses = new ArrayList<>();
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        String strFile = packName.replaceAll("\\.", "/");
        try {
            Enumeration<URL> urls = loader.getResources(strFile);
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if (url != null) {
                    String protocol = url.getProtocol();
                    String filePath = url.getPath();
                    if (protocol.equals("file")) {
                        loadClassImpl(packName, filePath, clses);
                    } else if (protocol.equals("jar")) {
                        loadJarImpl(packName, url, clses);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return clses;
    }

    private static void loadClassImpl(String packName, String dirPath, List<Class<?>> clses) {
        File dir = new File(dirPath);
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }

        File[] dirFiles = dir.listFiles(new FileFilter() {

            @Override
            public boolean accept(File file) {
                return file.isDirectory() || file.getName().endsWith(".class");
            }
        });

        for (File file : dirFiles) {
            if (file.isDirectory()) {
                loadClassImpl(packName + "." + file.getName(), dirPath + "/" + file.getName(), clses);
            } else {
                String clsName = file.getName();
                clsName = clsName.substring(0, clsName.length() - 6);
                try {
                    clses.add(Class.forName(packName + "." + clsName));
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static void loadJarImpl(String packName, URL url, List<Class<?>> clses) throws IOException {
        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
        JarFile jarFile = jarURLConnection.getJarFile();
        Enumeration<JarEntry> jarEntries = jarFile.entries();
        while (jarEntries.hasMoreElements()) {
            JarEntry jarEntry = jarEntries.nextElement();
            final String jarEntryName = jarEntry.getName();
            if (jarEntryName.endsWith(".class")) {
                String clsName = jarEntryName.replace("/", ".");
                if (clsName.startsWith(packName)) {
                    clsName = clsName.substring(0, clsName.length() - 6);
                    try {
                        clses.add(Class.forName(clsName));
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
