/**
 * Copyright (C) 2010 Orbeon, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version
 * 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.resources;

import org.apache.log4j.Logger;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.util.LoggerFactory;
import org.orbeon.oxf.util.NetUtils;

import java.io.*;
import java.net.*;
import java.util.jar.JarEntry;


/**
 * The classloader resource manager is able to load resources from a JAR file,
 * or from a directory in the system classpath. It is useful when resources are
 * bundled with an application.
 */
public class ClassLoaderResourceManagerImpl extends ResourceManagerBase {

    private static Logger logger = LoggerFactory.createLogger(ClassLoaderResourceManagerImpl.class);

    private final Class clazz;
    private final boolean prependSlash;


    private URLConnection getConnection(final String key, boolean doNotThrowResourceNotFound) throws IOException {
        final String adjustedKey = prependSlash && !key.startsWith("/") ? "/" + key : key;
        final URL u = clazz.getResource(adjustedKey);
        if (u == null) {
            if (doNotThrowResourceNotFound) return null;
            else throw new ResourceNotFoundException(key);
        }
        return u.openConnection();
    }

    /**
     * Initialize this resource manager.
     */
    public ClassLoaderResourceManagerImpl(final java.util.Map props) {
        this(props, null);
    }

    /**
     * Initialize this resource manager with rooted in the specified class
     *
     * @param c a root class
     */
    public ClassLoaderResourceManagerImpl(final java.util.Map props, final Class c) {
        super(props);
        clazz = c == null ? getClass() : c;
        prependSlash = c == null;
    }

    /**
     * Returns a binary input stream for the specified key. The key could point
     * to any document type (text or binary).
     *
     * @param key A Resource Manager key
     * @return a input stream
     */
    public InputStream getContentAsStream(String key) {
        if (logger.isDebugEnabled())
            logger.debug("getContentAsStream(" + key + ")");

        final InputStream ret;
        try {
            final URLConnection uc = getConnection(key, false);
            uc.setUseCaches(false);
            ret = uc.getInputStream();
        } catch (final IOException e) {
            throw new OXFException(e);
        }
        return ret;
    }

    /**
     * Gets the last modified timestamp for the specified resource
     *
     * @param key                        A Resource Manager key
     * @param doNotThrowResourceNotFound
     * @return a timestamp
     */
    protected long lastModifiedImpl(final String key, boolean doNotThrowResourceNotFound) {
        // NOTE: Slower implementations in the VCS history (back to 2004!).
        // Old impl was generating 303K of garbage per request to / in the examples app.
        final long ret;
        try {
            final URLConnection uc = getConnection(key, doNotThrowResourceNotFound);
            if (uc == null) {
                ret = -1;
            } else {
                if (uc instanceof JarURLConnection) {
                    final JarEntry je = ((JarURLConnection) uc).getJarEntry();
                    ret = je.getTime();
                } else {
                    final URL url = uc.getURL();
                    final String prot = url.getProtocol();
                    if ("file".equalsIgnoreCase(prot)) {
                        final String fnam = url.getPath();
                        final File f = new File(fnam);
                        if (f.exists()) {
                            ret = f.lastModified();
                        } else {
                            final String fnamDec = URLDecoder.decode(fnam, "utf-8");
                            final File fdec = new File(fnamDec);
                            ret = fdec.lastModified();
                        }
                    } else {
                        final long l = NetUtils.getLastModified(uc);
                        ret = l == 0 ? 1 : l;
                    }
                }
            }
        } catch (final IOException e) {
            throw new OXFException(e);
        }
        return ret;
    }

    /**
     * Returns the length of the file denoted by this abstract pathname.
     *
     * @return The length, in bytes, of the file denoted by this abstract pathname, or 0L if the file does not exist
     */
    public int length(String key) {
        try {
            return getConnection(key, false).getContentLength();
        } catch (IOException e) {
            throw new OXFException(e);
        }
    }

    /**
     * Indicates if the resource manager implementation suports write operations
     *
     * @return true if write operations are allowed
     */
    public boolean canWrite(String key) {
        return false;
    }

    /**
     * Allows writing to the resource
     *
     * @param key A Resource Manager key
     * @return an output stream
     */
    public OutputStream getOutputStream(String key) {
        throw new OXFException("Write Operation not supported");
    }

    /**
     * Allow writing to the resource
     *
     * @param key A Resource Manager key
     * @return a writer
     */
    public Writer getWriter(String key) {
        throw new OXFException("Write Operation not supported");
    }

    public String getRealPath(String key) {
        return null;
    }
}
