/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * 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 android.text;

/**
 * Abstract class for filtering login-related text (user names and passwords)
 * 
 */
public abstract class LoginFilter implements InputFilter {
    private boolean mAppendInvalid;  // whether to append or ignore invalid characters
    /**
     * Base constructor for LoginFilter
     * @param appendInvalid whether or not to append invalid characters.
     */
    LoginFilter(boolean appendInvalid) {
        mAppendInvalid = appendInvalid;
    }
    
    /**
     * Default constructor for LoginFilter doesn't append invalid characters.
     */
    LoginFilter() {
        mAppendInvalid = false;
    }
    
    /**
     * This method is called when the buffer is going to replace the
     * range <code>dstart &hellip; dend</code> of <code>dest</code>
     * with the new text from the range <code>start &hellip; end</code>
     * of <code>source</code>.  Returns the CharSequence that we want
     * placed there instead, including an empty string
     * if appropriate, or <code>null</code> to accept the original
     * replacement.  Be careful to not to reject 0-length replacements,
     * as this is what happens when you delete text.
     */
    public CharSequence filter(CharSequence source, int start, int end,
            Spanned dest, int dstart, int dend) {
        char[] out = new char[end - start]; // reserve enough space for whole string
        int outidx = 0;
        boolean changed = false;
        
        onStart();
        
        // Scan through beginning characters in dest, calling onInvalidCharacter() 
        // for each invalid character.
        for (int i = 0; i < dstart; i++) {
            char c = dest.charAt(i);
            if (!isAllowed(c)) onInvalidCharacter(c);
        }

        // Scan through changed characters rejecting disallowed chars
        for (int i = start; i < end; i++) {
            char c = source.charAt(i);
            if (isAllowed(c)) {
                // Character allowed. Add it to the sequence.
                out[outidx++] = c;
            } else {
                if (mAppendInvalid) out[outidx++] = c;
                else changed = true; // we changed the original string
                onInvalidCharacter(c);
            }
        }
        
        // Scan through remaining characters in dest, calling onInvalidCharacter() 
        // for each invalid character.
        for (int i = dend; i < dest.length(); i++) {
            char c = dest.charAt(i);
            if (!isAllowed(c)) onInvalidCharacter(c);
        }
        
        onStop();

        if (!changed) {
            return null;
        }
        
        String s = new String(out, 0, outidx);
        
        if (source instanceof Spanned) {
            SpannableString sp = new SpannableString(s);
            TextUtils.copySpansFrom((Spanned) source,
                                    start, end, null, sp, 0);
            return sp;
        } else {
            return s;
        }
    }
    
    /**
     * Called when we start processing filter.
     */
    public void onStart() {
        
    }
    
    /**
     * Called whenever we encounter an invalid character.
     * @param c the invalid character
     */
    public void onInvalidCharacter(char c) {
        
    }
    
    /**
     * Called when we're done processing filter
     */
    public void onStop() {
        
    }
    
    /**
     * Returns whether or not we allow character c. 
     * Subclasses must override this method.
     */
    public abstract boolean isAllowed(char c);

    /**
     * This filter rejects characters in the user name that are not compatible with GMail 
     * account creation. It prevents the user from entering user names with characters other than 
     * [a-zA-Z0-9.]. 
     * 
     */
    public static class UsernameFilterGMail extends LoginFilter {
        
        public UsernameFilterGMail() {
            super(false);
        }
        
        public UsernameFilterGMail(boolean appendInvalid) {
            super(appendInvalid);
        }
        
        @Override
        public boolean isAllowed(char c) {
            // Allow [a-zA-Z0-9@.]
            if ('0' <= c && c <= '9')
                return true;
            if ('a' <= c && c <= 'z')
                return true;
            if ('A' <= c && c <= 'Z')
                return true;
            if ('.' == c)
                return true;
            return false;
        }
    }

    /**
     * This filter rejects characters in the user name that are not compatible with Google login.
     * It is slightly less restrictive than the above filter in that it allows [a-zA-Z0-9._-]. 
     * 
     */
    public static class UsernameFilterGeneric extends LoginFilter {
        private static final String mAllowed = "@_-."; // Additional characters
        
        public UsernameFilterGeneric() {
            super(false);
        }
        
        public UsernameFilterGeneric(boolean appendInvalid) {
            super(appendInvalid);
        }
        
        @Override
        public boolean isAllowed(char c) {
            // Allow [a-zA-Z0-9@.]
            if ('0' <= c && c <= '9')
                return true;
            if ('a' <= c && c <= 'z')
                return true;
            if ('A' <= c && c <= 'Z')
                return true;
            if (mAllowed.indexOf(c) != -1)
                return true;
            return false;
        }
    }

    /**
     * This filter is compatible with GMail passwords which restricts characters to 
     * the Latin-1 (ISO8859-1) char set.
     * 
     */
    public static class PasswordFilterGMail extends LoginFilter {
        
        public PasswordFilterGMail() {
            super(false);
        }
        
        public PasswordFilterGMail(boolean appendInvalid) {
            super(appendInvalid);
        }
        
        // We should reject anything not in the Latin-1 (ISO8859-1) charset
        @Override
        public boolean isAllowed(char c) {
            if (32 <= c && c <= 127)
                return true; // standard charset
            // if (128 <= c && c <= 159) return true;  // nonstandard (Windows(TM)(R)) charset
            if (160 <= c && c <= 255)
                return true; // extended charset
            return false;
        }
    }
}
