Playfair cipher

You are encouraged to solve this task according to the task description, using any language you may know.
Task

Implement a  Playfair cipher for encryption and decryption.



The user must be able to choose   J  =  I     or   no   Q   in the alphabet.

The output of the encrypted and decrypted message must be in capitalized digraphs, separated by spaces.



Output example

                HI DE TH EG OL DI NT HE TR EX ES TU MP



import "/dynamic" for Enum
import "/str" for Str, Char
import "/trait" for Stepped
import "/ioutil" for Input
 
var PlayfairOption = Enum.create("PlayfairOption", ["NO_Q", "I_EQUALS_J"])
 
class Playfair {
    construct new(keyword, pfo) {
        _pfo = pfo
        // build_table
        _table = List.filled(5, null)
        for (i in 0..4) _table[i] = List.filled(5, "\0") // 5 * 5 char list
        var used = List.filled(26, false)
        if (_pfo == PlayfairOption.NO_Q) {
            used[16] = true // Q used
        } else {
            used[9]  = true // J used
        }
        var alphabet = Str.upper(keyword) + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        var i = 0
        var j = 0
        for (k in 0...alphabet.count) {
            var c = alphabet[k]
            if (Char.isAsciiUpper(c)) {
                var d = c.bytes[0] - 65
                if (!used[d]) {
                    _table[i][j] = c
                    used[d] = true
                    j = j + 1
                    if (j == 5) {
                        i = i + 1
                        if (i == 5) break // table has been filled
                        j = 0
                    }
                }
            }
        }
    }
 
    getCleanText_(plainText) {
        var plainText2 = Str.upper(plainText)  // ensure everything is upper case
        // get rid of any non-letters and insert X between duplicate letters
        var cleanText = ""
        var prevChar = "\0"  // safe to assume null character won't be present in plainText
        for (i in 0...plainText2.count) {
            var nextChar = plainText2[i]
            // It appears that Q should be omitted altogether if NO_Q option is specified - we assume so anyway
            if (Char.isAsciiUpper(nextChar) && (nextChar != "Q" || _pfo != PlayfairOption.NO_Q)) {
                // If I_EQUALS_J option specified, replace J with I
                if (nextChar == "J" && _pfo == PlayfairOption.I_EQUALS_J) nextChar = "I"
                if (nextChar != prevChar) {
                    cleanText = cleanText + nextChar
                } else {
                    cleanText = cleanText + "X" + nextChar
                }
                prevChar = nextChar
            }
        }
        var len = cleanText.count
        if (len % 2 == 1)  {  // dangling letter at end so add another letter to complete digram
            if (cleanText[-1] != "X") {
                cleanText = cleanText + "X"
            } else {
                cleanText = cleanText + "Z"
            }
        }
        return cleanText
    }
 
    findChar_(c) {
       for (i in 0..4) {
           for (j in 0..4) if (_table[i][j] == c) return [i, j]
       }
       return [-1, -1]
    }
 
    encode(plainText) {
        var cleanText = getCleanText_(plainText)
        var cipherText = ""
        var length = cleanText.count
        for (i in Stepped.new(0...length, 2)) {
            var pair = findChar_(cleanText[i])
            var row1 = pair[0]
            var col1 = pair[1]
            pair = findChar_(cleanText[i + 1])
            var row2 = pair[0]
            var col2 = pair[1]
            cipherText = cipherText +
                ((row1 == row2) ? _table[row1][(col1 + 1) % 5] +_table[row2][(col2 + 1) % 5] :
                 (col1 == col2) ? _table[(row1 + 1) % 5][col1] +_table[(row2 + 1) % 5][col2] :
                                  _table[row1][col2] +_table[row2][col1])
            if (i < length - 1) cipherText = cipherText + " "
        }
        return cipherText
    }
 
    decode(cipherText) {
        var decodedText = ""
        var length = cipherText.count
        for (i in Stepped.new(0...length, 3)) {  // cipherText will include spaces so we need to skip them
            var pair = findChar_(cipherText[i])
            var row1 = pair[0]
            var col1 = pair[1]
            pair = findChar_(cipherText[i + 1])
            var row2 = pair[0]
            var col2 = pair[1]
            decodedText = decodedText +
                ((row1 == row2) ? _table[row1][(col1 > 0) ? col1-1 : 4] +_table[row2][(col2 > 0) ?  col2-1 : 4] :
                 (col1 == col2) ? _table[(row1 > 0) ? row1-1 : 4][col1] +_table[(row2 > 0) ? row2-1 : 4][col2] :
                                  _table[row1][col2] +_table[row2][col1])
            if (i < length - 1) decodedText = decodedText + " "
        }
        return decodedText
    }
 
    printTable() {
        System.print("The_table to be used is :\n")
        for (i in 0..4) {
            for (j in 0..4) System.write(_table[i][j] + " ")
            System.print()
        }
    }
}
 
var keyword = Input.text("Enter Playfair keyword : ", 1)
var ignoreQ = Str.lower(Input.option("Ignore Q when building_table y/n : ", "yYnN"))
var pfo = (ignoreQ == "y") ? PlayfairOption.NO_Q : PlayfairOption.I_EQUALS_J
var playfair = Playfair.new(keyword, pfo)
playfair.printTable()
var plainText = Input.text("\nEnter plain text : ")
var encodedText = playfair.encode(plainText)
System.print("\nEncoded text is : %(encodedText)")
var decodedText = playfair.decode(encodedText)
System.print("Decoded text is : %(decodedText)")