package railo.commons.io.res.util;

import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;


/**
 * a WildcardPattern that accepts a comma- (or semi-colon-) separated value of patterns, e.g. "*.gif, *.jpg, *.jpeg, *.png"
 * and an optional isExclude boolean value which negates the results of the default implementation
 * 
 * also, lines 31 - 35 allow to set isExclude to true by passing a pattern whose first character is an exclamation point '!'
 * 
 * @author Igal
 */
public class WildcardPattern {

    private final String pattern;
    private final boolean isInclude;
    
    private final List<ParsedPattern> patterns;
    
    /**
     * 
     * @param pattern - the wildcard pattern, or a comma/semi-colon separated value of wildcard patterns
     * @param isCaseSensitive - if true, does a case-sensitive matching
     * @param isExclude - if true, the filter becomes an Exclude filter so that only items that do not match the pattern are accepted
     */
    public WildcardPattern( String pattern, boolean isCaseSensitive, boolean isExclude ) {

    	if ( pattern.charAt( 0 ) == '!' ) {		// set isExclude to true if the first char of pattern is an exclamation point '!'
    		
    		pattern = pattern.substring( 1 );
    		isExclude = true;
    	}
    	
        this.pattern = pattern;
        this.isInclude = !isExclude;
        
        StringTokenizer tokenizer = new StringTokenizer( pattern, ",;|" );
        
        patterns = new ArrayList<ParsedPattern>();
        
        while ( tokenizer.hasMoreTokens() ) {
            
            String token = tokenizer.nextToken().trim();
            
            if ( !token.isEmpty() )
                patterns.add( new ParsedPattern( token, isCaseSensitive ) );
        }
    }
    
    
    /** calls this( pattern, isCaseSensitive, false ); */
    public WildcardPattern( String pattern, boolean isCaseSensitive ) {
    
        this( pattern, isCaseSensitive, false );
    }
    
    
    public boolean isMatch( String input ) {
        
        for ( ParsedPattern pp : this.patterns ) {
            
            boolean match = pp.isMatch( input );
            
            if ( match )
                return isInclude;
        }
        
        return !isInclude;
    }
    
    
    public String toString() {
        
        return "WildcardPattern: " + pattern;
    }

    public static class ParsedPattern {

        public final static String MATCH_ANY = "*";
        public final static String MATCH_ONE = "?";

        private List<String> parts;
        private final boolean isCaseSensitive;

        private final boolean isLastPartMatchAny;
        

        public ParsedPattern( String pattern, boolean isCaseSensitive ) {

            this.isCaseSensitive = isCaseSensitive;

            if ( !isCaseSensitive )
                pattern = pattern.toLowerCase();

            parts = new ArrayList<String>();

            int len = pattern.length();

            int subStart = 0;

            for ( int i=subStart; i<len; i++ ) {

                char c = pattern.charAt( i );

                if ( c == '*' || c == '?' ) {

                    if ( i > subStart )
                        parts.add( pattern.substring( subStart, i ) );

                    parts.add( c == '*' ? MATCH_ANY : MATCH_ONE );

                    subStart = i + 1;
                }
            }

            if ( len > subStart ) {

                parts.add( pattern.substring( subStart ) );
            }

            isLastPartMatchAny = ( parts.get( parts.size() - 1 ) == MATCH_ANY );
        }


        /** calls this( pattern, false, false ); */
        public ParsedPattern( String pattern ) {

            this( pattern, false );
        }


        /** tests if the input string matches the pattern */
        public boolean isMatch( String input ) {

            if ( !isCaseSensitive )
                input = input.toLowerCase();

            int pos = 0;
            int len = input.length();

            boolean doMatchAny = false;

            for ( String part : parts ) {

                if ( part == MATCH_ANY ) {

                    doMatchAny = true;
                    continue;
                }

                if ( part == MATCH_ONE ) {

                    doMatchAny = false;
                    pos++;
                    continue;
                }

                int ix = input.indexOf( part, pos );

                if ( ix == -1 )
                    return false;

                if ( !doMatchAny && ix != pos )
                    return false;

                pos = ix + part.length();
                doMatchAny = false;
            }

            if ( !isLastPartMatchAny && ( len != pos ) )        // if pattern doesn't end with * then we shouldn't have any more characters in input
                return false;

            return true;
        }


        @Override
        public String toString() {

            StringBuilder sb = new StringBuilder();

            for ( String s : parts ) {

                sb.append( s );
                sb.append( ':' );
            }

            if ( sb.length() > 0 )
                sb.setLength( sb.length() - 1 );

            return "[" + sb.toString() + "]";
        }
    }
}

