export isiban

function isiban(str::AbstractString)::Bool
    ibanReg = Dict([
        ("AD", r"^(AD[0-9]{2})\d{8}[A-Z0-9]{12}$")
        ("AE", r"^(AE[0-9]{2})\d{3}\d{16}$")
        ("AL", r"^(AL[0-9]{2})\d{8}[A-Z0-9]{16}$")
        ("AT", r"^(AT[0-9]{2})\d{16}$")
        ("AZ", r"^(AZ[0-9]{2})[A-Z0-9]{4}\d{20}$")
        ("BA", r"^(BA[0-9]{2})\d{16}$")
        ("BE", r"^(BE[0-9]{2})\d{12}$")
        ("BG", r"^(BG[0-9]{2})[A-Z]{4}\d{6}[A-Z0-9]{8}$")
        ("BH", r"^(BH[0-9]{2})[A-Z]{4}[A-Z0-9]{14}$")
        ("BR", r"^(BR[0-9]{2})\d{23}[A-Z]{1}[A-Z0-9]{1}$")
        ("BY", r"^(BY[0-9]{2})[A-Z0-9]{4}\d{20}$")
        ("CH", r"^(CH[0-9]{2})\d{5}[A-Z0-9]{12}$")
        ("CR", r"^(CR[0-9]{2})\d{18}$")
        ("CY", r"^(CY[0-9]{2})\d{8}[A-Z0-9]{16}$")
        ("CZ", r"^(CZ[0-9]{2})\d{20}$")
        ("DE", r"^(DE[0-9]{2})\d{18}$")
        ("DK", r"^(DK[0-9]{2})\d{14}$")
        ("DO", r"^(DO[0-9]{2})[A-Z]{4}\d{20}$")
        ("EE", r"^(EE[0-9]{2})\d{16}$")
        ("EG", r"^(EG[0-9]{2})\d{25}$")
        ("ES", r"^(ES[0-9]{2})\d{20}$")
        ("FI", r"^(FI[0-9]{2})\d{14}$")
        ("FO", r"^(FO[0-9]{2})\d{14}$")
        ("FR", r"^(FR[0-9]{2})\d{10}[A-Z0-9]{11}\d{2}$")
        ("GB", r"^(GB[0-9]{2})[A-Z]{4}\d{14}$")
        ("GE", r"^(GE[0-9]{2})[A-Z0-9]{2}\d{16}$")
        ("GI", r"^(GI[0-9]{2})[A-Z]{4}[A-Z0-9]{15}$")
        ("GL", r"^(GL[0-9]{2})\d{14}$")
        ("GR", r"^(GR[0-9]{2})\d{7}[A-Z0-9]{16}$")
        ("GT", r"^(GT[0-9]{2})[A-Z0-9]{4}[A-Z0-9]{20}$")
        ("HR", r"^(HR[0-9]{2})\d{17}$")
        ("HU", r"^(HU[0-9]{2})\d{24}$")
        ("IE", r"^(IE[0-9]{2})[A-Z0-9]{4}\d{14}$")
        ("IL", r"^(IL[0-9]{2})\d{19}$")
        ("IQ", r"^(IQ[0-9]{2})[A-Z]{4}\d{15}$")
        ("IR", r"^(IR[0-9]{2})0\d{2}0\d{18}$")
        ("IS", r"^(IS[0-9]{2})\d{22}$")
        ("IT", r"^(IT[0-9]{2})[A-Z]{1}\d{10}[A-Z0-9]{12}$")
        ("JO", r"^(JO[0-9]{2})[A-Z]{4}\d{22}$")
        ("KW", r"^(KW[0-9]{2})[A-Z]{4}[A-Z0-9]{22}$")
        ("KZ", r"^(KZ[0-9]{2})\d{3}[A-Z0-9]{13}$")
        ("LB", r"^(LB[0-9]{2})\d{4}[A-Z0-9]{20}$")
        ("LC", r"^(LC[0-9]{2})[A-Z]{4}[A-Z0-9]{24}$")
        ("LI", r"^(LI[0-9]{2})\d{5}[A-Z0-9]{12}$")
        ("LT", r"^(LT[0-9]{2})\d{16}$")
        ("LU", r"^(LU[0-9]{2})\d{3}[A-Z0-9]{13}$")
        ("LV", r"^(LV[0-9]{2})[A-Z]{4}[A-Z0-9]{13}$")
        ("MC", r"^(MC[0-9]{2})\d{10}[A-Z0-9]{11}\d{2}$")
        ("MD", r"^(MD[0-9]{2})[A-Z0-9]{20}$")
        ("ME", r"^(ME[0-9]{2})\d{18}$")
        ("MK", r"^(MK[0-9]{2})\d{3}[A-Z0-9]{10}\d{2}$")
        ("MR", r"^(MR[0-9]{2})\d{23}$")
        ("MT", r"^(MT[0-9]{2})[A-Z]{4}\d{5}[A-Z0-9]{18}$")
        ("MU", r"^(MU[0-9]{2})[A-Z]{4}\d{19}[A-Z]{3}$")
        ("MZ", r"^(MZ[0-9]{2})\d{21}$")
        ("NL", r"^(NL[0-9]{2})[A-Z]{4}\d{10}$")
        ("NO", r"^(NO[0-9]{2})\d{11}$")
        ("PK", r"^(PK[0-9]{2})[A-Z0-9]{4}\d{16}$")
        ("PL", r"^(PL[0-9]{2})\d{24}$")
        ("PS", r"^(PS[0-9]{2})[A-Z0-9]{4}\d{21}$")
        ("PT", r"^(PT[0-9]{2})\d{21}$")
        ("QA", r"^(QA[0-9]{2})[A-Z]{4}[A-Z0-9]{21}$")
        ("RO", r"^(RO[0-9]{2})[A-Z]{4}[A-Z0-9]{16}$")
        ("RS", r"^(RS[0-9]{2})\d{18}$")
        ("SA", r"^(SA[0-9]{2})\d{2}[A-Z0-9]{18}$")
        ("SC", r"^(SC[0-9]{2})[A-Z]{4}\d{20}[A-Z]{3}$")
        ("SE", r"^(SE[0-9]{2})\d{20}$")
        ("SI", r"^(SI[0-9]{2})\d{15}$")
        ("SK", r"^(SK[0-9]{2})\d{20}$")
        ("SM", r"^(SM[0-9]{2})[A-Z]{1}\d{10}[A-Z0-9]{12}$")
        ("SV", r"^(SV[0-9]{2})[A-Z0-9]{4}\d{20}$")
        ("TL", r"^(TL[0-9]{2})\d{19}$")
        ("TN", r"^(TN[0-9]{2})\d{20}$")
        ("TR", r"^(TR[0-9]{2})\d{5}[A-Z0-9]{17}$")
        ("UA", r"^(UA[0-9]{2})\d{6}[A-Z0-9]{19}$")
        ("VA", r"^(VA[0-9]{2})\d{18}$")
        ("VG", r"^(VG[0-9]{2})[A-Z0-9]{4}\d{16}$")
        ("XK", r"^(XK[0-9]{2})\d{16}$")
    ])

    # Check format

    stripped = replace(str, r"[\s\-]+"i => "")
    countryCode = uppercase(stripped[1:2])
    if !(countryCode in keys(ibanReg))
        return false
    end

    if !contains(stripped, ibanReg[countryCode])
        return false
    end

    # Check checksum
    stripped = uppercase(replace(str, r"[^A-Z0-9]+"i => ""))
    rearranged = str[5:end] * str[1:4]
    alphaCapsReplacedWithDigits = join(map(((index, value), ) -> contains(string(value), r"[A-Z]") ? Int(codepoint(value)) - 55 : value, enumerate(rearranged)))
    splitted = SubString.(alphaCapsReplacedWithDigits, findall(r"\d{1,7}", alphaCapsReplacedWithDigits))
    remainder = parse(Int, reduce((acc, val) -> string(parse(Int, acc * val) % 97), splitted))

    return remainder === 1
end