package ij.util;

/**
 * This class allows for simple wildcard pattern matching. Possible
 * patterns allow to match single characters ('?') or any count of
 * characters ('*').<p>
 * Wildcard characters can be escaped (default: by an '\').<p>
 * This class always matches for the whole word.<p>
 * Examples:
 * <pre>
 * WildcardMatch wm = new WildcardMatch();
 * System.out.println(wm.match("CfgOptions.class", "C*.class"));      // true
 * System.out.println(wm.match("CfgOptions.class", "?gOpti*c?as?"));  // false
 * System.out.println(wm.match("CfgOptions.class", "??gOpti*c?ass")); // true
 * System.out.println(wm.match("What's this?",     "What*\\?"));      // true
 * System.out.println(wm.match("What's this?",     "What*?"));        // true
 * System.out.println(wm.match("A \\ backslash", "*\\\\?back*"));     // true
 * </pre>
 */
public class WildcardMatch {

    public WildcardMatch() {
    }

    public WildcardMatch(char singleChar, char multipleChars) {
        this.sc = singleChar;
        this.mc = multipleChars;
    }

    /**
     * Sets new characters to be used as wildcard characters, overriding the
     * the default of '?' for any single character match and '*' for any
     * amount of characters, including 0 characters.
     * @param singleChar The char used to match exactly ONE character.
     * @param multipleChars The char used to match any amount of characters
     * including o characters.
     */
    public void setWildcardChars(char singleChar, char multipleChars) {
        this.sc = singleChar;
        this.mc = multipleChars;
    }

    /**
     * Sets the new character to be used as an escape character, overriding the
     * the default of '\'.
     * @param escapeChar The char used to match escape wildcard characters.
     */
    public void setEscapeChar(char escapeChar) {
        this.ec = escapeChar;
    }

    /**
     * Returns the character used to specify exactly one character.
     * @return Wildcard character matching any single character.
     */
    public char getSingleWildcardChar() {
        return sc;
    }

    /**
     * Returns the character used to specify any amount of characters.
     * @return Wildcard character matching any count of characters.
     */
    public char getMultipleWildcardChar() {
        return mc;
    }

    /**
     * Returns the character used to escape the wildcard functionality of a
     * wildcard character. If two escape characters are used in sequence, they
     * mean the escape character itself. It defaults to '\'.
     * @return Escape character.
     */
    public char getEscapeChar() {
        return ec;
    }

    /**
     * Makes pattern matching case insensitive.
     * @param caseSensitive false for case insensitivity. Default is case
     * sensitive match.
     */
    public void setCaseSensitive(boolean caseSensitive) {
        this.caseSensitive = caseSensitive;
    }

    /**
     * Returns the current state of case sensitivity.
     * @return true for case sensitive pattern matching, false otherwise.
     */
    public boolean getCaseSensitive() {
        return caseSensitive;
    }
    boolean preceededByMultipleChar = false;

    /**
     * Matches a string against a pattern with wildcards. Two wildcard types
     * are supported: single character match (defaults to '?') and ANY
     * character match ('*'), matching any count of characters including 0.
     * Wildcard characters may be escaped by an escape character, which
     * defaults to '\'.
     * @param s The string, in which the search should be performed.
     * @param pattern The search pattern string including wildcards.
     * @return true, if string 's' matches 'pattern'.
     */
    public boolean match(String s, String pattern) {
        preceededByMultipleChar = false;
        isEscaped = false;
        if (!caseSensitive) {
            pattern = pattern.toLowerCase();
            s = s.toLowerCase();
        }
        int offset = 0;

        while (true) {
            String ps = getNextSubString(pattern);
            int len = ps.length();
            pattern = pattern.substring(len + escCnt);

            if (len > 0 && isWildcard(ps.charAt(0)) && escCnt == 0) {
                offset = getWildcardOffset(ps.charAt(0));
                if (isSingleWildcardChar(ps.charAt(0))) {
                    s = s.substring(1);
// This is not yet enough: If a '*' precedes '?', 's' might be SHORTER
// than seen here, for this we need preceededByMultipleChar variable...
                }
                if (pattern.length() == 0) {
                    return s.length() <= offset || preceededByMultipleChar;
                }
            } else {
                int idx = s.indexOf(ps);
                if (idx < 0 || (idx > offset && !preceededByMultipleChar)) {
                    return false;
                }
                s = s.substring(idx + len);
                preceededByMultipleChar = false;
            }
            if (pattern.length() == 0) {
                return s.length() == 0;
            }
        }
    }
    private char sc = '?';
    private char mc = '*';
    private char ec = '\\'; // Escape character
    private boolean caseSensitive = true;
    private boolean isEscaped = false;
    private int escCnt = 0;

    private String getNextSubString(String pat) {
        escCnt = 0;
        if ("".equals(pat)) {
            return "";
        }
        if (isWildcard(pat.charAt(0))) {
            // if '?' is preceeded by '*', we need special considerations:
            if (pat.length() > 1 && !isSingleWildcardChar(pat.charAt(0)) && isSingleWildcardChar(pat.charAt(1))) {
                preceededByMultipleChar = true;
            }
            return pat.substring(0, 1);
        } else {
            String s = "";
            int i = 0;
            while (i < pat.length() && !isWildcard(pat.charAt(i), isEscaped)) {
                if (pat.charAt(i) == ec) {
                    isEscaped = !isEscaped;
                    if (!isEscaped) {
                        s += pat.charAt(i);
                    }
                    escCnt++;
                } else if (isWildcard(pat.charAt(i))) {
                    isEscaped = false;
                    s += pat.charAt(i);
                } else {
                    s += pat.charAt(i);
                }
                i++;
            }
            return s;
        }
    }

    private boolean isWildcard(char c, boolean isEsc) {
        return !isEsc && isWildcard(c);
    }

    private boolean isSingleWildcardChar(char c) {
        return c == sc;
    }

    private boolean isWildcard(char c) {
        return c == mc || c == sc;
    }

    private int getWildcardOffset(char c) {
        if (c == mc) {
            return Integer.MAX_VALUE;
        }
        return 0;
    }
}