﻿// Country data columns:
//  - Name
//  - Code
//  - Country code
// Format syntax:
// 0 = [0-9]
// A = [A-Z]
// X = [0-9A-Z]
var countryIBANData = {
    belgium:        new Country("Belgium",      "BE",  "000",   "000000000",               false),
    luxembourg:     new Country("Luxembourg",   "LU",  "000",   "XXXXXXXXXXXXX",           false),
    netherlands:    new Country("Netherlands",  "NL",  "AAAA",  "0000000000",              true),
    france:         new Country("France",       "FR",   "",     "0000000000XXXXXXXXXXXXX", false)
};

function Country(name, code, bankFormat, accountFormat, useBic) {

    this.name = name;
    this.code = code;
    this.bankLength = bankFormat.length;
    this.accountLength = accountFormat.length;
    this.bankFormat = CreateRegExp(bankFormat);
    this.accountFormat = CreateRegExp(accountFormat);
    this.totalLength = 4 + this.bankLength + this.accountLength;
    this.useBic = useBic;
}

function CreateRegExp(format) {
    var regexString = "";
    var runner = { c: "", count: 0 };
    
    for (var i = 0; i < format.length; i++) {
        var c = format.charAt(i);
        if (c == runner.c) {
            runner.count++;
        } else {
            regexString += CreateRegExpSequence(runner);
            runner = { c: c, count: 1 };
        }
    }
    
    regexString += CreateRegExpSequence(runner);
                
    return new RegExp(regexString, "i");
}

function CreateRegExpSequence(data) {
    switch (data.c)
    {
        case "0":
            return "[0-9]{" + data.count + "}";
            
        case "A":
            return "[A-Z]{" + data.count + "}";
            
        case "X":
            return "[0-9A-Z]{" + data.count + "}";
            
        case "":
            return "";
        
        default:
          return data.c;
    }
}

// Modulo 97 for huge numbers given as digit strings.
function mod97(s) {
    var m = 0;
    for (var i = 0; i < s.length; ++i)
        m = (m * 10 + parseInt(s.charAt(i))) % 97;
    return m;
}

// Convert a capital letter into digits: A -> 10 ... Z -> 35 (ISO 13616).
function normalize(ch) {
    if (/\d/.test(ch)) {
        return ch.charCodeAt(0) - '0'.charCodeAt(0);
    }

    if (/[A-Z]/.test(ch)) {
        return 10 + (ch.charCodeAt(0) - 'A'.charCodeAt(0));
    }

    return 10 + (ch.charCodeAt(0) - 'a'.charCodeAt(0));
}

// Fill the string with leading zeros until length is reached.
function PadZero(s, length) {
    while (s.length < length)
        s = "0" + s;
        
    return s;
}

// Calculate 2-digit checksum of an IBAN.
function CalcIBANChecksum(iban) {
    var code = iban.substring(0, 2);
    var checksum = iban.substring(2, 4);
    var bban = iban.substring(4);

    // Assemble digit string
    var digits = "";
    for (var i = 0; i < bban.length; ++i) {
        digits += normalize(bban.charAt(i));
    }
    for (var i = 0; i < code.length; ++i) {
        digits += normalize(code.charAt(i));
    }
    digits += checksum;

    // Calculate checksum
    checksum = 98 - mod97(digits);
    return PadZero("" + checksum, 2);
}

// Fill the account number part of IBAN with leading zeros.
function PadAccount(country, account) {
    return PadZero(account, country.accountLength);
}

// Calculate the checksum and assemble the IBAN.
function CalcIBAN(country, bank, account) {
    var paddedAccount = PadAccount(country, account);
    var checksum = CalcIBANChecksum(country.code + "00" + bank + paddedAccount);
    return country.code + checksum + bank + paddedAccount;
}

function CalcAltIBAN(country, bank, account) {
    var paddedAccount = PadAccount(country, account);
    var checksum = CalcIBANChecksum(country.code + "00" + bank + paddedAccount);
    checksum = PadZero("" + mod97(checksum), 2);
    return country.code + checksum + bank + paddedAccount;
}

// Check the input, calculate the checksum and assemble the IBAN.
function CreateIBAN(countryName, bicCode, bankAccount) {
    var country = countryIBANData[countryName];

    if (country == null) {
        //alert("Unable to create IBAN number for this country: " + countryName);
        return "";
    }

    // Get rid of special characters
    bankAccount = bankAccount.replace(/[^0-9A-Z ]+/gi, "");
    
    var bankCode;
    var accountInfo;
    if (country.useBic) {
        bankCode = bicCode.substr(0, 4);
        accountInfo = bankAccount;
    } else {
        bankCode = bankAccount.substr(0, country.bankLength);
        accountInfo = bankAccount.substr(country.bankLength);
    }

    // Pad out account info
    accountInfo = PadZero(accountInfo, country.accountLength);

    if (country.bankFormat.test(bankCode) == false) {
        //alert("Unable to create IBAN number for this bank: " + bankCode);
        return "";
    }
    
    if (country.accountFormat.test(accountInfo) == false) {
        //alert("Unable to create IBAN number for this account: " + accountInfo);
        return "";
    }
    
    var iban = CalcIBAN(country, bankCode, accountInfo);
    var alternateIBAN = CalcAltIBAN(country, bankCode, accountInfo);
    
    if (iban != alternateIBAN) {
        //alert("The IBAN number was generated but some checks on it failed. Please make sure that the calculated IBAN is correct.");
    }
    
    return iban;
}


