#!/usr/bin/ruby

#
## https://rosettacode.org/wiki/Playfair_cipher
#

func playfair(key, from = 'J', to = (from == 'J' ? 'I' : '')) {

    func canon(str) {
        str.gsub(/[^[:alpha:]]/, '').uc.gsub(from, to)
    }

    var m = canon(key + ('A'..'Z' -> join)).chars.uniq.slices(5)

    var :ENC = gather {
        m.each { |r|
            for i,j in (^r ~X ^r) {
                i == j && next
                take(Pair("#{r[i]}#{r[j]}", "#{r[(i+1)%5]}#{r[(j+1)%5]}"))
            }
        }

        ^5 -> each { |k|
            var c = m.map {|a| a[k] }
            for i,j in (^c ~X ^c) {
                i == j && next
                take(Pair("#{c[i]}#{c[j]}", "#{c[(i+1)%5]}#{c[(j+1)%5]}"))
            }
        }

        cartesian([^5, ^5, ^5, ^5], {|i1,j1,i2,j2|
            i1 == i2 && next
            j1 == j2 && next
            take(Pair("#{m[i1][j1]}#{m[i2][j2]}", "#{m[i1][j2]}#{m[i2][j1]}"))
        })
    }.map { (.key, .value) }...

    var DEC = ENC.flip

    func enc(red) {
        gather {
            var str = canon(red)
            while (var m = (str =~ /(.)(?(?=\1)|(.?))/g)) {
                take("#{m[0]}#{m[1] == '' ? 'X' : m[1]}")
            }
        }.map { ENC{_} }.join(' ')
    }

    func dec(black) {
        canon(black).split(2).map { DEC{_} }.join(' ')
    }

    return(enc, dec)
}

var (encode, decode) = playfair('Playfair example')

var orig = "Hide the gold in...the TREESTUMP!!!"
say " orig:\t#{orig}"

var black = encode(orig)
say "black:\t#{black}"

var red = decode(black)
say "  red:\t#{red}"

assert_eq(black, "BM OD ZB XD NA BE KU DM UI XM MO UV IF")
assert_eq(red, "HI DE TH EG OL DI NT HE TR EX ES TU MP")
