import random


def gen(n=12, flags={}, forbidden=""):
    """Generate a random string of characters for use as a secure password.

    Inputs
    n: Length of the string to generate.
    flags:
       digits: Include at least one digit in the string.
       lower: Include at least one lowercase letter in the string.
       upper: Include at least one uppercase letter in the string.
       special: Include at least one common special character in the string.
       more: Include at least one of other special characters in the string.
       ReduceConfusion: Remove "01l|O" characters.
    forbidden: Any characters that should not be in the final string.

    Outputs
    A string of random ACSII characters.
    """

    # Create the random number generator
    rng = random.SystemRandom()

    # This variable will hold a string of potential characters from which to
    # make the password.
    ascii_chars = "";

    # Digits
    ascii_digits = '0123456789'
    if flags['digits']:
        ascii_chars += ascii_digits

    # Uppercase Letters
    ascii_uppers = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    if flags['upper']:
        ascii_chars += ascii_uppers

    # Lowercase Letters
    ascii_lowers = "abcdefghijklmnopqrstuvwxyz"
    if flags['lower']:
        ascii_chars += ascii_lowers

    # Special Characters (Common)
    ascii_special = "_=+!@#$%&*?-"
    if flags['special']:
        ascii_chars += ascii_special

    # More special Characters (Less Common)
    ascii_more = r"""`|^\/~<>'",.(){}[];:"""
    if flags['more']:
        ascii_chars += ascii_more

    # Remove Confusion (no 1's, 0's, l's, |'s or O's)
    if flags['ReduceConfusion']:
        ascii_chars = ascii_chars.replace("l", "")
        ascii_chars = ascii_chars.replace("O", "")
        ascii_chars = ascii_chars.replace("1", "")
        ascii_chars = ascii_chars.replace("0", "")
        ascii_chars = ascii_chars.replace("|", "")

    # Remove any characters specified as forbidden by the user
    ascii_chars = [c for c in ascii_chars if c not in forbidden]

    def meetsCriteria(candidate):
        """Ensure the candidate password contains characters from each desired
        set of ASCII characters."""
        met = True
        if flags['digits']:
            if len(set(candidate) & set(ascii_digits)) < 1:
                met = False
        if flags['upper']:
            if len(set(candidate) & set(ascii_uppers)) < 1:
                met = False
        if flags['lower']:
            if len(set(candidate) & set(ascii_lowers)) < 1:
                met = False
        if flags['special']:
            if len(set(candidate) & set(ascii_special)) < 1:
                met = False
        if flags['more']:
            if len(set(candidate) & set(ascii_more)) < 1:
                met = False
        return met

    # Generate the random password and ensure it meets the criteria (contains
    # characters from each specified group).
    pw = ""
    while not meetsCriteria(pw):
        pw = ""
        for count in range(0,n):
            # Randomly select a character from the list
            ridx = rng.randint(0, len(ascii_chars) - 1)
            pw += ascii_chars[ridx]

    return pw