Create a password generation program which will generate passwords containing random ASCII characters from the following groups:

         lower-case letters:  a ──► z
         upper-case letters:  A ──► Z
                     digits:  0 ──► 9
 other printable characters:  !"#$%&'()*+,-./:;<=>?@[]^_{|}~ 
 (the above character list excludes white-space, backslash and grave) 



The generated password(s) must include   at least one   (of each of the four groups):

   lower-case letter, 
   upper-case letter,
   digit  (numeral),   and 
   one  "other"  character. 



The user must be able to specify the password length and the number of passwords to generate.

The passwords should be displayed or written to a file, one per line.

The randomness should be from a system source or library.

The program should implement a help option or button which should describe the program and options when invoked.

You may also allow the user to specify a seed value, and give the option of excluding visually similar characters.

For example:            Il1     O0     5S     2Z            where the characters are:

   capital eye, lowercase ell, the digit one
   capital oh, the digit zero 
   the digit five, capital ess
   the digit two, capital zee


// version 1.1.4-3
 
import java.util.Random
import java.io.File
 
val r = Random()
val rr = Random()  // use a separate generator for shuffles
val ls = System.getProperty("line.separator") 
 
var lower = "abcdefghijklmnopqrstuvwxyz"
var upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
var digit = "0123456789"
var other = """!"#$%&'()*+,-./:;<=>?@[]^_{|}~"""
 
val exclChars = arrayOf(
    "'I', 'l' and '1'", 
    "'O' and '0'     ",
    "'5' and 'S'     ",
    "'2' and 'Z'     "
)
 
fun String.shuffle(): String {
    val sb = StringBuilder(this)
    var n = sb.length
    while (n > 1) {
        val k = rr.nextInt(n--)
        val t = sb[n]
        sb[n] = sb[k]
        sb[k] = t
    }
    return sb.toString()
}
 
fun generatePasswords(pwdLen: Int, pwdNum: Int, toConsole: Boolean, toFile: Boolean) {
    val sb = StringBuilder()
    val ll = lower.length
    val ul = upper.length
    val dl = digit.length
    val ol = other.length
    val tl = ll + ul + dl + ol 
    var fw = if (toFile) File("pwds.txt").writer() else null
 
    if (toConsole) println("\nThe generated passwords are:")
    for (i in 0 until pwdNum) {
        sb.setLength(0)
        sb.append(lower[r.nextInt(ll)])
        sb.append(upper[r.nextInt(ul)])
        sb.append(digit[r.nextInt(dl)])
        sb.append(other[r.nextInt(ol)])
 
        for (j in 0 until pwdLen - 4) {
            val k = r.nextInt(tl)
            sb.append(when (k) {
                in 0 until ll -> lower[k]
                in ll until ll + ul -> upper[k - ll]
                in ll + ul until tl - ol -> digit[k - ll - ul]
                else -> other[tl - 1 - k]
            })
        }
        var pwd = sb.toString()
        repeat(5) { pwd = pwd.shuffle() } // shuffle 5 times say
        if (toConsole) println("  ${"%2d".format(i + 1)}:  $pwd")
        if (toFile) {
            fw!!.write(pwd)
            if (i < pwdNum - 1) fw.write(ls)
        }
    }
    if (toFile) {
       println("\nThe generated passwords have been written to the file pwds.txt") 
       fw!!.close()
    } 
}
 
fun printHelp() {
    println(""" 
        |This program generates up to 99 passwords of between 5 and 20 characters in 
        |length.
        |
        |You will be prompted for the values of all parameters when the program is run 
        |- there are no command line options to memorize.
        |
        |The passwords can either be written to the console or to a file (pwds.txt), 
        |or both.
        |
        |The passwords must contain at least one each of the following character types:
        |   lower-case letters :  a -> z
        |   upper-case letters :  A -> Z
        |   digits             :  0 -> 9
        |   other characters   :  !"#$%&'()*+,-./:;<=>?@[]^_{|}~
        |
        |Optionally, a seed can be set for the random generator 
        |(any non-zero Long integer) otherwise the default seed will be used. 
        |Even if the same seed is set, the passwords won't necessarily be exactly
        |the same on each run as additional random shuffles are always performed.
        |
        |You can also specify that various sets of visually similar characters
        |will be excluded (or not) from the passwords, namely: Il1  O0  5S  2Z
        | 
        |Finally, the only command line options permitted are -h and -help which
        |will display this page and then exit.
        |
        |Any other command line parameters will simply be ignored and the program
        |will be run normally.
        |
    """.trimMargin())
}   
 
fun main(args: Array<String>) {
    if (args.size == 1 && (args[0] == "-h" || args[0] == "-help")) {
       printHelp()
       return
    }
 
    println("Please enter the following and press return after each one")
 
    var pwdLen: Int?
    do {
       print("  Password length (5 to 20)     : ")
       pwdLen = readLine()!!.toIntOrNull() ?: 0       
    }
    while (pwdLen !in 5..20)
 
    var pwdNum: Int?
    do {
       print("  Number to generate (1 to 99)  : ")
       pwdNum = readLine()!!.toIntOrNull() ?: 0       
    }
    while (pwdNum !in 1..99)
 
    var seed: Long?
    do {
       print("  Seed value (0 to use default) : ")
       seed = readLine()!!.toLongOrNull()       
    }
    while (seed == null)
    if (seed != 0L) r.setSeed(seed)
 
    println("  Exclude the following visually similar characters")
    for (i in 0..3) {
        var yn: String
        do {
            print("    ${exclChars[i]} y/n : ")
            yn = readLine()!!.toLowerCase()
        }
        while (yn != "y" && yn != "n")
        if (yn == "y") {
            when (i) {
                0 -> {
                    upper = upper.replace("I", "")
                    lower = lower.replace("l", "")
                    digit = digit.replace("1", "")
                }
 
                1 -> {
                    upper = upper.replace("O", "")
                    digit = digit.replace("0", "")
                }
 
                2 -> {
                    upper = upper.replace("S", "")
                    digit = digit.replace("5", "")
                }
 
                3 -> {
                    upper = upper.replace("Z", "")
                    digit = digit.replace("2", "")
                }
            }
        }
    }
 
    var toConsole: Boolean?
    do {
        print("  Write to console   y/n : ")
        val t = readLine()!!
        toConsole = if (t == "y") true else if (t == "n") false else null
    }
    while (toConsole == null)
 
    var toFile: Boolean? = true
    if (toConsole) {
        do {
            print("  Write to file      y/n : ")
            val t = readLine()!!
            toFile = if (t == "y") true else if (t == "n") false else null
        }
        while (toFile == null)
    }
 
    generatePasswords(pwdLen!!, pwdNum!!, toConsole, toFile!!)
}