package org.apache.maven.wagon;

/*
 * 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.
 */

import java.io.File;
import java.util.StringTokenizer;

/**
 * Various path (URL) manipulation routines
 *
 * @author <a href="michal.maczka@dimatics.com">Michal Maczka</a>
 *
 */
public final class PathUtils
{
    private PathUtils()
    {
    }

    /**
     * Returns the directory path portion of a file specification string.
     * Matches the equally named unix command.
     *
     * @return The directory portion excluding the ending file separator.
     */
    public static String dirname( final String path )
    {
        final int i = path.lastIndexOf( "/" );

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

    /**
     * Returns the filename portion of a file specification string.
     *
     * @return The filename string with extension.
     */
    public static String filename( final String path )
    {
        final int i = path.lastIndexOf( "/" );
        return ( ( i >= 0 ) ? path.substring( i + 1 ) : path );
    }

    public static String[] dirnames( final String path )
    {
        final String dirname = PathUtils.dirname( path );
        return split( dirname, "/", -1 );

    }

    private static String[] split( final String str, final String separator, final int max )
    {
        final StringTokenizer tok;

        if ( separator == null )
        {
            // Null separator means we're using StringTokenizer's default
            // delimiter, which comprises all whitespace characters.
            tok = new StringTokenizer( str );
        }
        else
        {
            tok = new StringTokenizer( str, separator );
        }

        int listSize = tok.countTokens();

        if ( max > 0 && listSize > max )
        {
            listSize = max;
        }

        final String[] list = new String[listSize];

        int i = 0;

        int lastTokenBegin;
        int lastTokenEnd = 0;

        while ( tok.hasMoreTokens() )
        {
            if ( max > 0 && i == listSize - 1 )
            {
                // In the situation where we hit the max yet have
                // tokens left over in our input, the last list
                // element gets all remaining text.
                final String endToken = tok.nextToken();

                lastTokenBegin = str.indexOf( endToken, lastTokenEnd );

                list[i] = str.substring( lastTokenBegin );

                break;

            }
            else
            {
                list[i] = tok.nextToken();

                lastTokenBegin = str.indexOf( list[i], lastTokenEnd );

                lastTokenEnd = lastTokenBegin + list[i].length();
            }

            i++;
        }
        return list;
    }

    /**
     * Return the host name (Removes protocol and path from the URL) E.g: for input
     * <code>http://www.codehause.org</code> this method will return <code>www.apache.org</code>
     *
     * @param url the url
     * @return the host name
     */
    public static String host( final String url )
    {
        if ( url == null || url.length() == 0 )
        {
            return "localhost";
        }
        String authorization = authorization( url );
        int index = authorization.indexOf( '@' );
        String host = ( index >= 0 ) ? authorization.substring( index + 1 ) : authorization;
        // In case we have IPv6 in the host portion of url
        // we have to remove brackets '[' and ']'
        return ( ( host.charAt( 0 ) == '[' ) && ( host.charAt( host.length() - 1 ) == ']' ) )
                        ? host.substring( 1, host.length() - 1 )
                        : host;
    }

    /**
     * This was changed from private to package local so that it can be unit tested.
     */
    static String authorization( final String url )
    {
        if ( url == null )
        {
            return "localhost";
        }

        String protocol = PathUtils.protocol( url );

        if ( protocol == null || protocol.equalsIgnoreCase( "file" ) )
        {
            return "localhost";
        }

        String host = url;
        if ( protocol.equalsIgnoreCase( "scm" ) )
        {
            // skip over type
            host = host.substring( host.indexOf( ":", 4 ) + 1 ).trim();
        }

        protocol = PathUtils.protocol( host );

        if ( protocol.equalsIgnoreCase( "file" ) )
        {
            return "localhost";
        }

        // skip over protocol
        host = host.substring( host.indexOf( ":" ) + 1 ).trim();
        if ( host.startsWith( "//" ) )
        {
            host = host.substring( 2 );
        }

        int pos = host.indexOf( "/" );

        if ( pos > 0 )
        {
            host = host.substring( 0, pos );
        }

        pos = host.indexOf( '@' );

        pos = ( pos > 0 ) ? endOfHostPosition( host, pos ) : endOfHostPosition( host, 0 );

        if ( pos > 0 )
        {
            host = host.substring( 0, pos );
        }
        return host;
    }

    private static int endOfHostPosition( String host, int pos )
    {
        // if this is IPv6 then it will be in IPv6 Literal Addresses in URL's format
        // see: http://www.ietf.org/rfc/rfc2732.txt
        int endOfIPv6Pos = host.indexOf( ']', pos );
        return ( endOfIPv6Pos > 0 ) ? endOfIPv6Pos + 1 : host.indexOf( ":", pos );
    }

    /**
     * /**
     * Return the protocol name.
     * <br/>
     * E.g: for input
     * <code>http://www.codehause.org</code> this method will return <code>http</code>
     *
     * @param url the url
     * @return the host name
     */
    public static String protocol( final String url )
    {
        final int pos = url.indexOf( ":" );

        if ( pos == -1 )
        {
            return "";
        }
        return url.substring( 0, pos ).trim();
    }

    /**
     * @param url
     * @return the port or {@link WagonConstants#UNKNOWN_PORT} if not existent
     */
    public static int port( String url )
    {

        final String protocol = PathUtils.protocol( url );

        if ( protocol == null || protocol.equalsIgnoreCase( "file" ) )
        {
            return WagonConstants.UNKNOWN_PORT;
        }

        final String authorization = PathUtils.authorization( url );

        if ( authorization == null )
        {
            return WagonConstants.UNKNOWN_PORT;
        }

        if ( protocol.equalsIgnoreCase( "scm" ) )
        {
            // skip over type
            url = url.substring( url.indexOf( ":", 4 ) + 1 ).trim();
        }

        if ( url.regionMatches( true, 0, "file:", 0, 5 ) || url.regionMatches( true, 0, "local:", 0, 6 ) )
        {
            return WagonConstants.UNKNOWN_PORT;
        }

        // skip over protocol
        url = url.substring( url.indexOf( ":" ) + 1 ).trim();
        if ( url.startsWith( "//" ) )
        {
            url = url.substring( 2 );
        }

        int start = authorization.length();

        if ( url.length() > start && url.charAt( start ) == ':' )
        {
            int end = url.indexOf( '/', start );

            if ( end == start + 1 )
            {
                // it is :/
                return WagonConstants.UNKNOWN_PORT;
            }

            if ( end == -1 )
            {
                end = url.length();
            }

            return Integer.parseInt( url.substring( start + 1, end ) );
        }
        else
        {
            return WagonConstants.UNKNOWN_PORT;
        }

    }

    /**
     * Derive the path portion of the given URL.
     *
     * @param url the repository URL
     * @return the basedir of the repository
     * @todo need to URL decode for spaces?
     */
    public static String basedir( String url )
    {
        String protocol = PathUtils.protocol( url );

        String retValue = null;

        if ( protocol.equalsIgnoreCase( "scm" ) )
        {
            // skip over SCM bits
            if ( url.regionMatches( true, 0, "scm:svn:", 0, 8 ) )
            {
                url = url.substring( url.indexOf( ":", 4 ) + 1 );
                protocol = PathUtils.protocol( url );
            }
        }

        if ( protocol.equalsIgnoreCase( "file" ) )
        {
            retValue = url.substring( protocol.length() + 1 );
            retValue = decode( retValue );
            // special case: if omitted // on protocol, keep path as is
            if ( retValue.startsWith( "//" ) )
            {
                retValue = retValue.substring( 2 );

                if ( retValue.length() >= 2 && ( retValue.charAt( 1 ) == '|' || retValue.charAt( 1 ) == ':' ) )
                {
                    // special case: if there is a windows drive letter, then keep the original return value
                    retValue = retValue.charAt( 0 ) + ":" + retValue.substring( 2 );
                }
                else
                {
                    // Now we expect the host
                    int index = retValue.indexOf( "/" );
                    if ( index >= 0 )
                    {
                        retValue = retValue.substring( index + 1 );
                    }

                    // special case: if there is a windows drive letter, then keep the original return value
                    if ( retValue.length() >= 2 && ( retValue.charAt( 1 ) == '|' || retValue.charAt( 1 ) == ':' ) )
                    {
                        retValue = retValue.charAt( 0 ) + ":" + retValue.substring( 2 );
                    }
                    else if ( index >= 0 )
                    {
                        // leading / was previously stripped
                        retValue = "/" + retValue;
                    }
                }
            }

            // special case: if there is a windows drive letter using |, switch to :
            if ( retValue.length() >= 2 && retValue.charAt( 1 ) == '|' )
            {
                retValue = retValue.charAt( 0 ) + ":" + retValue.substring( 2 );
            }
        }
        else
        {
            final String authorization = PathUtils.authorization( url );

            final int port = PathUtils.port( url );

            int pos = 0;

            if ( protocol.equalsIgnoreCase( "scm" ) )
            {
                pos = url.indexOf( ":", 4 ) + 1;
                pos = url.indexOf( ":", pos ) + 1;
            }
            else
            {
                int index = url.indexOf( "://" );
                if ( index != -1 )
                {
                    pos = index + 3;
                }
            }

            pos += authorization.length();

            if ( port != WagonConstants.UNKNOWN_PORT )
            {
                pos = pos + Integer.toString( port ).length() + 1;
            }

            if ( url.length() > pos )
            {
                retValue = url.substring( pos );
                if ( retValue.startsWith( ":" ) )
                {
                    // this is for :/ after the host
                    retValue = retValue.substring( 1 );
                }

                // one module may be allowed in the path in CVS
                retValue = retValue.replace( ':', '/' );
            }
        }

        if ( retValue == null )
        {
            retValue = "/";
        }
        return retValue.trim();
    }

    /**
     * Decodes the specified (portion of a) URL. <strong>Note:</strong> This decoder assumes that ISO-8859-1 is used to
     * convert URL-encoded octets to characters.
     *
     * @param url The URL to decode, may be <code>null</code>.
     * @return The decoded URL or <code>null</code> if the input was <code>null</code>.
     */
    private static String decode( String url )
    {
        String decoded = url;
        if ( url != null )
        {
            int pos = -1;
            while ( ( pos = decoded.indexOf( '%', pos + 1 ) ) >= 0 )
            {
                if ( pos + 2 < decoded.length() )
                {
                    String hexStr = decoded.substring( pos + 1, pos + 3 );
                    char ch = (char) Integer.parseInt( hexStr, 16 );
                    decoded = decoded.substring( 0, pos ) + ch + decoded.substring( pos + 3 );
                }
            }
        }
        return decoded;
    }

    public static String user( String url )
    {
        String host = authorization( url );
        int index = host.indexOf( '@' );
        if ( index > 0 )
        {
            String userInfo = host.substring( 0, index );
            index = userInfo.indexOf( ':' );
            if ( index > 0 )
            {
                return userInfo.substring( 0, index );
            }
            else if ( index < 0 )
            {
                return userInfo;
            }
        }
        return null;
    }

    public static String password( String url )
    {
        String host = authorization( url );
        int index = host.indexOf( '@' );
        if ( index > 0 )
        {
            String userInfo = host.substring( 0, index );
            index = userInfo.indexOf( ':' );
            if ( index >= 0 )
            {
                return userInfo.substring( index + 1 );
            }
        }
        return null;
    }

    // TODO: move to plexus-utils or use something appropriate from there
    public static String toRelative( File basedir, String absolutePath )
    {
        String relative;

        absolutePath = absolutePath.replace( '\\', '/' );
        String basedirPath = basedir.getAbsolutePath().replace( '\\', '/' );

        if ( absolutePath.startsWith( basedirPath ) )
        {
            relative = absolutePath.substring( basedirPath.length() );
            if ( relative.startsWith( "/" ) )
            {
                relative = relative.substring( 1 );
            }
            if ( relative.length() <= 0 )
            {
                relative = ".";
            }
        }
        else
        {
            relative = absolutePath;
        }

        return relative;
    }
}
