package com.wisedu.coeus.web;

import com.wisedu.coeus.debug.Logger;
import com.wisedu.coeus.debug.LoggerFactory;
import com.wisedu.coeus.helper.ApplicationHelper;
import com.wisedu.coeus.util.Strings;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DefaultPropertiesPersister;
import org.springframework.util.PropertiesPersister;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;

/**
 * 读取系统配置文件
 * 默认从/WEB-INF/conf/下加载
 */

@SuppressWarnings("unchecked")
public class PropertiesFactoryBean implements FactoryBean, InitializingBean {
    private static Logger LOGGER = LoggerFactory.getLogger(PropertiesFactoryBean.class);

    private Object configProperties;

    private String defaultPropertiesLocation = "/WEB-INF/conf/";
    private String customPropertiesLocation = "/WEB-INF/custom/conf/";

    private boolean localOverride = false;

    private Properties[] localProperties;
    private Resource[] locations;

    private boolean isSupportXmlFile = false;

    private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();

    public static final String XML_FILE_EXTENSION = ".xml";
    public static final String PROPERTIES_FILE_EXTENSION = ".properties";
    public static final String HTML_FILE_EXTENSION = ".html";
    public static final String HTML_FILE_EXTENSION_SHORT = ".htm";
    public static final String TXT_FILE_EXTENSION = ".txt";

    public static final String FILE_LAST_LOAD_PREFIX = "environment.";

    private String templateEncoding = "UTF-8";

    private String fileEncoding = null;

    private boolean ignoreResourceNotFound = false;

    private String ccsPropertiesLocation;

    @Override
    public void afterPropertiesSet() throws Exception {
        this.configProperties = createInstance();
    }

    @Override
    public Object getObject() throws Exception {
        return this.configProperties;
    }

    @Override
    public Class<?> getObjectType() {
        return Properties.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    private Object createInstance() throws IOException {
        Properties result = this.mergeProperties();
        Properties fileProp = new Properties();
        Properties rootProp = new Properties();
        initConfigLocations(this.defaultPropertiesLocation,result, fileProp, rootProp);
        initConfigLocations(this.customPropertiesLocation,result, fileProp, rootProp);


        fileProp.putAll(result);
        return fileProp;
    }

    /**
     * 合并属性
     * @return
     * @throws IOException
     */
    private Properties mergeProperties() throws IOException {
        Properties result = new Properties();

        if (this.localOverride) {
            // Load properties from file upfront, to let local properties override.
            loadProperties(result);
        }

        if (this.localProperties != null) {
            for (int i = 0; i < this.localProperties.length; i++) {
                CollectionUtils.mergePropertiesIntoMap(this.localProperties[i], result);
            }
        }

        if (! this.localOverride) {
            // Load properties from file afterwards, to let those properties override.
            loadProperties(result);
        }

        return result;
    }

    private void initConfigLocations(String dir,Properties result, Properties fileProps, Properties rootProps) throws IOException {

        List<Resource> locs = new ArrayList<Resource>(32);
        String rootPath = ApplicationHelper.getWebRootPath();
        StringBuilder sb = new StringBuilder(128);
        sb.append("file:///").append(rootPath).append(dir);
        // 默认的配置文件路径
        String defaultLocation = sb.toString().replace('\\', '/');


        LOGGER.info("default config path is " + defaultLocation);

        try {

            this.loadPropByPath(defaultLocation, locs, fileProps, true);
            this.loadProperties(result, locs.toArray(new Resource[locs.size()]));

        }
        catch (IOException ex) {
            if (this.ignoreResourceNotFound) {
                LOGGER.warn("Could not load properties from " + ccsPropertiesLocation + ": " + ex.getMessage());

            }
            else {
                throw ex;
            }
        }
    }

    /**
     * 载入配置属性
     * @param props
     * @throws IOException
     */
    private void loadProperties(Properties props) throws IOException {
        loadProperties(props, null);
    }

    /**
     * 载入配置属性
     * @param props
     * @param locs
     * @throws IOException
     */
    private void loadProperties(Properties props, Resource[] locs) throws IOException {
        if (locs == null) {
            locs = this.locations;
        }
        if (locs != null) {
            Resource location;
            for (int i = 0, n = locs.length; i < n; i++) {
                location = locs[i];
                LOGGER.info("Loading config from " + location);
                InputStream is = null;
                try {
                    is = location.getInputStream();
                    if (this.isSupportXmlFile && location.getFilename().endsWith(XML_FILE_EXTENSION)) {
                        this.propertiesPersister.loadFromXml(props, is);
                    }
                    else {
                        if (this.fileEncoding != null) {
                            this.propertiesPersister.load(props, new InputStreamReader(is, this.fileEncoding));
                        }
                        else {
                            this.propertiesPersister.load(props, is);
                        }
                    }
                }
                catch (IOException ex) {
                    if (this.ignoreResourceNotFound) {
                        LOGGER.warn("Could not load properties from " + location + ": " + ex.getMessage());
                    }
                    else {
                        throw ex;
                    }
                }
                finally {
                    if (is != null) {
                        is.close();
                    }
                }
            }
        }
    }

    private void loadPropByPath(String location, List locations, Properties fileProps, boolean isSupportLastFile)
            throws IOException {
        UrlResource ur = new UrlResource(location);
        if (! ur.getFile().exists()) {
            return;
        }
        Collection fs = FileUtils.listFiles(ur.getFile(), null, false);
        if (!fs.isEmpty()) {
            Iterator fi = fs.iterator();
            File f;
            String fileName;
            // 最后加载的文件
            File lastFile = null;
            while (fi.hasNext()) {
                f = (File) fi.next();
                fileName = Strings.trimToEmpty(f.getName());
                // 若以.xml或.properties文件结尾的，按照Spring读取配置文件方式读取
                if (fileName.endsWith(PROPERTIES_FILE_EXTENSION) || fileName.endsWith(XML_FILE_EXTENSION)) {
                    if (isSupportLastFile && fileName.startsWith(FILE_LAST_LOAD_PREFIX)) {
                        lastFile = f;
                    }
                    else {
                        locations.add(new FileSystemResource(f));
                    }
                }
                else if (fileName.endsWith(HTML_FILE_EXTENSION) || fileName.endsWith(HTML_FILE_EXTENSION_SHORT)
                        || fileName.endsWith(TXT_FILE_EXTENSION)) {
                    // 否则，则将其文件名作key，文件的内容作value读取
                    fileProps.setProperty(Strings.getFileBaseName(fileName), getFileContent(f));
                    LOGGER.info("file '{}' content has been loaded", f.toString());
                }
                else {
                    LOGGER.info("file '{}' is ignore", f.toString());
                }
            }
            if (lastFile != null) {
                locations.add(new FileSystemResource(lastFile));
            }
        }
    }

    private String getFileContent(final File file) throws IOException {
        return FileUtils.readFileToString(file, templateEncoding);
    }
}
