/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.myfaces.extensions.scripting.jsf.resources;


import org.apache.myfaces.extensions.scripting.core.common.util.ClassLoaderUtils;

import javax.faces.application.ProjectStage;
import javax.faces.context.FacesContext;
import java.io.InputStream;
import java.net.URL;

/**
 * A resource loader implementation which loads resources from the thread ClassLoader.
 * 
 */
public class ClassLoaderResourceLoader extends ResourceLoader
{
    /**
     * It checks version like this: 1, 1_0, 1_0_0, 100_100
     * 
     * Used on getLibraryVersion to filter resource directories
     **/
    //protected static Pattern VERSION_CHECKER = Pattern.compile("\\p{Digit}+(_\\p{Digit}*)*");

    /**
     * It checks version like this: /1.js, /1_0.js, /1_0_0.js, /100_100.js
     * 
     * Used on getResourceVersion to filter resources
     **/
    //protected static Pattern RESOURCE_VERSION_CHECKER = Pattern.compile("/\\p{Digit}+(_\\p{Digit}*)*\\..*");

    /*
    private FileFilter _libraryFileFilter = new FileFilter()
    {
        public boolean accept(File pathname)
        {
            if (pathname.isDirectory() && VERSION_CHECKER.matcher(pathname.getName()).matches())
            {
                return true;
            }
            return false;
        }
    };*/

    /*
    private FileFilter _resourceFileFilter = new FileFilter()
    {
        public boolean accept(File pathname)
        {
            if (pathname.isDirectory() && RESOURCE_VERSION_CHECKER.matcher(pathname.getName()).matches())
            {
                return true;
            }
            return false;
        }
    };*/
    
    private final boolean _developmentStage;

    public ClassLoaderResourceLoader(String prefix)
    {
        super(prefix);
        _developmentStage = FacesContext.getCurrentInstance().isProjectStage(ProjectStage.Development);
    }

    @Override
    public String getLibraryVersion(String path)
    {
        return null;
        /*
        String libraryVersion = null;
        if (getPrefix() != null)
            path = getPrefix() + '/' + path;

        URL url = getClassLoader().getResource(path);

        if (url == null)
        {
            // This library does not exists for this
            // ResourceLoader
            return null;
        }

        // The problem here is how to scan the directory. When a ClassLoader
        // is used two cases could occur
        // 1. The files are unpacked so we can use Url.toURI and crawl
        // the directory using the api for files.
        // 2. The files are packed in a jar. This case is more tricky,
        // because we only have a URL. Checking the jar api we can use
        // JarURLConnection (Sounds strange, but the api of
        // URL.openConnection says that for a jar connection a
        // JarURLConnection is returned). From this point we can access
        // to the jar api and solve the algoritm.
        if (url.getProtocol().equals("file"))
        {
            try
            {
                File directory = new File(url.toURI());
                if (directory.isDirectory())
                {
                    File[] versions = directory.listFiles(_libraryFileFilter);
                    for (int i = 0; i < versions.length; i++)
                    {
                        String version = versions[i].getName();
                        if (VERSION_CHECKER.matcher(version).matches())
                        {
                            if (libraryVersion == null)
                            {
                                libraryVersion = version;
                            }
                            else if (getVersionComparator().compare(libraryVersion, version) < 0)
                            {
                                libraryVersion = version;
                            }
                        }
                    }
                }
            }
            catch (URISyntaxException e)
            {
                // Just return null, because library version cannot be
                // resolved.
                Logger log = Logger.getLogger(ClassLoaderResourceLoader.class.getName()); 
                if (log.isLoggable(Level.WARNING))
                {
                    log.log(Level.WARNING, "url "+url.toString()+" cannot be translated to uri: "+e.getMessage(), e);
                }
            }
        }
        else if (isJarResourceProtocol(url.getProtocol()))
        {
            try
            {
                url = getClassLoader().getResource(path + '/');

                if (url != null)
                {
                    JarURLConnection conn = (JarURLConnection)url.openConnection();
                    // See DIGESTER-29 for related problem
                    conn.setUseCaches(false);

                    try
                    {
                        if (conn.getJarEntry().isDirectory())
                        {
                            // Unfortunately, we have to scan all entry files
                            // because there is no proper api to scan it as a
                            // directory tree.
                            JarFile file = conn.getJarFile();
                            for (Enumeration<JarEntry> en = file.entries(); en.hasMoreElements();)
                            {
                                JarEntry entry = en.nextElement();
                                String entryName = entry.getName();
    
                                if (entryName.startsWith(path + '/'))
                                {
                                    if (entryName.length() == path.length() + 1)
                                    {
                                        // the same string, just skip it
                                        continue;
                                    }
    
                                    if (entryName.charAt(entryName.length() - 1) != '/')
                                    {
                                        // Skip files
                                        continue;
                                    }
    
                                    entryName = entryName.substring(path.length() + 1, entryName.length() - 1);
    
                                    if (entryName.indexOf('/') >= 0)
                                    {
                                        // Inner Directory
                                        continue;
                                    }
    
                                    String version = entryName;
                                    if (VERSION_CHECKER.matcher(version).matches())
                                    {
                                        if (libraryVersion == null)
                                        {
                                            libraryVersion = version;
                                        }
                                        else if (getVersionComparator().compare(libraryVersion, version) < 0)
                                        {
                                            libraryVersion = version;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    finally
                    {
                        //See TRINIDAD-73
                        //just close the input stream again if
                        //by inspecting the entries the stream
                        //was let open.
                        try
                        {
                            conn.getInputStream().close();
                        }
                        catch (Exception exception)
                        {
                            // Ignored
                        }
                    }
                }
            }
            catch (IOException e)
            {
                // Just return null, because library version cannot be
                // resolved.
                Logger log = Logger.getLogger(ClassLoaderResourceLoader.class.getName()); 
                if (log.isLoggable(Level.WARNING))
                {
                    log.log(Level.WARNING, "IOException when scanning for resource in jar file:", e);
                }
            }
        }
        return libraryVersion;
        */
    }

    @Override
    public InputStream getResourceInputStream(ResourceMeta resourceMeta)
    {
        if (getPrefix() != null && !"".equals(getPrefix()))
        {
            return getClassLoader().getResourceAsStream(getPrefix() + '/' + resourceMeta.getResourceIdentifier());
        }
        else
        {
            return getClassLoader().getResourceAsStream(resourceMeta.getResourceIdentifier());
        }
    }

    @Override
    public URL getResourceURL(ResourceMeta resourceMeta)
    {
        if (getPrefix() != null && !"".equals(getPrefix()))
        {
            return getClassLoader().getResource(getPrefix() + '/' + resourceMeta.getResourceIdentifier());
        }
        else
        {
            return getClassLoader().getResource(resourceMeta.getResourceIdentifier());
        }
    }

    @Override
    public String getResourceVersion(String path)
    {
        return null;
        /*
        String resourceVersion = null;

        if (getPrefix() != null)
            path = getPrefix() + '/' + path;

        URL url = getClassLoader().getResource(path);

        if (url == null)
        {
            // This library does not exists for this
            // ResourceLoader
            return null;
        }

        if (url.getProtocol().equals("file"))
        {
            try
            {
                File directory = new File(url.toURI());
                if (directory.isDirectory())
                {
                    File[] versions = directory.listFiles(_resourceFileFilter);
                    for (int i = 0; i < versions.length; i++)
                    {
                        String version = versions[i].getName();
                        if (resourceVersion == null)
                        {
                            resourceVersion = version;
                        }
                        else if (getVersionComparator().compare(resourceVersion, version) < 0)
                        {
                            resourceVersion = version;
                        }
                    }
                    //Since it is a directory and no version found set resourceVersion as invalid
                    if (resourceVersion == null)
                    {
                        resourceVersion = VERSION_INVALID;
                    }
                }
            }
            catch (URISyntaxException e)
            {
                Logger log = Logger.getLogger(ClassLoaderResourceLoader.class.getName()); 
                if (log.isLoggable(Level.WARNING))
                {
                    log.log(Level.WARNING, "url "+url.toString()+" cannot be translated to uri: "+e.getMessage(), e);
                }
            }
        }
        else if (isJarResourceProtocol(url.getProtocol()))
        {
            try
            {
                url = getClassLoader().getResource(path + '/');

                if (url != null)
                {
                    JarURLConnection conn = (JarURLConnection)url.openConnection();
                    // See DIGESTER-29 for related problem
                    conn.setUseCaches(false);

                    try
                    {
                        if (conn.getJarEntry().isDirectory())
                        {
                            // Unfortunately, we have to scan all entry files
                            JarFile file = conn.getJarFile();
                            for (Enumeration<JarEntry> en = file.entries(); en.hasMoreElements();)
                            {
                                JarEntry entry = en.nextElement();
                                String entryName = entry.getName();
    
                                if (entryName.startsWith(path + '/'))
                                {
                                    if (entryName.length() == path.length() + 1)
                                    {
                                        // the same string, just skip it
                                        continue;
                                    }
        
                                    entryName = entryName.substring(path.length());
                                    if (RESOURCE_VERSION_CHECKER.matcher(entryName).matches())
                                    {
                                        String version = entryName.substring(1, entryName.lastIndexOf('.'));
                                        if (resourceVersion == null)
                                        {
                                            resourceVersion = version;
                                        }
                                        else if (getVersionComparator().compare(resourceVersion, version) < 0)
                                        {
                                            resourceVersion = version;
                                        }
                                    }
                                }
                            }
                            if (resourceVersion == null)
                            {
                                resourceVersion = VERSION_INVALID;
                            }
                        }
                    }
                    finally
                    {
                        //See TRINIDAD-73
                        //just close the input stream again if
                        //by inspecting the entries the stream
                        //was let open.
                        try
                        {
                            conn.getInputStream().close();
                        }
                        catch (Exception exception)
                        {
                            // Ignored
                        }
                    }

                }
            }
            catch (IOException e)
            {
                // Just return null, because library version cannot be
                // resolved.
                Logger log = Logger.getLogger(ClassLoaderResourceLoader.class.getName()); 
                if (log.isLoggable(Level.WARNING))
                {
                    log.log(Level.WARNING, "IOException when scanning for resource in jar file:", e);
                }
            }
        }
        return resourceVersion;
        */
    }

    @Override
    public ResourceMeta createResourceMeta(String prefix, String libraryName, String libraryVersion,
                                           String resourceName, String resourceVersion)
    {
        if (_developmentStage && libraryName != null && 
                ResourceLoaderUtils.JAVAX_FACES_LIBRARY_NAME.equals(libraryName) &&
                ResourceLoaderUtils.JSF_JS_RESOURCE_NAME.equals(resourceName))
        {
            // InternalClassLoaderResourceLoader will serve it, so return null in this case.
            return null;
        } else if (_developmentStage && libraryName != null &&
                ResourceLoaderUtils.MYFACES_LIBRARY_NAME.equals(libraryName) &&
                ResourceLoaderUtils.MYFACES_JS_RESOURCE_NAME.equals(resourceName)) {
            // InternalClassLoaderResourceLoader will serve it, so return null in this case.
             return null;
        } else
        {
            return new ResourceMetaImpl(prefix, libraryName, libraryVersion, resourceName, resourceVersion);
        }
    }

    /**
     * Returns the ClassLoader to use when looking up resources under the top level package. By default, this is the
     * context class loader.
     * 
     * @return the ClassLoader used to lookup resources
     */
    protected ClassLoader getClassLoader()
    {
        return ClassLoaderUtils.getDefaultClassLoader();
    }

    @Override
    public boolean libraryExists(String libraryName)
    {
        if (getPrefix() != null && !"".equals(getPrefix()))
        {
            URL url = getClassLoader().getResource(getPrefix() + '/' + libraryName);
            if (url != null)
            {
                return true;
            }
        }
        else
        {
            URL url = getClassLoader().getResource(libraryName);
            if (url != null)
            {
                return true;
            }
        }
        return false;
    }

    /**
     * <p>Determines whether the given URL resource protocol refers to a JAR file. Note that
     * BEA WebLogic and IBM WebSphere don't use the "jar://" protocol for some reason even
     * though you can treat these resources just like normal JAR files, i.e. you can ignore
     * the difference between these protocols after this method has returned.</p>
     *
     * @param protocol the URL resource protocol you want to check
     *
     * @return <code>true</code> if the given URL resource protocol refers to a JAR file,
     *          <code>false</code> otherwise
     */
    /*
    private static boolean isJarResourceProtocol(String protocol)
    {
        // Websphere uses the protocol "wsjar://" and Weblogic uses the protocol "zip://".
        return "jar".equals(protocol) || "wsjar".equals(protocol) || "zip".equals(protocol); 
    }*/

}
