/*
 * Copyright (C) 2004-2008 Jive Software. All rights reserved.
 *
 * 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 org.jivesoftware.xmpp.workgroup.utils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLConnection;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <code>URLFileSystem</code> class handles some of the most common
 * functionallity when working with URLs.
 *
 * @version 1.0, 03/12/14
 */

public class URLFileSystem {
    
    private static final Logger Log = LoggerFactory.getLogger(URLFileSystem.class);
    
    public static void main(String args[]) {
    }

    /**
     * Copies the contents at <CODE>src</CODE> to <CODE>dst</CODE>.
     */
    public static void copy(URL src, File dst) throws IOException {
        InputStream in = null;
        OutputStream out = null;
        try {
            in = src.openStream();
            out = new FileOutputStream(dst);
            dst.mkdirs();
            copy(in, out);
        }
        finally {
            try {
                if (in != null) in.close();
            }
            catch (IOException e) {
            }
            try {
                if (out != null) out.close();
            }
            catch (IOException e) {
            }
        }
    }

    /**
     * Common code for copy routines.  By convention, the streams are
     * closed in the same method in which they were opened.  Thus,
     * this method does not close the streams when the copying is done.
     */
    private static void copy(InputStream in, OutputStream out)
            throws IOException {
        final byte[] buffer = new byte[4096];
        while (true) {
            final int bytesRead = in.read(buffer);
            if (bytesRead < 0) {
                break;
            }
            out.write(buffer, 0, bytesRead);
        }
    }

    /**
     * If a dot ('.') occurs in the path portion of the {@link URL}, then
     * all of the text starting at the last dot is returned, including
     * the dot.  If the last dot is also the last character in the path,
     * then the dot by itself is returned.  If there is no dot in the
     * path, then the empty string is returned.
     */
    public static String getSuffix(URL url) {
        final String path = url.getPath();
        int lastDot = path.lastIndexOf('.');

        return (lastDot >= 0) ? path.substring(lastDot) : "";
    }


    //--------------------------------------------------------------------------
    //  URLFileSystemHelper public API...
    //--------------------------------------------------------------------------
    /**
     * Returns a canonical form of the {@link URL}, if one is available.
     * <P>
     * <p/>
     * The default implementation just returns the specified {@link URL}
     * as-is.
     */
    public URL canonicalize(URL url) {
        return url;
    }

    /**
     * Tests whether the application can read the resource at the
     * specified {@link URL}.
     *
     * @return <CODE>true</CODE> if and only if the specified
     *         {@link URL} points to a resource that exists <EM>and</EM> can be
     *         read by the application; <CODE>false</CODE> otherwise.
     */
    public boolean canRead(URL url) {
        try {
            final URLConnection urlConnection = url.openConnection();
            return urlConnection.getDoInput();
        }
        catch (Exception e) {
            return false;
        }
    }


    /**
     * Tests whether the application can modify the resource at the
     * specified {@link URL}.
     *
     * @return <CODE>true</CODE> if and only if the specified
     *         {@link URL} points to a file that exists <EM>and</EM> the
     *         application is allowed to write to the file; <CODE>false</CODE>
     *         otherwise.
     */
    public boolean canWrite(URL url) {
        try {
            final URLConnection urlConnection = url.openConnection();
            return urlConnection.getDoOutput();
        }
        catch (Exception e) {
            return false;
        }
    }

    /**
     * Tests whether the application can create the resource at the specified
     * {@link URL}.
     *
     * @return <CODE>true</CODE> if the resource at the specified {@link URL}
     *         exists or can be created; <CODE>false</CODE> otherwise.
     */
    public boolean canCreate(URL url) {
        return true;
    }

    /**
     * Tests whether the specified {@link URL} is valid. If the resource
     * pointed by the {@link URL} exists the method returns <CODE>true</CODE>.
     * If the resource does not exist, the method tests that all components
     * of the path can be created.
     *
     * @return <CODE>true</CODE> if the {@link URL} is valid.
     */
    public boolean isValid(URL url) {
        if (exists(url)) {
            return true;
        }

        return canCreate(url);
    }

    /**
     * Returns <CODE>true</CODE> if the specified {@link URL} points to a
     * resource that currently exists; returns <CODE>false</CODE>
     * otherwise.<P>
     * <p/>
     * The default implementation simply returns <CODE>false</CODE>
     * without doing anything.
     */
    public static boolean exists(URL url) {
        return url2File(url).exists();
    }

    public static boolean mkdirs(URL url) {
        final File file = url2File(url);
        if (!file.exists()) {
            return file.mkdirs();
        }
        return true;
    }


    /**
     * Returns the name of the file contained by the {@link URL}, not
     * including any protocol, hostname authentication, directory path,
     * anchor, or query.  This simply returns the simple filename.  For
     * example, if you pass in an {@link URL} whose string representation
     * is:
     * <p/>
     * <BLOCKQUOTE><CODE>
     * protocol://host:1010/dir1/dir2/file.ext#anchor?query
     * </CODE></BLOCKQUOTE>
     * <p/>
     * the returned value is "<CODE>file.ext</CODE>" (without the
     * quotes).<P>
     * <p/>
     * The returned file name should only be used for display purposes
     * and not for opening streams or otherwise trying to locate the
     * resource indicated by the {@link URL}.
     */
    public static String getFileName(URL url) {
        if (url == null) {
            return "";
        }

        final String path = url.getPath();
        if (path.equals("/")) {
            return "/";
        }
        final int lastSep = path.lastIndexOf('/');
        if (lastSep == path.length() - 1) {
            final int lastSep2 = path.lastIndexOf('/', lastSep - 1);
            return path.substring(lastSep2 + 1, lastSep);
        }
        else {
            return path.substring(lastSep + 1);
        }
    }


    /**
     * Returns the number of bytes contained in the resource that the
     * specified {@link URL} points to.  If the length cannot be
     * determined, <CODE>-1</CODE> is returned.<P>
     * <p/>
     * The default implementation attempts to get the content length from
     * the {@link URLConnection} associated with the {@link URL}.  If that
     * fails for some reason (e.g. the resource does not exist, there was
     * some other an I/O exception, etc.), <CODE>-1</CODE> is returned.
     *
     * @see URLConnection
     */
    public long getLength(URL url) {
        try {
            final URLConnection urlConnection = url.openConnection();
            return urlConnection.getContentLength();
        }
        catch (Exception e) {
            return -1;
        }
    }


    /**
     * Returns the name of the file contained by the {@link URL}, not
     * including any protocol, hostname authentication, directory path,
     * anchor, or query.  This simply returns the simple filename.  For
     * example, if you pass in an {@link URL} whose string representation
     * is:
     * <p/>
     * <BLOCKQUOTE><CODE>
     * protocol://host:1010/dir1/dir2/file.ext1.ext2#anchor?query
     * </CODE></BLOCKQUOTE>
     * <p/>
     * the returned value is "<CODE>file</CODE>" (without the quotes).<P>
     * <p/>
     * The returned file name should only be used for display purposes
     * and not for opening streams or otherwise trying to locate the
     * resource indicated by the {@link URL}.<P>
     * <p/>
     * The default implementation first calls {@link #getFileName(URL)} to
     * get the file name part.  Then all characters starting with the
     * first occurrence of '.' are removed.  The remaining string is then
     * returned.
     */
    public static String getName(URL url) {
        final String fileName = getFileName(url);
        final int firstDot = fileName.indexOf('.');
        return firstDot > 0 ? fileName.substring(0, firstDot) : fileName;
    }


    /**
     * Returns the path part of the {@link URL}.  The returned string
     * is acceptable to use in one of the {@link URLFactory} methods
     * that takes a path.<P>
     * <p/>
     * The default implementation delegates to {@link URL#getPath()}.
     */
    public String getPath(URL url) {
        return url.getPath();
    }


    /**
     * Returns the path part of the {@link URL} without the last file
     * extension.  To clarify, the following examples demonstrate the
     * different cases that come up:
     * <p/>
     * <TABLE BORDER COLS=2 WIDTH="100%">
     * <TR>
     * <TD><CENTER>Path part of input {@link URL}</CENTER></TD>
     * <TD><CENTER>Output {@link String}</CENTER</TD>
     * </TR>
     * <TR>
     * <TD><CODE>/dir/file.ext</CODE></TD>
     * <TD><CODE>/dir/file</CODE></TD>
     * <TR>
     * <TR>
     * <TD><CODE>/dir/file.ext1.ext2</CODE></TD>
     * <TD><CODE>/dir/file.ext1</CODE></TD>
     * <TR>
     * <TR>
     * <TD><CODE>/dir1.ext1/dir2.ext2/file.ext1.ext2</CODE></TD>
     * <TD><CODE>/dir1.ext1/dir2.ext2/file.ext1</CODE></TD>
     * <TR>
     * <TR>
     * <TD><CODE>/file.ext</CODE></TD>
     * <TD><CODE>/file</CODE></TD>
     * <TR>
     * <TR>
     * <TD><CODE>/dir.ext/file</CODE></TD>
     * <TD><CODE>/dir.ext/file</CODE></TD>
     * <TR>
     * <TR>
     * <TD><CODE>/dir/file</CODE></TD>
     * <TD><CODE>/dir/file</CODE></TD>
     * <TR>
     * <TR>
     * <TD><CODE>/file</CODE></TD>
     * <TD><CODE>/file</CODE></TD>
     * <TR>
     * <TR>
     * <TD><CODE>/.ext</CODE></TD>
     * <TD><CODE>/</CODE></TD>
     * <TR>
     * </TABLE>
     * <p/>
     * The default implementation gets the path from {@link
     * #getPath(URL)} and then trims off all of the characters beginning
     * with the last "." in the path, if and only if the last "." comes
     * after the last "/" in the path.  If the last "." comes before
     * the last "/" or if there is no "." at all, then the entire path
     * is returned.
     */
    public String getPathNoExt(URL url) {
        final String path = getPath(url);
        final int lastSlash = path.lastIndexOf("/");
        final int lastDot = path.lastIndexOf(".");
        if (lastDot <= lastSlash) {
            //  When the lastDot < lastSlash, it means that one of the
            //  directories has an extension, but the filename itself has
            //  no extension.  In this case, returning the whole path is
            //  the correct behavior.
            //
            //  The only time that lastDot and lastSlash can be equal occurs
            //  when both of them are -1.  In that case, returning the whole
            //  path is the correct behavior.
            return path;
        }
        //  At this point, we know that lastDot must be non-negative, so
        //  we can return the whole path string up to the last dot.
        return path.substring(0, lastDot);
    }


    /**
     * Returns the platform-dependent String representation of the
     * {@link URL}; the returned string should be considered acceptable
     * for users to read.  In general, the returned string should omit
     * as many parts of the {@link URL} as possible.  For the "file"
     * protocol, therefore, the platform pathname should just be the
     * pathname alone (no protocol) using the appropriate file separator
     * character for the current platform.  For other protocols, it may
     * be necessary to reformat the {@link URL} string into a more
     * human-readable form.  That decision is left to each
     * <CODE>URLFileSystemHelper</CODE> implementor.
     * <p/>
     * The default implementation returns <CODE>url.toString()</CODE>.
     * If the {@link URL} is <CODE>null</CODE>, the empty string is
     * returned.
     *
     * @return The path portion of the specified {@link URL} in
     *         platform-dependent notation.  This value should only be used for
     *         display purposes and not for opening streams or otherwise trying
     *         to locate the document.
     */
    public String getPlatformPathName(URL url) {
        return url != null ? url.toString() : "";
    }

    public static URL newFileURL(File file) {
        String filePath = file.getPath();
        if (filePath == null) {
            return null;
        }
        final String path = sanitizePath(filePath);
        return newURL("file", path);
    }

    public static URL newFileURL(String filePath) {
        if (filePath == null) {
            return null;
        }
        final String path = sanitizePath(filePath);
        return newURL("file", path);
    }

    /**
     * This "sanitizes" the specified string path by converting all
     * {@link File#separatorChar} characters to forward slash ('/').
     * Also, a leading forward slash is prepended if the path does
     * not begin with one.
     */
    private static String sanitizePath(String path) {
        if (File.separatorChar != '/') {
            path = path.replace(File.separatorChar, '/');
        }
        if (!path.startsWith("/")) {
            path = "/" + path;
        }
        return path;
    }

    public static URL newURL(String protocol, String path) {
        return newURL(protocol, null, null, -1, path, null, null);
    }

    //--------------------------------------------------------------------------
    //  direct access factory methods...
    //--------------------------------------------------------------------------
    /**
     * Creates a new {@link URL} whose parts have the exact values that
     * are specified.  <EM>In general, you should avoid calling this
     * method directly.</EM><P>
     * <p/>
     * This method is the ultimate place where all of the other
     * <CODE>URLFactory</CODE> methods end up when creating an
     * {@link URL}.
     * <p/>
     * Non-sanitizing.
     */
    public static URL newURL(String protocol, String userinfo,
                             String host, int port,
                             String path, String query, String ref) {
        try {
            final URL seed = new URL(protocol, "", -1, "");
            final String authority = port < 0 ? host : host + ":" + port;
            final Object[] args = new Object[]
            {
                protocol, host, new Integer(port),
                authority, userinfo,
                path, query, ref,
            };

            //  IMPORTANT -- this *MUST* be the only place in URLFactory where
            //  the URL.set(...) method is used.  --jdijamco
            urlSet.invoke(seed, args);
            return seed;
        }
        catch (Exception e) {
            Log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * This {@link java.lang.reflect.Method} is used to work-around a bug in Sun's
     * <CODE>java.net.URL</CODE> implementation.  The {@link java.lang.reflect.Method}
     * allows us to set the parts of an {@link URL} directly.
     */
    private static final Method urlSet;

    static {
        final Class str = String.class;
        try {
            urlSet = URL.class.getDeclaredMethod("set", new Class[]{str, str, int.class, str, str, str, str, str});

            //  IMPORTANT:  This call to setAccessible effectively overrides
            //  the "protected" visibility constraint on the URL.set(...)
            //  method.  This is an intentional breaking of encapsulation to
            //  work-around severe bugs in Sun's java.net.URL implementation
            //  having to do with:
            //    *  poor handling of special characters like #, ?, and ;
            //    *  poor handling of whitespace
            //    *  no go way to disambiguate UNC paths on Win32
            //
            //  The use of setAccessible is an implementation detail of the
            //  URLFactory, and if Sun some day fixes their java.net.URL
            //  implementation to address the problems above, we may be able
            //  to change the internal mechanism to use the regular URL
            //  constructors.  For the time being, after having weighed the
            //  various other alternatives and even tried some of them (and
            //  encountered other problems), our decision is to force our way
            //  through to the URL.set(...) method, taking care to invoke
            //  it method exactly once per URL object with the exactly the
            //  right arguments.
            //
            //  --jdijamco  March 14, 2001
            urlSet.setAccessible(true);
        }
        catch (NoSuchMethodException e) {
            //!jdijamco -- Have some fallback option so that <clinit> doesn't
            //!jdijamco -- just totally barf and prevent the IDE from starting?
            throw new IllegalStateException();
        }
    }

    public static final File url2File(URL url) {
        final String path = url.getPath();
        final File file = new File(path);
        return file;
    }

    public static URL getParent(URL url) {
        final File file = url2File(url);
        final File parentFile = file.getParentFile();
        if (parentFile != null && !file.equals(parentFile)) {
            try {
                return parentFile.toURL();
            }
            catch (Exception ex) {
                return null;
            }
        }
        return null;
    }


}

