import java.io.File;
import java.util.ArrayList;
import java.util.Scanner;

public class Problem_2_Password_Strength {

    static int score; // to be modified by methods

    public static void main(String[] args) {

        try {
            Scanner file = new Scanner(new File("C:\\Users\\Mike\\Desktop\\problem_2_password_strength_DATA20.txt"));
            ArrayList<String> list = new ArrayList<String>();
            
            // read in the file to the list
            while (file.hasNextLine()) {
                list.add(file.nextLine());
            }

            for (String password : list) {
                score = 0; // reset it each password
                score += password.length() * 4;
                
                // call methods to alter the score based on the password grading
                
                basicRequirements(password);
                counts(password);
                
                if (password.matches("[0-9]+")) // regex; only digits
                    score -= password.length(); // -1 for each digit
                if (password.matches("[a-zA-Z]+")) // regex; only letters
                    score -= password.length(); // -1 for each letter
                
                middles(password);
                
                // check consecutive types for each (uppercase letters, lowercase letters and digits)
                consecutiveTypes(password, "[0-9]"); // or consecutiveTypes(password, "\\d");
                consecutiveTypes(password, "[a-z]");
                consecutiveTypes(password, "[A-Z]");
                
                sequentialTypes(password);
                
                // negative score goes to 0, score > 100 goes to 100
                if (score < 0) score = 0;
                if (score > 100) score = 100;
                
                // display output based on intervals
                if (score >= 0 && score <= 20) System.out.println("Very Weak (score = " + score + ")");
                if (score >= 21 && score <= 40) System.out.println("Weak (score = " + score + ")");
                if (score >= 41 && score <= 60) System.out.println("Good (score = " + score + ")");
                if (score >= 61 && score <= 80) System.out.println("Strong (score = " + score + ")");
                if (score >= 81 && score <= 100) System.out.println("Very Strong (score = " + score + ")");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void middles(String password) {
        for (int i = 1; i < password.length() - 1; i++) { // for every character in password string
            String c = password.substring(i, i+1); // current character in password
            if (c.matches("[^a-zA-Z]")) { // not a letter, so a digit or symbol
                score += 2;
            }
        }
    }
    
    /* parameter s is the password string
       and regex is the type to check. 
       ex: digits would be "[0-9]" or "\\d" */
    public static void consecutiveTypes(String s, String regex) {

        // ArrayList that holds the length of consecutive set (as element) for each consecutive set (each set its own element)
        ArrayList<Integer> type = new ArrayList<Integer>();
        
        for (int i = 0; i < s.length()-1; i++) { // for every character in password

            int count = 1; // length of consecutive set
            String s1 = s.substring(i,i+1), s2 = s.substring(i+1,i+2); // adjacent characters
            
            // while loop to keep iterating through a consecutive set
            while (s1.matches(regex) && s2.matches(regex)) { // if adjacent are both the same type
                count++; // length of consecutive set increases
                i++; // move over one index in the password because we've counted it in the consecutive set
                
                // can't check any more adjacent characters because we're at the end of the password, so break
                if (i > s.length() - 2) {
                    break;
                }
                // reset the adjacent characters
                s1 = s.substring(i,i+1);
                s2 = s.substring(i+1,i+2);
            }
            type.add(count); // add length of consecutive set to list once the while loop is done (consecutive set is over)
        }
        
        // update the score
        for (int length : type) { // for every number length of consecutive set of every consecutive set
            score -= 2 * (length - 1);
        }
    }

    public static void sequentialTypes(String s) {

        // ArrayLists that hold the length of sequential sets (as element) for each sequential set (each set its own element)
        ArrayList<Integer> letters = new ArrayList<Integer>(), digits = new ArrayList<Integer>();
        
        for (int i = 0; i < s.length()-1; i++) { // for every character in password

            int count = 1; // length of sequential set
            char c1 = s.charAt(i), c2 = s.charAt(i+1); // adjacent characters
            
            // check if sequence should be increasing or decreasing (difference in ASCII values positive or negative)
            int upOrDown;
            if ((int)(c1) - (int)(c2) < 0) upOrDown = -1;
            else upOrDown = 1;
            
            // while loop to keep iterating through a consecutive set
            // both characters are digits; and the difference between the two ASCII values is 1 (they're sequential) and following the sequential trend
            while (String.valueOf(c1).matches("[0-9]") && String.valueOf(c2).matches("[0-9]") && (int)(c1) - (int)(c2) == upOrDown) {
                count++; // length of sequential set increases
                i++; // move over one index in the password because we've counted it in the sequential set
                
                // can't check any more adjacent characters because we're at the end of the password, so break
                if (i > s.length() - 2) {
                    break;
                }
                
                // reset the adjacent characters
                c1 = s.charAt(i);
                c2 = s.charAt(i+1);
            }
            digits.add(count); // add length of sequential set to list once the while loop is done (sequential set is over)
        }
        
        // do the same thing as the above for loop (with digit type), but instead with lowercase letters and then again with uppercase letters
        // but add the sequential lengths to the same list because we are checking sequential LETTERS, so the longest
        // sequential set length needs to include both lowercase and uppercase for the letters
        for (int i = 0; i < s.length()-1; i++) {
            
            int count = 1;
            char c1 = s.charAt(i), c2 = s.charAt(i+1);
            
            int upOrDown;
            if ((int)(c1) - (int)(c2) < 0) upOrDown = -1;
            else upOrDown = 1;
            
            while (String.valueOf(c1).matches("[a-z]") && String.valueOf(c2).matches("[a-z]") && (int)(c1) - (int)(c2) == upOrDown) {
                count++;
                i++;
                
                if (i > s.length() - 2) {
                    break;
                }
                
                c1 = s.charAt(i);
                c2 = s.charAt(i+1);
            }
            letters.add(count);
        }
        for (int i = 0; i < s.length()-1; i++) {
            
            int count = 1;
            char c1 = s.charAt(i), c2 = s.charAt(i+1);
            
            int upOrDown;
            if ((int)(c1) - (int)(c2) < 0) upOrDown = -1;
            else upOrDown = 1;
            
            while (String.valueOf(c1).matches("[A-Z]") && String.valueOf(c2).matches("[A-Z]") && (int)(c1) - (int)(c2) == upOrDown) {
                count++;
                i++;
                
                if (i > s.length() - 2) {
                    break;
                }
                
                c1 = s.charAt(i);
                c2 = s.charAt(i+1);
            }
            letters.add(count);
        }
        
        int max = 2; // initialize to 2 just incase there is no other max found, this way the default is less than what can be updated to the score
        for (int i : digits) { // for every sequential digit set length
            if (i > max) { // if this length is greater than the previous max
                max = i; // this is the new max
            }
        }
        if (max > 2) { // if length is longer than 2 (has to be a sequence of at least 3 in length)
            score -= 3 * (max - 2); // update score
        }
        
        // do the same thing as above except now with every sequential LETTER set length
        max = 2;
        for (int i : letters) {
            if (i > max) {
                max = i;
            }
        }
        if (max > 2) {
            score -= 3 * (max - 2);
        }
    }

    public static void counts(String password) {
        // counts of each
        int uppers = 0, lowers = 0, digits = 0, symbols = 0;
        
        for (int i = 0; i < password.length(); i++) { // for every character in password
            
            String c = password.substring(i,i+1); // current character in password
            
            // count occurrences
            if (c.matches("[a-z]")) { // lowercase
                lowers++;
            } else if (c.matches("[A-Z]")) { // uppercase
                uppers++;
            } else if (c.matches("[0-9]")) { // digits
                digits++;
            } else if (c.matches("[^a-zA-Z0-9]")) { // symbols
                symbols++;
            }
        }
        
        if (uppers > 0) {
            score += (password.length() - uppers) * 2;
        }
        if (lowers > 0) {
            score += (password.length() - lowers) * 2;
        }
        if (digits < password.length()) {
            score += 4 * digits;
        }
        score += 6 * symbols;
    }

    /* 8 length
     * 3 of: uppercase, lowercase, symbol, digit
     * digits: 48-57
     */
    public static void basicRequirements(String password) {
        
        boolean longEnough = false;
        if (password.length() > 7) 
            longEnough = true;

        int types = 0;

        boolean digit = false, uppercase = false, lowercase = false, symbol = false;

        for (int i = 0; i < password.length(); i++) { // for every character in password string
            
            // current character in password string
            String s = password.substring(i,i+1);
            char ch = password.charAt(i);
            
            if (s.matches("[0-9]")) // regex
            //if (Character.isDigit(ch)) // Character class
            //if (ch >= 48 && ch <= 57) // ASCII value
                digit = true;
            else if (s.matches("[A-Z]")) // regex
            //else if (Character.isUpperCase(ch)) // Character class
            //else if (ch >= 65 && ch <= 90) // ASCII value
                uppercase = true; // 
            else if (s.matches("[a-z]")) // regex
            //else if (Character.isLowerCase(ch)) // Character class
            //else if (ch >= 97 && ch <= 122) // ASCII value
                lowercase = true;
            else if (s.matches("[^a-zA-Z0-9]")) // regex
            //else if (!Character.isLetterOrDigit(ch)) // Character class
            //else if (ch < 48 || (ch > 57 && ch < 65) || (ch < 97 && ch > 90) || ch > 122) // ASCII value
            //else
                symbol = true;
        }

        if (symbol) types++;
        if (digit) types++;
        if (uppercase) types++;
        if (lowercase) types++;

        boolean qualify = false;
        if (types >= 3 && longEnough) 
            qualify = true;

        if (qualify) {
            score += 2 + types * 2;
        }
    }
}