/*
 * Copyright (C) 2005 Alfresco, Inc.
 *
 * Licensed under the Mozilla Public License version 1.1 
 * with a permitted attribution clause. You may obtain a
 * copy of the License at
 *
 *   http://www.alfresco.org/legal/license.txt
 *
 * 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.alfresco.filesys.util;

/**
 * Wildcard Utility Class.
 * <p>
 * The WildCard class may be used to check Strings against a wildcard pattern using the SMB/CIFS
 * wildcard rules.
 * <p>
 * A number of static convenience methods are also provided.
 * 
 * @author GKSpencer
 */
public final class WildCard
{

    // Multiple character wildcard

    public static final int MULTICHAR_WILDCARD = '*';

    // Single character wildcard

    public static final int SINGLECHAR_WILDCARD = '?';

    // Unicode wildcards
    //
    // The spec states :-
    // translate '?' to '>'
    // translate '.' to '"' if followed by a '?' or '*'
    // translate '*' to '<' if followed by a '.'

    public static final int SINGLECHAR_UNICODE_WILDCARD = '>';
    public static final int DOT_UNICODE_WILDCARD = '"';
    public static final int MULTICHAR_UNICODE_WILDCARD = '<';

    // Wildcard types

    public static final int WILDCARD_NONE = 0; // no wildcard characters present in pattern
    public static final int WILDCARD_ALL = 1; // '*.*' and '*'
    public static final int WILDCARD_NAME = 2; // '*.ext'
    public static final int WILDCARD_EXT = 3; // 'name.*'
    public static final int WILDCARD_COMPLEX = 4; // complex wildcard

    public static final int WILDCARD_INVALID = -1;

    // Wildcard pattern and type

    private String m_pattern;
    private int m_type;

    // Start/end string to match for name/extension matching

    private String m_matchPart;
    private boolean m_caseSensitive;

    // Complex wildcard pattern

    private char[] m_patternChars;

    /**
     * Default constructor
     */
    public WildCard()
    {
        setType(WILDCARD_INVALID);
    }

    /**
     * Class constructor
     * 
     * @param pattern String
     * @param caseSensitive boolean
     */
    public WildCard(String pattern, boolean caseSensitive)
    {
        setPattern(pattern, caseSensitive);
    }

    /**
     * Return the wildcard pattern type
     * 
     * @return int
     */
    public final int isType()
    {
        return m_type;
    }

    /**
     * Check if case sensitive matching is enabled
     * 
     * @return boolean
     */
    public final boolean isCaseSensitive()
    {
        return m_caseSensitive;
    }

    /**
     * Return the wildcard pattern string
     * 
     * @return String
     */
    public final String getPattern()
    {
        return m_pattern;
    }

    /**
     * Return the match part for wildcard name and wildcard extension type patterns
     * 
     * @return String
     */
    public final String getMatchPart()
    {
        return m_matchPart;
    }

    /**
     * Determine if the string matches the wildcard pattern
     * 
     * @param str String
     * @return boolean
     */
    public final boolean matchesPattern(String str)
    {

        // Check the pattern type and compare the string

        boolean sts = false;

        switch (isType())
        {

        // Match all wildcard

        case WILDCARD_ALL:
            sts = true;
            break;

        // Match any name

        case WILDCARD_NAME:
            if (isCaseSensitive())
            {

                // Check if the string ends with the required file extension

                sts = str.endsWith(m_matchPart);
            }
            else
            {

                // Normalize the string and compare

                String upStr = str.toUpperCase();
                sts = upStr.endsWith(m_matchPart);
            }
            break;

        // Match any file extension

        case WILDCARD_EXT:
            if (isCaseSensitive())
            {

                // Check if the string starts with the required file name

                sts = str.startsWith(m_matchPart);
            }
            else
            {

                // Normalize the string and compare

                String upStr = str.toUpperCase();
                sts = upStr.startsWith(m_matchPart);
            }
            break;

        // Complex wildcard matching

        case WILDCARD_COMPLEX:
            if (isCaseSensitive())
                sts = matchComplexWildcard(str);
            else
            {

                // Normalize the string and compare

                String upStr = str.toUpperCase();
                sts = matchComplexWildcard(upStr);
            }
            break;

        // No wildcard characters in pattern, compare strings

        case WILDCARD_NONE:
            if (isCaseSensitive())
            {
                if (str.compareTo(m_pattern) == 0)
                    sts = true;
            }
            else if (str.equalsIgnoreCase(m_pattern))
                sts = true;
            break;
        }

        // Return the wildcard match status

        return sts;
    }

    /**
     * Match a complex wildcard pattern with the specified string
     * 
     * @param str String
     * @return boolean
     */
    protected final boolean matchComplexWildcard(String str)
    {

        // Convert the string to a char array for matching

        char[] strChars = str.toCharArray();

        // Compare the string to the wildcard pattern

        int wpos = 0;
        int wlen = m_patternChars.length;

        int spos = 0;
        int slen = strChars.length;

        char patChar;
        boolean matchFailed = false;

        while (matchFailed == false && wpos < m_patternChars.length)
        {

            // Match the current pattern character

            patChar = m_patternChars[wpos++];

            switch (patChar)
            {

            // Match single character

            case SINGLECHAR_WILDCARD:
                if (spos < slen)
                    spos++;
                else
                    matchFailed = true;
                break;

            // Match zero or more characters

            case MULTICHAR_WILDCARD:

                // Check if there is another character in the wildcard pattern

                if (wpos < wlen)
                {

                    // Check if the character is not a wildcard character

                    patChar = m_patternChars[wpos];
                    if (patChar != SINGLECHAR_WILDCARD && patChar != MULTICHAR_WILDCARD)
                    {

                        // Find the required character in the string

                        while (spos < slen && strChars[spos] != patChar)
                            spos++;
                        if (spos >= slen)
                            matchFailed = true;
                    }
                }
                else
                {

                    // Multi character wildcard at the end of the pattern, match all remaining
                    // characters

                    spos = slen;
                }
                break;

            // Match the pattern and string character

            default:
                if (spos >= slen || strChars[spos] != patChar)
                    matchFailed = true;
                else
                    spos++;
                break;
            }
        }

        // Check if the match was successul and return status

        if (matchFailed == false && spos == slen)
            return true;
        return false;
    }

    /**
     * Set the wildcard pattern string
     * 
     * @param pattern String
     * @param caseSensitive boolean
     */
    public final void setPattern(String pattern, boolean caseSensitive)
    {

        // Save the pattern string and case sensitive flag

        m_pattern = pattern;
        m_caseSensitive = caseSensitive;

        setType(WILDCARD_INVALID);

        // Check if the pattern string is valid

        if (pattern == null || pattern.length() == 0)
            return;

        // Check for the match all wildcard

        if (pattern.compareTo("*.*") == 0 || pattern.compareTo("*") == 0)
        {
            setType(WILDCARD_ALL);
            return;
        }

        // Check for a name wildcard, ie. '*.ext'

        if (pattern.startsWith("*."))
        {

            // Split the string to get the extension string

            if (pattern.length() > 2)
                m_matchPart = pattern.substring(1);
            else
                m_matchPart = "";

            // If matching is case insensitive then normalize the string

            if (isCaseSensitive() == false)
                m_matchPart = m_matchPart.toUpperCase();

            // If the file extension contains wildcards we will need to use a regular expression

            if (containsWildcards(m_matchPart) == false)
            {
                setType(WILDCARD_NAME);
                return;
            }
        }

        // Check for a file extension wildcard

        if (pattern.endsWith(".*"))
        {

            // Split the string to get the name string

            if (pattern.length() > 2)
                m_matchPart = pattern.substring(0, pattern.length() - 2);
            else
                m_matchPart = "";

            // If matching is case insensitive then normalize the string

            if (isCaseSensitive() == false)
                m_matchPart = m_matchPart.toUpperCase();

            // If the file name contains wildcards we will need to use a regular expression

            if (containsWildcards(m_matchPart) == false)
            {
                setType(WILDCARD_EXT);
                return;
            }
        }

        // Save the complex wildcard pattern as a char array for later pattern matching

        if (isCaseSensitive() == false)
            m_patternChars = m_pattern.toUpperCase().toCharArray();
        else
            m_patternChars = m_pattern.toCharArray();

        setType(WILDCARD_COMPLEX);
    }

    /**
     * Set the wildcard type
     * 
     * @param typ int
     */
    private final void setType(int typ)
    {
        m_type = typ;
    }

    /**
     * Return the wildcard as a string
     * 
     * @return String
     */
    public String toString()
    {
        StringBuffer str = new StringBuffer();
        str.append("[");
        str.append(getPattern());
        str.append(",");
        str.append(isType());
        str.append(",");

        if (m_matchPart != null)
            str.append(m_matchPart);

        if (isCaseSensitive())
            str.append(",Case");
        else
            str.append(",NoCase");
        str.append("]");

        return str.toString();
    }

    /**
     * Check if the string contains any wildcard characters.
     * 
     * @return boolean
     * @param str java.lang.String
     */
    public final static boolean containsWildcards(String str)
    {

        // Check the string for wildcard characters

        if (str.indexOf(MULTICHAR_WILDCARD) != -1)
            return true;

        if (str.indexOf(SINGLECHAR_WILDCARD) != -1)
            return true;

        // No wildcards found in the string

        return false;
    }

    /**
     * Check if a string contains any of the Unicode wildcard characters
     * 
     * @param str String
     * @return boolean
     */
    public final static boolean containsUnicodeWildcard(String str)
    {

        // Check if the string contains any of the Unicode wildcards

        if (str.indexOf(SINGLECHAR_UNICODE_WILDCARD) != -1 || str.indexOf(MULTICHAR_UNICODE_WILDCARD) != -1
                || str.indexOf(DOT_UNICODE_WILDCARD) != -1)
            return true;
        return false;
    }

    /**
     * Convert the Unicode wildcard string to a standard DOS wildcard string
     * 
     * @param str String
     * @return String
     */
    public final static String convertUnicodeWildcardToDOS(String str)
    {

        // Create a buffer for the new wildcard string

        StringBuffer newStr = new StringBuffer(str.length());

        // Convert the Unicode wildcard string to a DOS wildcard string

        for (int i = 0; i < str.length(); i++)
        {

            // Get the current character

            char ch = str.charAt(i);

            // Check for a Unicode wildcard character

            if (ch == SINGLECHAR_UNICODE_WILDCARD)
            {

                // Translate to the DOS single character wildcard character

                ch = SINGLECHAR_WILDCARD;
            }
            else if (ch == MULTICHAR_UNICODE_WILDCARD)
            {

                // Check if the current character is followed by a '.', if so then translate to the
                // DOS multi character
                // wildcard

                if (i < (str.length() - 1) && str.charAt(i + 1) == '.')
                    ch = MULTICHAR_WILDCARD;
            }
            else if (ch == DOT_UNICODE_WILDCARD)
            {

                // Check if the current character is followed by a DOS single/multi character
                // wildcard

                if (i < (str.length() - 1))
                {
                    char nextCh = str.charAt(i + 1);
                    if (nextCh == SINGLECHAR_WILDCARD || nextCh == MULTICHAR_WILDCARD
                            || nextCh == SINGLECHAR_UNICODE_WILDCARD)
                        ch = '.';
                }
            }

            // Append the character to the translated wildcard string

            newStr.append(ch);
        }

        // Return the translated wildcard string

        return newStr.toString();
    }

    /**
     * Convert a wildcard string to a regular expression
     * 
     * @param path String
     * @return String
     */
    public final static String convertToRegexp(String path)
    {

        // Convert the path to characters, check if the wildcard string ends with a single character
        // wildcard

        char[] smbPattern = path.toCharArray();
        boolean endsWithQ = smbPattern[smbPattern.length - 1] == '?';

        // Build up the regular expression

        StringBuffer sb = new StringBuffer();
        sb.append('^');

        for (int i = 0; i < smbPattern.length; i++)
        {

            // Process the current character

            switch (smbPattern[i])
            {

            // Multi character wildcard

            case '*':
                sb.append(".*");
                break;

            // Single character wildcard

            case '?':
                if (endsWithQ)
                {
                    boolean restQ = true;
                    for (int j = i + 1; j < smbPattern.length; j++)
                    {
                        if (smbPattern[j] != '?')
                        {
                            restQ = false;
                            break;
                        }
                    }
                    if (restQ)
                        sb.append(".?");
                    else
                        sb.append('.');
                }
                else
                    sb.append('.');
                break;

            // Escape regular expression special characters

            case '.':
            case '+':
            case '\\':
            case '[':
            case ']':
            case '^':
            case '$':
            case '(':
            case ')':
                sb.append('\\');
                sb.append(smbPattern[i]);
                break;

            // Normal characters, just pass through

            default:
                sb.append(smbPattern[i]);
                break;
            }
        }
        sb.append('$');

        // Return the regular expression string

        return sb.toString();
    }
}