/***************************************************************************
                             -------------------
    cvs         : $Id$
    begin       : Sat Aug 10 2002
    copyright   : (C) 2002 by Fabian Kaiser
    email       : fabian@openhbci.de

 ***************************************************************************
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Lesser General Public            *
 *   License as published by the Free Software Foundation; either          *
 *   version 2.1 of the License, or (at your option) any later version.    *
 *                                                                         *
 *   This library is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   Lesser General Public License for more details.                       *
 *                                                                         *
 *   You should have received a copy of the GNU Lesser General Public      *
 *   License along with this library; if not, write to the Free Software   *
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston,                 *
 *   MA  02111-1307  USA                                                   *
 *                                                                         *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "algorithms.h"
#include <cassert>
#include <cstdio>
#include <cstdlib> // for free(3)
#include <fstream>
#include <iostream>

#if OS_WIN32
# include <windows.h>
# include <shlobj.h>
# undef ERROR
#else
# include <sys/stat.h>
# include <string.h> // for memset(3)
#endif // OS_WIN32

using namespace std;

/* Divisions-Makros (copied from Michael Plugge
   <m.plugge@fh-mannheim.de> and his konto_check package. */

/* Makros zur Modulo-Bildung über iterierte Subtraktionen.
 * auf Intel-Hardware ist dies schneller als eine direkte Modulo-Operation;
 * Auf Alpha ist Modulo allerdings schneller (gute FPU).
 */
#ifdef __ALPHA
#   define SUB_352(a)   (a) %= 11
#   define SUB_176(a)   (a) %= 11
#   define SUB_88(a)   (a) %= 11
#   define SUB_44(a)   (a) %= 11
#   define SUB_22(a)   (a) %= 11
#   define SUB_11(a)   (a) %= 11

#   define SUB_320(a)   (a) %= 10
#   define SUB_160(a)   (a) %= 10
#   define SUB_80(a)   (a) %= 10
#   define SUB_40(a)   (a) %= 10
#   define SUB_20(a)   (a) %= 10
#   define SUB_10(a)   (a) %= 10

#   define SUB_288(a)   (a) %= 9
#   define SUB_144(a)   (a) %= 9
#   define SUB_72(a)   (a) %= 9
#   define SUB_36(a)   (a) %= 9
#   define SUB_18(a)   (a) %= 9
#   define SUB_9(a)   (a) %= 9

#   define SUB_224(a)   (a) %= 7
#   define SUB_112(a)   (a) %= 7
#   define SUB_56(a)   (a) %= 7
#   define SUB_28(a)   (a) %= 7
#   define SUB_14(a)   (a) %= 7
#   define SUB_7(a)   (a) %= 7
#else
#   define SUB(a, x) if ((a) >= (x)) (a) -= (x)
#   define SUB_352(a) SUB((a), 352); SUB_176(a)
#   define SUB_176(a) SUB((a), 176); SUB_88(a)
#   define SUB_88(a)  SUB((a), 88); SUB_44(a)
#   define SUB_44(a)  SUB((a), 44); SUB_22(a)
#   define SUB_22(a)  SUB((a), 22); SUB_11(a)
#   define SUB_11(a)  SUB((a), 11)

#   define SUB_320(a) SUB((a), 320); SUB_160(a)
#   define SUB_160(a) SUB((a), 160); SUB_80(a)
#   define SUB_80(a)  SUB((a), 80); SUB_40(a)
#   define SUB_40(a)  SUB((a), 40); SUB_20(a)
#   define SUB_20(a)  SUB((a), 20); SUB_10(a)
#   define SUB_10(a)  SUB((a), 10)

#   define SUB_288(a) SUB((a), 288); SUB_144(a)
#   define SUB_144(a) SUB((a), 144); SUB_72(a)
#   define SUB_72(a)  SUB((a), 72); SUB_36(a)
#   define SUB_36(a)  SUB((a), 36); SUB_18(a)
#   define SUB_18(a)  SUB((a), 18); SUB_9(a)
#   define SUB_9(a)   SUB((a), 9)

#   define SUB_224(a) SUB((a), 224); SUB_112(a)
#   define SUB_112(a) SUB((a), 112); SUB_56(a)
#   define SUB_56(a)  SUB((a), 56); SUB_28(a)
#   define SUB_28(a)  SUB((a), 28); SUB_14(a)
#   define SUB_14(a)  SUB((a), 14); SUB_7(a)
#   define SUB_7(a)   SUB((a), 7)
#endif

// Alternative macros
#ifdef __ALPHA
#else
#define SUBIP(a, x)  (a >= x ? a-x : a)
#define SUBIP_10(a) SUBIP(a, 10)
#define SUBIP_20(a) SUBIP_10(SUBIP(a, 20))
#define SUBIP_40(a) SUBIP_20(SUBIP(a, 40))
#define SUBIP_80(a) SUBIP_40(SUBIP(a, 80))
#endif
// However, it is difficult to evaluate the performance improvement
// since especially valgrind doesn't show the extra cost of the modulo
// operation. These macros are therefore not used so far.

// ////////////////////

#ifdef OS_WIN32
static std::string accnum_get_prefix()
{
    // Partial implementation of
    // http://developer.gnome.org/doc/API/2.0/glib/glib-Windows-Compatibility-Functions.html#g-win32-get-package-installation-directory
    char ch_fn[MAX_PATH], *p;

    // From the docs: Retrieves the fully-qualified path for the
    // file that contains the specified module. If the first
    // parameter is NULL, GetModuleFileName retrieves the path of
    // the executable file of the current process.
    if (!GetModuleFileName(NULL, ch_fn, MAX_PATH)) {
        return "";
    }

    if ((p = strrchr(ch_fn, '\\')) != NULL) {
        *p = '\0';
    }

    p = strrchr(ch_fn, '\\');
    if (p && (_stricmp(p+1, "bin") == 0
              || _stricmp(p+1, "lib") == 0)) {
        *p = '\0';
    }

    return std::string(ch_fn);
}

#endif // OS_WIN32

std::string algorithms_get_bankdata_dir()
{
    std::string pkgdatadir;
    const char *xdg_data_dirs = getenv("XDG_DATA_DIRS");
    std::string datadirs = xdg_data_dirs ? xdg_data_dirs : "";
#ifdef OS_WIN32
    if (!datadirs.empty()) {
        pkgdatadir = datadirs;
    } else {
        std::string prefix(accnum_get_prefix());
        pkgdatadir = prefix + "\\share\\ktoblzcheck";
    }
#else // OS_WIN32
    if (!datadirs.empty()) {
        struct stat stat_buf;
        memset(&stat_buf, 0, sizeof(stat_buf));
        const char delim = ':';
        size_t index = datadirs.find(delim);
        size_t previous = 0;
        const std::string dirname("/ktoblzcheck");
        if (index == std::string::npos) {
            pkgdatadir = datadirs;
        } else {
            while (index != std::string::npos) {
                pkgdatadir = datadirs.substr(previous, index - previous) + dirname;
                int result = stat(pkgdatadir.c_str(), &stat_buf);
                if (result == 0) {
                    break;
                }
                pkgdatadir.clear();
                previous = index + 1;
                index = datadirs.find(delim, previous);
            }
        }
        if (pkgdatadir.empty()) {
            pkgdatadir = datadirs.substr(previous) + dirname;
            int result = stat(pkgdatadir.c_str(), &stat_buf);
            if (result != 0) {
                pkgdatadir.clear();
            }
        }
    }
# ifdef BANKDATA_PATH
    if (pkgdatadir.empty()) {
        pkgdatadir = BANKDATA_PATH;
    }
# endif // BANKDATA_PATH
#endif // OS_WIN32
    return pkgdatadir;
}

// ////////////////////

AccountNumberCheck::Result
algo01(const int modulus, const int weight[10], const bool crossfoot, const int checkIndex,
       const int accountId[10])
{
    int result = algo03(modulus, weight, crossfoot, accountId, 0, 9);
    //  cout << "res%mod: " << result << endl;
    // no rest after division by modulus (in algo03)?
    // then the the result will be 0!
    if (0 == result) {
        result = modulus;
    }

    // and calc the check number
    result = (modulus - result) % 10;

    // compare the result with the real check number
    if (accountId[checkIndex - 1] == result) {
        return AccountNumberCheck::OK;
    } else {
        return AccountNumberCheck::ERROR;
    }
}

// New algo02 for Pruefmethode 02 and similar methods
// 04, 07, 14, 58, 85/a, A2/v2, B2/v1, B4/v2, C3/v2, C4/v2
// For all of these methods modulus is usually 11
AccountNumberCheck::Result
algo02(const int modulus, const int weight[10], const int checkIndex, const int accountId[10],
       const int startAdd, const int stopAdd)
{
    int res[10];

    // mult the weight with the account id
    multArray(accountId, weight, res);

    // add all values, get modulus
    int result = add(res, startAdd, stopAdd);
    result %= modulus;

    if (result != 0) {
        // multi-digit check-numbers are not allowed
        if (modulus-result >= 10) {
            return AccountNumberCheck::ERROR;
        }
        result = modulus-result;
    }

    // compare the result with the real check number
    if (accountId[checkIndex - 1] == result) {
        return AccountNumberCheck::OK;
    } else {
        return AccountNumberCheck::ERROR;
    }
}

int algo03(const int modulus, const int weight[10], const bool crossfoot, const int accountId[10],
           const int startAdd, const int stopAdd)
{
    int res[10];
    // mult the weight with the account id
    multArray(accountId, weight, res);

    // for 00,...: calc the crossfoot for each value
    if (crossfoot) {
        crossFoot(res);
    }

    // add all values
    int result = add(res, startAdd, stopAdd);
    //  cout << "add: " << result << endl;
    result = result % modulus;

    return result;
}

int algo03a(const int weight[10], const bool crossfoot, const int accountId[10], const int startAdd,
            const int stopAdd)
{
    int res[10];
    // same as algo03, but return result before modulus
    // mult the weight with the account id
    multArray(accountId, weight, res);

    // for 00,...: calc the crossfoot for each value
    if (crossfoot) {
        crossFoot(res);
    }

    // add all values
    int result = add(res, startAdd, stopAdd);
    //  cout << "add: " << result << endl;
    return result;
}

// Algorithm used in methods 24 and B9 variant 1.
int algo05(const int modulus1, const int modulus2, const int weight[10], const int accountId[10],
           const int startAdd, const int stopAdd)
{
    int res[10];
    // mult the weight with the account id
    multArray(accountId, weight, res);

    // add the weight, individual modulus
    for (int i = startAdd; i <= stopAdd; i++) {
        res[i] = (res[i] + weight[i]) % modulus1;
    }

    // sum the stuff, final modulus
    int result = add(res, startAdd, stopAdd) % modulus2;

    return result;
}

AccountNumberCheck::Result
algo06(const int accountId[10])
{
    int i = 0;
    int c2 = 0;
    int d2 = 0;
    int a5 = 0;
    int p = 0;
    int account[10];

    int tab1[5] = {0, 4, 3, 2, 6};
    int tab2[5] = {7, 1, 5, 9, 8};

    // copy to keep interface array const
    for (i = 0; i <= 9; i++) {
        account[i] = accountId[i];
    }

    i = 4;
    while (0 == account[i-1]) {
        i++;
    }

    c2 = i % 2;
    d2 = 0;
    a5 = 0;

    while (i < 10) {
        switch (account[i-1]) {
        case 0:
            account[i-1] = 5;
            break;
        case 1:
            account[i-1] = 6;
            break;
        case 5:
            account[i-1] = 10;
            break;
        case 6:
            account[i-1] = 1;
            break;
        }

        if (c2 == d2) {
            if (account[i-1] > 5) {
                if (0 == c2 && 0 == d2) {
                    c2 = 1;
                    d2 = 1;
                    a5 += 6 - (account[i-1] - 6);
                } else { // 0 == c2 == d2
                    c2 = 0;
                    d2 = 0;
                    a5 += account[i-1];
                } // 0 == c2 == d2
            } else { // account(i-1) > 5
                if (0 == c2 && 0 == d2) {
                    c2 = 1;
                    a5 += account[i-1];
                } else { // 0 == c2 == d2
                    c2 = 0;
                    a5 += account[i-1];
                } // 0 == c2 == d2
            } // account(i-1) > 5
        } else { // c2 == d2
            if (account[i-1] > 5) {
                if (0 == c2) {
                    c2 = 1;
                    d2 = 0;
                    a5 += -6 + (account[i-1] - 6);
                } else { // 0 == c2
                    c2 = 0;
                    d2 = 1;
                    a5 -= account[i-1];
                } // 0 == c2
            } else { // account(i-1) > 5
                if (0 == c2) {
                    c2 = 1;
                    a5 -= account[i-1];
                } else { // 0 == c2
                    c2 = 0;
                    a5 -= account[i-1];
                } // 0 == c2
            } // account(i-1) > 5
        } // c2 == d2
        i++;
    }

    while (a5 < 0 || a5 > 4) {
        if (a5 > 4) {
            a5 -= 5;
        } else {
            a5 += 5;
        }
    }
    // Fixed wrong ending of while-loop; by Erik Kerger <erik@kedo.com>

    if (0 == d2) {
        p = tab1[a5];
    } else {
        p = tab2[a5];
    }

    if (p == account[9]) {
        return AccountNumberCheck::OK;
    } else {
        if (0 == account[3]) {
            if (p > 4) {
                p -= 5;
            } else {
                p += 5;
            }
            if (p == account[9]) {
                return AccountNumberCheck::OK;
            }
        }
    }

    // hugh...
    // such a stupid algorithm...;-(
    return AccountNumberCheck::ERROR;
}

AccountNumberCheck::Result
algo04(const std::string &bankId, const std::string accountId)
{
    // keep the interface argument constant
    std::string accountBuf = accountId;

    while (8 < accountBuf.length() && '0' == accountBuf[0]) {
        accountBuf = accountBuf.substr(1);
    }

    if (8 != accountBuf.length()) {
        return AccountNumberCheck::ERROR;
    }
    if (5 > bankId.length()) {
        return AccountNumberCheck::ERROR;
    }

    // hmm, here the spec is not clear:
    // as far as i unserstand, the 4. digit from rigth must not be '0'
    // because a '0' would be skipped and thus p would'nt be on index 6 of the
    // eser alt number
    string bankPart = bankId.substr(bankId.length() - 4);

    string accountPart = accountBuf.substr(2);
    // strip leading '0's
    while (0 < accountPart.length() && '0' == accountPart[0]) {
        accountPart = accountPart.substr(1);
    }

    string checkPart = accountBuf.substr(0, 2);
    // backup the check number
    char checkNumber = checkPart[1];
    // the checknumber must not be used when multiplying -> set to 0
    string eserUnpadded = bankPart + checkPart[0] + '0' + accountPart;
    string eser12 = string(12 - eserUnpadded.length(), '0') + eserUnpadded;

    // map the 12 digit eser-number to our 10 digit arrays so we can
    // use the std multiplication
    int eser02[10];
    int eser10[10];
    number2Array(eser12.substr(0, 2), eser02);
    number2Array(eser12.substr(2), eser10);
    int weight02[10];
    int weight10[10];
    number2Array("0000000042", weight02);
    number2Array("1637905842", weight10);
    weight10[5] = 10;

    // perform the multiplication
    int res02[10];
    int res10[10];
    multArray(eser02, weight02, res02);
    multArray(eser10, weight10, res10);

    // add-mod
    int result = add_10(res02) + add_10(res10);
    result = result % 11;

    // find a multiple of weight[checkNumberIndex] that fits
    // 10 == (result + multiple) % 11
    int i = 0;
    int pWeight = weight10[9 - accountPart.length()];
    while (i < 11) {
        if (10 == (((i*pWeight) + result) % 11)) {
            break;
        }
        i++;
    }

    // multiple found, does it match?
    // if none was found (11 == i), the next condition will evaluate to false
    if (checkNumber - '0' == i) {
        return AccountNumberCheck::OK;
    }

    return AccountNumberCheck::ERROR;
}

// Added by Jens Gecius
// Some corrections by Erik Kerger
// same as algo04, but for nine digit accountIDs with
// a little different eser12 system.
AccountNumberCheck::Result
algo04a(const std::string &bankId, const std::string accountId)
{
    // keep the interface argument constant
    std::string accountBuf = accountId;

    while (9 < accountBuf.length() && '0' == accountBuf[0]) {
        accountBuf = accountBuf.substr(1);
    }

    if (9 != accountBuf.length()) {
        return AccountNumberCheck::ERROR;
    }
    if (5 > bankId.length()) {
        return AccountNumberCheck::ERROR;
    }

    // the middle 2 digits of the bankid
    string bankPart = bankId.substr(bankId.length() - 4, 2);
    //while (bankPart.length() > 0 && '0' == bankPart[0])
    //bankPart = bankPart.substr(1);
    string bankPart2 = bankId.substr(bankId.length() - 1);
    // cout << "bankpart: " << bankPart << endl;
    // the second digit of the accountBuf
    string checkPart = accountBuf.substr(2, 1);
    // cout << "checkPart: " << checkPart << endl;
    string accountPart1 = accountBuf.substr(0, 1);
    string accountPart2 = accountBuf.substr(1, 1);
    string accountPart = accountBuf.substr(3);
    // skip leading "0" in the the accountPart
    while (accountPart.length() > 0 && '0' == accountPart[0]) {
        accountPart = accountPart.substr(1);
    }
    // cout << "accoutPart: " << accountPart << endl;
    string eser12unpadded = bankPart + accountPart2 + bankPart2
                            + accountPart1 + checkPart + accountPart;
    // cout << "eser12unpadded: " << eser12unpadded << endl;
    // concat and padd to 12 byte
    string eser12 = eser12unpadded;
    while (eser12.length() < 12) {
        eser12 = "0" + eser12;
    }

    // slit so we can use the int[10]-methods we already have
    // xx xxxxxxxxxx (eser02 and eser10)
    //  string eser10 = eser12.substr(2);
    //  string eser02 = eser12.substr(0, 2);
    int eser10[10];
    int eser02[10];
    number2Array(eser12.substr(2), eser10);
    number2Array(eser12.substr(0, 2), eser02);
    // cout << "eser02: " << array2Number(eser02) << endl;
    // cout << "eser10: " << array2Number(eser10) << endl;

    int weight02[10];
    int weight10[10];
    number2Array("0000000042", weight02);
    number2Array("1637905842", weight10);
    // weight10[5] must be 10.
    weight10[5] = 10;

    int res02[10];
    int res10[10];
    // mult the weight
    multArray(eser02, weight02, res02);
    multArray(eser10, weight10, res10);

    int result = add_10(res02);
    // cout << "result add1: " << result << endl;
    // cout << "result add2: " << add(res10, 0, 9) << endl;
    result += add_10(res10);
    // cout << "result: " << result << endl;
    result = result % 11;
    // cout << "result: " << result << endl;
    // cout << "weight02: " << array2Number(weight02) << endl;
    // cout << "weight10: " << array2Number(weight10) << endl;
    // the weight for the check-digit
    // int realWeight = weight10[9 - accountPart.length()];
    // cout << "realWeight: " << realWeight << endl;
    // the "Pruefziffernberechnung" doku shows a way to calculate the
    // check num. As we have to check only, so only have to check the
    // result we calculated including the check digit is 10.
    if (result == 10) {
        return AccountNumberCheck::OK;
    }

    return AccountNumberCheck::ERROR;
}

// iterated transformation
AccountNumberCheck::Result
algo07(const int accountId[10], const int transform[6][10])
{
    int result = 0;
    for (int i = 0; i < 10; i++) {
        result = result + transform[transform[5][i]][accountId[i]];
    }
    result = 10 - (result % 10);
    if (result == 10) {
        result = 0;
    }
    if (result == accountId[9]) {
        return AccountNumberCheck::OK;
    }
    return AccountNumberCheck::ERROR;
}

void multArray(const int *a, const int *b, int dest[10])
{
#define MULTARRAY(i) dest[i] = a[i] * b[i]
    MULTARRAY(0);
    MULTARRAY(1);
    MULTARRAY(2);
    MULTARRAY(3);
    MULTARRAY(4);
    MULTARRAY(5);
    MULTARRAY(6);
    MULTARRAY(7);
    MULTARRAY(8);
    MULTARRAY(9);
    // Sigh -- writing it as above greatly speeds up runtime compared
    // to a conventional for-loop:
//     for (int i=0; i<10; i++) {
//	dest[i] = a[i] * b[i];
//     }
}

// This one is unused
void crossFoot(const int *source, int dest[10], int start, int stop)
{
    for (int i = start; i < stop+1; i++) {
        int elem = source[i];
        dest[i] = elem / 10  +  elem % 10;
    }
}

void crossFoot(int *source)
{
#define CROSSFOOTOP(i)  source[i] = source[i] / 10 + source[i] % 10
    CROSSFOOTOP(0);
    CROSSFOOTOP(1);
    CROSSFOOTOP(2);
    CROSSFOOTOP(3);
    CROSSFOOTOP(4);
    CROSSFOOTOP(5);
    CROSSFOOTOP(6);
    CROSSFOOTOP(7);
    CROSSFOOTOP(8);
    CROSSFOOTOP(9);
}

int add(const int *source, int start, int stop)
{
    const int *sp = source + start;
    switch (stop - start) {
    case 0:
        return sp[0];
    case 1:
        return sp[0] + sp[1];
    case 2:
        return sp[0] + sp[1] + sp[2];
    case 3:
        return sp[0] + sp[1] + sp[2] + sp[3];
    case 4:
        return sp[0] + sp[1] + sp[2] + sp[3] + sp[4];
    case 5:
        return sp[0] + sp[1] + sp[2] + sp[3] + sp[4]
               + sp[5];
    case 6:
        return sp[0] + sp[1] + sp[2] + sp[3] + sp[4]
               + sp[5] + sp[6];
    case 7:
        return sp[0] + sp[1] + sp[2] + sp[3] + sp[4]
               + sp[5] + sp[6] + sp[7];
    case 8:
        return sp[0] + sp[1] + sp[2] + sp[3] + sp[4]
               + sp[5] + sp[6] + sp[7] + sp[8];
    case 9:
        return sp[0] + sp[1] + sp[2] + sp[3] + sp[4]
               + sp[5] + sp[6] + sp[7] + sp[8] + sp[9];
    default:
        assert(0);
        return 0; // never reached
    }
}

string array2Number(const int a[10])
{
    string result("0000000000");
    for (unsigned i = 0; i < 10; i++) {
        result[i] = '0' + a[i];
    }

    return result;
}

void number2Array(const string &number, int a[10])
{
    if (number.size() == 10) {
#define ASSIGNARRAY(i)  a[i] = number[i] - '0'
        ASSIGNARRAY(0);
        ASSIGNARRAY(1);
        ASSIGNARRAY(2);
        ASSIGNARRAY(3);
        ASSIGNARRAY(4);
        ASSIGNARRAY(5);
        ASSIGNARRAY(6);
        ASSIGNARRAY(7);
        ASSIGNARRAY(8);
        ASSIGNARRAY(9);
    } else {
        unsigned padd_size = 10 - number.size();
        unsigned i = 0;
        for (; i < padd_size; i++) {
            a[i] = 0;
        }
        for (; i < 10; i++) {
            a[i] = number[i-padd_size] - '0';
        }
    }
}

long_long number2LongLong(const string &number)
{
    long_long result = 0;

    for (unsigned int i = 0; i < number.length(); i++) {
        result *= 10;
        result += number[i] - '0';
    }

    return result;
}
