/*
 * Copyright 2008 Ayman Al-Sairafi ayman.alsairafi@gmail.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License 
 *       at http://www.apache.org/licenses/LICENSE-2.0 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.  
 */
package jsyntaxpane.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author subwiz
 * @author Ayman Al-Sairafi
 */
public class JarServiceProvider {

    public static final String SERVICES_ROOT = "META-INF/services/";
    private static final Logger LOG = Logger.getLogger(JarServiceProvider.class.getName());

    /**
     * Prevent anyone from instantiating this class. Just use the static method
     */
    private JarServiceProvider() {
    }

    private static ClassLoader getClassLoader() {
        ClassLoader cl = JarServiceProvider.class.getClassLoader();
        return cl == null ? ClassLoader.getSystemClassLoader() : cl;
    }

    /**
     * Return an Object array from the file in META-INF/resources/{classname}
     *
     * @param cls
     * @return
     * @throws java.io.IOException
     */
    public static List<Object> getServiceProviders(Class cls) throws IOException {
        ArrayList<Object> l = new ArrayList<Object>();
        ClassLoader cl = getClassLoader();
        String serviceFile = SERVICES_ROOT + cls.getName();
        Enumeration<URL> e = cl.getResources(serviceFile);
        while (e.hasMoreElements()) {
            URL u = e.nextElement();
            InputStream is = u.openStream();
            BufferedReader br = null;
            try {
                br = new BufferedReader(
                        new InputStreamReader(is, Charset.forName("UTF-8")));
                String str = null;
                while ((str = br.readLine()) != null) {
                    int commentStartIdx = str.indexOf("#");
                    if (commentStartIdx != -1) {
                        str = str.substring(0, commentStartIdx);
                    }
                    str = str.trim();
                    if (str.length() == 0) {
                        continue;
                    }
                    try {
                        Object obj = cl.loadClass(str).newInstance();
                        l.add(obj);
                    } catch (Exception ex) {
                        LOG.warning("Could not load: " + str);
                        LOG.warning(ex.getMessage());
                    }
                }
            } finally {
                if (br != null) {
                    br.close();
                }
            }
        }
        return l;
    }

    /**
     * Read a file in the META-INF/services location. File name will be fully
     * qualified classname, in all lower-case, appended with ".properties" If no
     * file is found, then a an empty Property instance will be returned
     *
     * @param clazz
     * @return Property file read.
     */
    public static Properties readProperties(Class clazz) {
        return readProperties(clazz.getName());
    }

    /**
     * Read a file in the META-INF/services named name appended with
     * ".properties"
     *
     * If no file is found, then a an empty Property instance will be returned
     *
     * @param name name of file (use dots to separate subfolders).
     * @return Property file read.
     */
    public static Properties readProperties(String name) {
        Properties props = new Properties();       
        String serviceFile = name.toLowerCase();
        // JPEXS: Added locale support for menu actions
        String propSuffix = ".properties";
        if (serviceFile.endsWith(propSuffix)) {
            serviceFile = serviceFile.substring(0, serviceFile.length() - propSuffix.length());
        }
        Locale defaultLocale = Locale.getDefault();
        String langName = defaultLocale.getLanguage();
        InputStream is = null;
        is = findResource(serviceFile + "_" + langName + propSuffix);
        if (is == null) {
            is = findResource(serviceFile + propSuffix);
        }
        if (is != null) {
            try {
                props.load(is);
            } catch (IOException ex) {
                Logger.getLogger(JarServiceProvider.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return props;
    }

    /**
     * Read a file in the META-INF/services named name appended with
     * ".properties", and returns it as a
     * <code>Map<String, String></code> If no file is found, then a an empty
     * Property instance will be returned
     *
     * @param name name of file (use dots to separate subfolders).
     * @return Map of keys and values
     */
    public static Map<String, String> readStringsMap(String name) {
        Properties props = readProperties(name);
        HashMap<String, String> map = new HashMap<String, String>();
        if (props != null) {
            for (Map.Entry e : props.entrySet()) {
                map.put(e.getKey().toString(), e.getValue().toString());
            }
        }
        return map;
    }

    /**
     * Read the given URL and returns a List of Strings for each input line Each
     * line will not have the line terminator.
     *
     * The resource is searched in /META-INF/services/url, then in url, then the
     * url is treated as a location in the current classpath and an attempt to
     * read it from that location is done.
     *
     * @param url location of file to read
     * @return List of Strings for each line read. or EMPTY_LIST if URL is not
     * found
     */
    @SuppressWarnings("unchecked")
    public static List<String> readLines(String url) {
        InputStream is = findResource(url);
        if (is == null) {
            return Collections.EMPTY_LIST;
        }
        List<String> lines = new ArrayList<String>();
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            for (String line = br.readLine(); line != null; line = br.readLine()) {
                // Trim and unescape some control chars
                line = line.trim().replace("\\n", "\n").replace("\\t", "\t");
                lines.add(line);
            }
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, null, ex);
        } finally {
            try {
                is.close();
            } catch (IOException ex) {
                LOG.log(Level.SEVERE, null, ex);
            }
            return lines;
        }

    }

    /**
     * Attempt to find a location url. The following locations are searched in
     * sequence: url, SERVICES_ROOT/url all classpath/url
     *
     * @param url
     * @param cl classloader
     * @return InputSTream at that location, or null if not found
     * @see JarServiceProvider#findResource(java.lang.String)
     */
    public static InputStream findResource(String url, ClassLoader cl) {
        InputStream is = null;

        URL loc = cl.getResource(url);
        if (loc == null) {
            loc = cl.getResource(url);
        }
        if (loc == null) {
            loc = cl.getResource(SERVICES_ROOT + url);
        }
        if (loc == null) {
            is = ClassLoader.getSystemResourceAsStream(url);
        } else {
            try {
                is = loc.openStream();
            } catch (IOException ex) {
                Logger.getLogger(JarServiceProvider.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return is;
    }

    /**
     * Attempt to find a location url. The following locations are searched in
     * sequence: url, SERVICES_ROOT/url all classpath/url The System ClassLoader
     * is used.
     *
     * @param url
     * @return InputSTream at that location, or null if not found
     * @see JarServiceProvider#findResource(java.lang.String,
     * java.lang.ClassLoader)
     */
    public static InputStream findResource(String url) {
        return findResource(url, getClassLoader());
    }
}
