/***************************************************************************
 *   Copyright (C) 2008 by S. MANKOWSKI / G. DE BURE support@mankowski.fr  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program 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 General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>  *
 ***************************************************************************/
/** @file
* This file implements classes SKGServices.
*
* @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgservices.h"
#include "skgtraces.h"
#include "skgdocument.h"

#include <klocale.h>
#include <kglobal.h>
#include <ksavefile.h>
#include <kio/netaccess.h>

#include <sys/time.h>

#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QSqlError>
#include <QSqlDriver>
#include <QVariant>
#include <QRegExp>
#include <QFile>
#include <QtCrypto>
#include <QDomDocument>

int SKGServices::SKGSqlTraces = (SKGServices::getenv("SKGTRACESQL").isEmpty() ? -1 : SKGServices::stringToInt(SKGServices::getenv("SKGTRACESQL")));

SKGError SKGServices::m_lastCallbackError;

QList< SKGServices::SKGSearchCriteria > SKGServices::stringToSearchCriterias(const QString& iString)
{
    QList< SKGServices::SKGSearchCriteria > output;

    QStringList words = SKGServices::splitCSVLine(iString, ' ', true);
    int nbwords = words.count();

    SKGServices::SKGSearchCriteria criteria;
    criteria.mode = '+';
    bool atLeastOnePlus = false;
    for (int i = 0; i < nbwords; ++i) {
        QString word = words.at(i);
        bool isWordStartingByPlus = word.startsWith(QLatin1String("+"));
        bool isWordStartingByLess = word.startsWith(QLatin1String("-"));
        if (isWordStartingByPlus || isWordStartingByLess) {
            QChar nextChar;
            if (word.count() > 1) nextChar = word[1];
            if (nextChar < '0' || nextChar > '9') {
                word = word.right(word.length() - 1);
                if (i != 0) {
                    if (criteria.mode == '-') output.push_back(criteria);
                    else {
                        output.push_front(criteria);
                        atLeastOnePlus = true;
                    }
                }
                criteria.words.clear();
                criteria.mode = (isWordStartingByPlus ? '+' : '-');
            }
        }
        criteria.words.push_back(word);
    }
    if (criteria.mode == '-') output.push_back(criteria);
    else {
        output.push_front(criteria);
        atLeastOnePlus = true;
    }

    if (!atLeastOnePlus) {
        //Add a '+' always true
        SKGServices::SKGSearchCriteria criteria;
        criteria.mode = '+';
        criteria.words.push_back("");
        output.push_front(criteria);
    }

    return output;
}

QString SKGServices::getenv(const QString& iAttribute)
{
    return QString::fromUtf8(qgetenv(iAttribute.toUtf8().constData()));
}

QString SKGServices::intToString(int iNumber)
{
    QString output;
    output.setNum(iNumber);
    return output;
}

int SKGServices::stringToInt(const QString& iNumber)
{
    if (iNumber.isEmpty()) return 0;

    bool ok;
    int output = iNumber.toInt(&ok);
    if (!ok) {
        SKGTRACE << "WARNING: SKGServices::stringToInt(" << iNumber << ") failed" << endl;
    }

    return output;
}

QString SKGServices::stringToSqlString(const QString& iString)
{
    QString output = iString;
    output.replace('\'', "''");
    return output;
}

QString SKGServices::stringToHtml(const QString& iString)
{
    QString output = iString;
    output.replace('&', "&amp;"); //Must be done first
    output.replace('<', "&lt;");
    output.replace('>', "&gt;");
    output.replace('"', "&quot;");

    return output;
}

QString SKGServices::htmlToString(const QString& iString)
{
    QString output = iString;
    output.replace("&lt;", "<");
    output.replace("&gt;", ">");
    output.replace("&quot;", "\"");
    output.replace("&amp;", "&");

    return output;
}

QString SKGServices::stringsToCsv(const QStringList& iList, const QChar& iSeparator)
{
    QString output;
    int nb = iList.count();
    for (int i = 0; i < nb; ++i) {
        output.append(SKGServices::stringToCsv(iList[i]));
        if (i < nb - 1) output.append(iSeparator);
    }

    return output;
}

QString SKGServices::stringToCsv(const QString& iNumber)
{
    QString output = iNumber;
    output.replace('"', "#SKGDOUBLECOTE#");
    output.replace("#SKGDOUBLECOTE#", "\"\"");
    output = '"' % output % '"';
    return output;
}

double SKGServices::stringToDouble(const QString& iNumber)
{
    if (iNumber.isEmpty()) return 0;
    QString number = iNumber;
    number.remove(QRegExp("[^0-9-+eE,.]"));

    bool ok;
    double output = number.toDouble(&ok);
    if (!ok) {
        QString tmp = number;
        tmp.replace(',', '.');
        if (tmp.count('.') > 1) tmp.remove(tmp.indexOf('.'), 1);
        output = tmp.toDouble(&ok);
        if (!ok) {
            QString tmp = number;
            tmp.replace('.', ',');
            if (tmp.count(',') > 1) tmp.remove(tmp.indexOf(','), 1);
            output = tmp.toDouble(&ok);
            if (!ok) {
                QString tmp = number;
                tmp.remove(',');
                output = tmp.toDouble(&ok);
            }
        }
    }
    if (!ok) {
        SKGTRACE << "WARNING: SKGServices::stringToDouble(" << iNumber << ") failed" << endl;
    }
    return output;
}

QString SKGServices::doubleToString(double iNumber)
{
    QString output;
    output.setNum(iNumber, 'g', 10);
    return output;
}

QString SKGServices::timeToString(const QDateTime& iDateTime)
{
    QDateTime d = iDateTime;
    if (!d.isValid()) d = QDateTime::currentDateTime();
    return d.toString("yyyy-MM-dd HH:mm:ss");
}

QString SKGServices::dateToSqlString(const QDateTime& iDateTime)
{
    QDateTime d = iDateTime;
    if (!d.isValid()) d = QDateTime::currentDateTime();
    return d.toString("yyyy-MM-dd");
}

int SKGServices::nbWorkingDays(const QDate& iFrom, const QDate& iTo)
{
    int nb = 0;
    QDate min = (iFrom < iTo ? iFrom : iTo);
    QDate max = (iFrom < iTo ? iTo : iFrom);

    while (min != max) {
        if (min.dayOfWeek() <= 5) ++nb;
        min = min.addDays(1);
    }
    return nb;
}

QDateTime SKGServices::stringToTime(const QString& iDateString)
{
    QDateTime output = QDateTime::fromString(iDateString, "yyyy-MM-dd HH:mm:ss");
    if (!output.isValid()) {
        output = QDateTime::fromString(iDateString, "yyyy-MM-dd");
    }

    return output;
}

QStringList SKGServices::splitCSVLine(const QString& iString, const QChar& iSeparator, bool iCoteDefineBlock, QChar* oRealSeparator)
{
    QStringList items;
    QString item;
    bool isInBlock = false;
    QChar realSeparator = iSeparator;

    QChar cote = ' '; //Not yet defined
    int nb = iString.length();
    for (int pos = 0; pos < nb; ++pos) {
        QChar c = iString.at(pos);
        if (isInBlock) {
            if (c == cote) {
                if (pos < nb - 1 && iString.at(pos + 1) == cote) ++pos;
                else {
                    items.push_back(item);
                    item = "";
                    isInBlock = false;

                    ++pos; //To ignore next separator
                    if (pos < nb) realSeparator = iString.at(pos); //To get the real separator
                }
            }

            if (isInBlock) item += c;
        } else  if ((c == '\"' || c == '\'') && item.count() == 0 &&  iCoteDefineBlock) {
            if (cote == ' ') cote = c; //Set the real cote char
            isInBlock = true;
        } else  if (QString(c) == realSeparator) {
            items.push_back(item);
            item = "";
            isInBlock = false;
        } else {
            item += c;
        }
    }

    if (!item.isEmpty() || (nb > 0 && iString.at(nb - 1) == realSeparator)) items.push_back(item);

    if (oRealSeparator) *oRealSeparator = realSeparator;

    if (isInBlock) items.clear();

    return items;
}

QString SKGServices::getDateFormat(const QStringList& iDates)
{
    SKGTRACEIN(2, "SKGServices::getDateFormat");
    bool f_YYYY_MM_DD = true;
    bool f_YYYYMMDD = true;
    bool f_DDMMYYYY = true;
    bool f_MMDDYYYY = true;
    bool f_MM_DD_YY = true;
    bool f_DD_MM_YY = true;
    bool f_MM_DD_YYYY = true;
    bool f_DD_MM_YYYY = true;

    //Build regexp
    QRegExp rx("(.+)-(.+)-(.+)");

    //Check all dates
    int nb = iDates.count();
    for (int i = 0; i < nb; ++i) {

        QString val = iDates.at(i);
        val = val.replace('/', '-');
        val = val.replace('\\', '-');
        val = val.replace("'20", "-20");
        val = val.replace("' ", "-200");
        val = val.replace('\'', "-20");
        val = val.replace('.', '-');
        if (rx.indexIn(val) == -1) {
            f_YYYY_MM_DD = false;
            f_MM_DD_YY = false;
            f_DD_MM_YY = false;
            f_MM_DD_YYYY = false;
            f_DD_MM_YYYY = false;

            if (val.length() == 8) {
                int left2 = SKGServices::stringToInt(val.left(2));
                if (left2 > 12) {
                    f_MMDDYYYY = false;
                }
                if (left2 > 31) {
                    f_DDMMYYYY = false;
                }

                int mid2 = SKGServices::stringToInt(val.mid(2, 2));
                if (mid2 > 12) {
                    f_DDMMYYYY = false;
                }
                if (mid2 > 31) {
                    f_MMDDYYYY = false;
                }

                int mid4 = SKGServices::stringToInt(val.mid(4, 2));
                if (mid4 > 12) {
                    f_YYYYMMDD = false;
                }

                int right2 = SKGServices::stringToInt(val.right(2));
                if (right2 > 31) {
                    f_YYYYMMDD = false;
                }
            }
        } else {
            f_YYYYMMDD = false;
            f_DDMMYYYY = false;
            f_MMDDYYYY = false;

            QString v1 = rx.cap(1);
            QString v2 = rx.cap(2);
            QString v3 = rx.cap(3);

            if (SKGServices::stringToInt(v1) > 12) {
                f_MM_DD_YY = false;
                f_MM_DD_YYYY = false;
            }

            if (SKGServices::stringToInt(v2) > 12) {
                f_DD_MM_YY = false;
                f_DD_MM_YYYY = false;
            }

            if (SKGServices::stringToInt(v1) > 31 || SKGServices::stringToInt(v2) > 31) {
                f_MM_DD_YY = false;
                f_MM_DD_YYYY = false;
                f_DD_MM_YY = false;
                f_DD_MM_YYYY = false;
            }

            if (SKGServices::stringToInt(v3) > 31) {
                f_YYYY_MM_DD = false;
            }

            if (v1.length() == 4) {
                f_MM_DD_YY = false;
                f_DD_MM_YY = false;
                f_MM_DD_YYYY = false;
                f_DD_MM_YYYY = false;
            } else {
                f_YYYY_MM_DD = false;
            }

            if (v3.length() == 4) {
                f_YYYY_MM_DD = false;
//                 f_MM_DD_YY=false;
                f_DD_MM_YY = false;
            } else {
                f_MM_DD_YYYY = false;
                f_DD_MM_YYYY = false;
            }
        }
    }

    if (f_YYYYMMDD) return "YYYYMMDD";
    else if (f_MMDDYYYY) return "MMDDYYYY";
    else if (f_DDMMYYYY) return "DDMMYYYY";
    else if (f_DD_MM_YY && f_MM_DD_YY) {
        KLocale* locale = KGlobal::locale();
        if (locale) {
            QString sFormat = locale->dateFormatShort();
            if (sFormat.startsWith(QLatin1String("%m")) || sFormat.startsWith(QLatin1String("%n"))) return "MM-DD-YY";
        }
        return "DD-MM-YY";
    } else if (f_MM_DD_YY) return "MM-DD-YY";
    else if (f_DD_MM_YY) return "DD-MM-YY";
    else if (f_DD_MM_YYYY && f_MM_DD_YYYY) {
        KLocale* locale = KGlobal::locale();
        if (locale) {
            QString sFormat = locale->dateFormatShort();
            if (sFormat.startsWith(QLatin1String("%m")) || sFormat.startsWith(QLatin1String("%n"))) return "MM-DD-YYYY";
        }
        return "DD-MM-YYYY";
    } else if (f_MM_DD_YYYY) return "MM-DD-YYYY";
    else if (f_DD_MM_YYYY) return "DD-MM-YYYY";
    else if (f_YYYY_MM_DD) return "YYYY-MM-DD";

    return "";
}

QString SKGServices::dateToSqlString(const QString& iDate, const QString& iFormat)
{
    QString input = iDate;

    QString YYYY = "0000";
    QString MM = "00";
    QString DD = "00";
    if (iFormat == "YYYYMMDD") {
        YYYY = input.mid(0, 4);
        MM = input.mid(4, 2);
        DD = input.mid(6, 2);

    }
    if (iFormat == "DDMMYYYY") {
        YYYY = input.mid(4, 4);
        MM = input.mid(2, 2);
        DD = input.mid(0, 2);

    }
    if (iFormat == "MMDDYYYY") {
        YYYY = input.mid(4, 4);
        MM = input.mid(0, 2);
        DD = input.mid(2, 2);

    } else {
        QString val = iDate;
        val = val.replace('\\', '-');
        val = val.replace('/', '-');
        val = val.replace("'20", "-20");
        val = val.replace("' ", "-200");
        val = val.replace('\'', "-20");
        val = val.replace('.', '-');
        QRegExp rx("(.+)-(.+)-(.+)");
        if (rx.indexIn(val) != -1) {
            QString v1 = rx.cap(1);
            QString v2 = rx.cap(2);
            QString v3 = rx.cap(3);
            if (iFormat == "YYYY-MM-DD") {
                YYYY = v1;
                MM = v2;
                DD = v3;
            } else if (iFormat == "MM/DD/YY" || iFormat == "MM-DD-YY" || iFormat == "MM/DD/YYYY" || iFormat == "MM-DD-YYYY") {
                MM = v1;
                DD = v2;
                YYYY = v3;
            } else if (iFormat == "DD/MM/YY" || iFormat == "DD-MM-YY" || iFormat == "DD/MM/YYYY" || iFormat == "DD-MM-YYYY") {
                DD = v1;
                MM = v2;
                YYYY = v3;
            }
        }
    }

    if (MM.length() == 1) MM = '0' % MM;
    if (DD.length() == 1) DD = '0' % DD;
    if (YYYY.length() == 1) YYYY = '0' % YYYY;
    if (YYYY.length() == 2) {
        if (stringToInt(YYYY) > 70) YYYY = "19" % YYYY;
        else YYYY = "20" % YYYY;
    }

    QString date = YYYY % '-' % MM % '-' % DD;
    date.replace(' ', '0');
    return dateToSqlString(stringToTime(date));
}

QStringList SKGServices::tableToDump(const SKGStringListList& iTable, SKGServices::DumpMode iMode)
{
    SKGTRACEIN(10, "SKGServices::tableToDump");
    //initialisation
    QStringList oResult;

    //Compute max size of each column
    int* maxSizes = NULL;
    if (iMode == DUMP_TEXT) {
        int nb = iTable.count();
        for (int i = 0; i < nb; ++i) {
            QStringList line = iTable.at(i);

            if (maxSizes == NULL) {
                int size = line.size();
                maxSizes = new int[size];
                if (maxSizes) {
                    for (int j = 0; j < size; ++j) {
                        maxSizes[j] = 0;
                    }
                }
            }

            int nb2 = line.size();
            for (int j = 0; j < nb2; ++j) {
                QString s = line.at(j);
                if (s.length() > maxSizes[j]) maxSizes[j] = s.length();
            }
        }
    }

    //dump
    int nb = iTable.count();
    for (int i = 0; i < nb; ++i) {
        QString lineFormated;
        if (iMode == DUMP_TEXT) {
            lineFormated = "| ";
        }

        QStringList line = iTable.at(i);
        int nb2 = line.size();
        for (int j = 0; j < nb2; ++j) {
            QString s = line.at(j);
            s.remove('\n');

            if (iMode == DUMP_CSV) {
                if (j > 0) lineFormated += ';';
                lineFormated += stringToCsv(s);
            } else {
                s = s.leftJustified(maxSizes[j], ' ');
                lineFormated += s % " | ";
            }
        }
        oResult.push_back(lineFormated);
    }

    //delete
    if (maxSizes) {
        delete [] maxSizes;
        maxSizes = NULL;
    }

    return oResult;
}

QString SKGServices::getRealTable(const QString& iTable)
{
    QString output = iTable;
    if (output.length() > 2 && output.startsWith(QLatin1String("v_"))) {
        output = output.mid(2, output.length() - 2);

        int pos = output.indexOf("_");
        if (pos != -1) output = output.left(pos);
    }

    return output;
}

SKGError SKGServices::cryptFile(const QString& iFileSource, const QString& iFileTarget, const QString& iPassword, bool iEncrypt, const QString& iHeaderFile)
{
    SKGError err;
    SKGTRACEINRC(10, "SKGServices::cryptFile", err);
    SKGTRACEL(10) << "Input parameter [iFileSource]=[" << iFileSource << ']' << endl;
    SKGTRACEL(10) << "Input parameter [iFileTarget]=[" << iFileTarget << ']' << endl;
    SKGTRACEL(10) << "Input parameter [iHeaderFile]=[" << iHeaderFile << ']' << endl;

    QCA::Initializer init;
    if (!iPassword.isEmpty() && !QCA::isSupported("aes128-ecb")) {
        //Set error message
        err.setReturnCode(ERR_WRITEACCESS); //To avoid password request
        err.setMessage(i18nc("An error message about encryption", "AES128 encryption is not supported. Please install qca-ossl."));
    } else {
        // Create a random key - you'd probably use one from another
        // source in a real application
        QCA::SymmetricKey key(QByteArray("skrooge"));

        // Create a random initialisation vector - you need this
        // value to decrypt the resulting cipher text, but it
        // need not be kept secret (unlike the key).
        QCA::InitializationVector iv(iPassword.toAscii());

        // create a 128 bit AES cipher object using Cipher Block Chaining (CBC) mode
        QCA::Cipher* cipher = NULL;
        if (!iPassword.isEmpty()) cipher = new QCA::Cipher("aes128", QCA::Cipher::CBC,
                    // use Default padding, which is equivalent to PKCS7 for CBC
                    QCA::Cipher::DefaultPadding,
                    iEncrypt ? QCA::Encode : QCA::Decode,
                    key, iv);

        QString iFileSourceTmp;
        if (!KIO::NetAccess::download(KUrl(iFileSource), iFileSourceTmp, NULL)) err = SKGError(ERR_FAIL, KIO::NetAccess::lastErrorString());

        if (!err) {
            QFile fileInput(iFileSourceTmp);
            if (!fileInput.open(QIODevice::ReadOnly)) err = SKGError(ERR_INVALIDARG, i18nc("Error message: Opening a file failed", "Open file '%1' failed", iFileSource));
            else {
                //Read document
                QByteArray input = fileInput.readAll();
                QByteArray h = (iHeaderFile % (!cipher ? "_DECRYPTED-" : "_ENCRYPTED-")).toLatin1();
                //BUG 249955 vvv
                if (!cipher && input.startsWith(QByteArray((iHeaderFile % "_ENCRYPTED-").toLatin1()))) err = SKGError(ERR_UNEXPECTED, i18nc("Error message about encrypting a file", "Encryption failed"));
                //BUG 249955 ^^^
                if (!iEncrypt && h.count() && input.startsWith(h)) input = input.right(input.length() - h.length());

                // we use the cipher object to encrypt the argument we passed in
                // the result of that is returned - note that if there is less than
                // 16 bytes (1 block), then nothing will be returned - it is buffered
                // update() can be called as many times as required.
                QCA::SecureArray u;
                if (!err && cipher) {
                    u = cipher->update(input);

                    // We need to check if that update() call worked.
                    if (!cipher->ok()) err = SKGError(ERR_UNEXPECTED, i18nc("Error message about encrypting a file", "Encryption failed"));
                }

                // output the results of that stage
                if (!err) {
                    KSaveFile fileOutput(iFileTarget);
                    if (!fileOutput.open()) err = SKGError(ERR_WRITEACCESS, i18nc("Error message: writing a file failed", "Write file '%1' failed", iFileTarget));
                    else {
                        //Write header
                        if (iEncrypt && h.count()) fileOutput.write(h);

                        //Write document
                        if (!cipher) fileOutput.write(input);
                        else {
                            fileOutput.write(u.toByteArray());

                            // Because we are using PKCS7 padding, we need to output the final (padded) block
                            // Note that we should always call final() even with no padding, to clean up
                            QCA::SecureArray f = cipher->final();

                            // Check if the final() call worked
                            if (!cipher->ok()) err = SKGError(ERR_UNEXPECTED, i18nc("Error message about encrypting a file", "Encryption failed"));

                            // and output the resulting block. The ciphertext is the results of update()
                            // and the result of final()
                            fileOutput.write(f.toByteArray());
                        }

                        //Close the file
                        fileOutput.finalize();
                        fileOutput.close();
                    }
                }
            }
        }

        KIO::NetAccess::removeTempFile(iFileSourceTmp);

        delete cipher;
        cipher = NULL;
    }
    return err;
}

SKGError SKGServices::copySqliteDatabaseToXml(QSqlDatabase* iDb, QDomDocument& oDocument)
{
    SKGError err;
    SKGTRACEINRC(10, "SKGServices::copySqliteDatabaseToXml", err);
    if (iDb) {
        oDocument = QDomDocument("SKGML");
        QDomElement document = oDocument.createElement("document");
        oDocument.appendChild(document);

        // Copy the tables
        QStringList listTables = iDb->tables();
        listTables.removeAll("sqlite_sequence");
        listTables.removeAll("sqlite_stat1");
        int nb = listTables.count();
        for (int i = 0; !err && i < nb; ++i) {
            QString tableName = listTables.at(i);

            QDomElement table = oDocument.createElement("table");
            document.appendChild(table);
            table.setAttribute("name", tableName);

            SKGStringListList listRows;
            err = SKGServices::executeSelectSqliteOrder(iDb, "SELECT * FROM " % tableName, listRows);
            int nbRows = listRows.count();
            if (nbRows) {
                QStringList titles = listRows.at(0);
                for (int j = 1; !err && j < nbRows; ++j) { //Forget title
                    QStringList values = listRows.at(j);

                    QDomElement row = oDocument.createElement("row");
                    table.appendChild(row);

                    int nbVals = values.count();
                    for (int k = 0; k < nbVals; ++k) {
                        row.setAttribute(titles.at(k), values.at(k));
                    }
                }
            }
        }
    }
    return err;
}

SKGError SKGServices::copySqliteDatabase(QSqlDatabase* iFileDb, QSqlDatabase* iMemoryDb, bool iFromFileToMemory)
{
    SKGError err;
    SKGTRACEINRC(10, "SKGServices::copySqliteDatabase", err);
    if (iFileDb && iMemoryDb) {
        SKGTRACEL(20) << "Input parameter [iFileDb]=[" << iFileDb->databaseName() << ']' << endl;
        SKGTRACEL(20) << "Input parameter [iMemoryDb]=[" << iMemoryDb->databaseName() << ']' << endl;
        SKGTRACEL(10) << "Input parameter [iFromFileToMemory]=[" << (iFromFileToMemory ? "FILE->MEMORY" : "MEMORY->FILE") << ']' << endl;

        QString dbFileName = iFileDb->databaseName();
        // Copy the tables
        SKGStringListList listTables;
        int nb = 0;
        if (!err) {
            SKGTRACEINRC(10, "SKGServices::copySqliteDatabase-COPYTABLES", err);
            err = SKGServices::executeSelectSqliteOrder((iFromFileToMemory ? iFileDb : iMemoryDb),
                    "SELECT sql, tbl_name FROM sqlite_master WHERE type='table' AND sql NOT NULL and name NOT IN ('sqlite_stat1', 'sqlite_sequence')",
                    listTables);

            nb = listTables.count();
            for (int i = 1; !err && i < nb; ++i) { //Forget header
                QString val = listTables.at(i).at(0);
                err = SKGServices::executeSqliteOrder((iFromFileToMemory ? iMemoryDb : iFileDb), val);
            }
        }
        //Attach db
        if (!err) {
            SKGTRACEINRC(10, "SKGServices::copySqliteDatabase-ATTACH", err);
            err = SKGServices::executeSqliteOrder(iMemoryDb, "ATTACH DATABASE '" % dbFileName % "' as source");
        }

        //Copy records
        if (!err) {
            SKGTRACEINRC(10, "SKGServices::copySqliteDatabase-COPY", err);
            err = SKGServices::executeSqliteOrder(iMemoryDb, "BEGIN");
            if (!err) {
                for (int i = 1; !err && i < nb; ++i) { //Forget header
                    QString val = listTables.at(i).at(1);
                    if (iFromFileToMemory)  err = SKGServices::executeSqliteOrder(iMemoryDb, "insert into main." % val % " select * from source." % val);
                    else  err = SKGServices::executeSqliteOrder(iMemoryDb, "insert into source." % val % " select * from main." % val);
                }

            }
            SKGServices::executeSqliteOrder(iMemoryDb, "COMMIT");
        }

        //Detach
        {
            SKGTRACEINRC(10, "SKGServices::copySqliteDatabase-DETACH", err);
            SKGError err2 = SKGServices::executeSqliteOrder(iMemoryDb, "DETACH DATABASE source");
            if (!err && err2) err = err2;
        }

        //Optimization
        if (!err) {

            QStringList optimization;
            optimization << "PRAGMA case_sensitive_like=true"
                         << "PRAGMA journal_mode=MEMORY"
                         << "PRAGMA temp_store=MEMORY"
                         << "PRAGMA locking_mode=EXCLUSIVE"
                         << "PRAGMA synchronous = OFF"
                         << "PRAGMA recursive_triggers=true"
                         ;
            err = SKGServices::executeSqliteOrders(iFromFileToMemory ? iMemoryDb : iFileDb, optimization);
        }

        // Copy the triggers
        if (!err) {
            SKGTRACEINRC(10, "SKGServices::copySqliteDatabase-COPYTRIGGER", err);
            SKGStringListList listSqlOrder;
            err = SKGServices::executeSelectSqliteOrder((iFromFileToMemory ? iFileDb : iMemoryDb),
                    "SELECT sql FROM sqlite_master WHERE type='trigger' AND sql NOT NULL and name NOT IN ('sqlite_stat1', 'sqlite_sequence')",
                    listSqlOrder);

            int nb = listSqlOrder.count();
            for (int i = 1; !err && i < nb; ++i) { //Forget header
                QString val = listSqlOrder.at(i).at(0);
                err = SKGServices::executeSqliteOrder((iFromFileToMemory ? iMemoryDb : iFileDb), val);
            }
        }

        // Copy the indexes
        if (!err) {
            SKGTRACEINRC(10, "SKGServices::copySqliteDatabase-COPYINDEX", err);
            SKGStringListList listSqlOrder;
            err = SKGServices::executeSelectSqliteOrder((iFromFileToMemory ? iFileDb : iMemoryDb),
                    "SELECT sql FROM sqlite_master WHERE type='index' AND sql NOT NULL and name NOT IN ('sqlite_stat1', 'sqlite_sequence')",
                    listSqlOrder);

            int nb = listSqlOrder.count();
            for (int i = 1; !err && i < nb; ++i) { //Forget header
                QString val = listSqlOrder.at(i).at(0);
                err = SKGServices::executeSqliteOrder((iFromFileToMemory ? iMemoryDb : iFileDb), val);
            }
        }

        // Copy the views
        if (!err) {
            SKGTRACEINRC(10, "SKGServices::copySqliteDatabase-COPYVIEW", err);
            SKGStringListList listSqlOrder;
            err = SKGServices::executeSelectSqliteOrder((iFromFileToMemory ? iFileDb : iMemoryDb),
                    "SELECT sql FROM sqlite_master WHERE type='view' AND sql NOT NULL and name NOT IN ('sqlite_stat1', 'sqlite_sequence')",
                    listSqlOrder);

            int nb = listSqlOrder.count();
            for (int i = 1; !err && i < nb; ++i) { //Forget header
                QString val = listSqlOrder.at(i).at(0);
                err = SKGServices::executeSqliteOrder((iFromFileToMemory ? iMemoryDb : iFileDb), val);
            }
        }

        //Check if created file exists
        if (!err && !iFromFileToMemory && !QFile(dbFileName).exists()) {
            //Set error message
            err.setReturnCode(ERR_FAIL);
            err.setMessage(i18nc("An error message: creating a file failed", "Creation file '%1' failed", dbFileName));
        }
    }
    if (err) err.addError(SQLLITEERROR + ERR_FAIL, i18nc("Error message: something failed", "%1 failed", QString("copySqliteDatabase::copySqliteDatabase()")));
    return err;
}

SKGError SKGServices::executeSqliteOrders(QSqlDatabase* iDb, const QStringList& iSqlOrders)
{
    SKGError err;
    _SKGTRACEINRC(10, "SKGServices::executeSqliteOrder(QSqlDatabase)", err);
    int nb = iSqlOrders.count();
    for (int i = 0; !err && i < nb; ++i) {
        err = SKGServices::executeSqliteOrder(iDb, iSqlOrders.at(i));
    }
    return err;
}

SKGError SKGServices::executeSqliteOrder(QSqlDatabase* iDb, const QString& iSqlOrder, int* iLastId)
{
    SKGError err;
    _SKGTRACEINRC(10, "SKGServices::executeSqliteOrder(QSqlDatabase)", err);
    SKGTRACEL(20) << "Input parameter [iSqlOrder]=[" << iSqlOrder << ']' << endl;

    if (iDb == NULL) {
        err = SKGError(ERR_POINTER, i18nc("Error message", "No database defined"));
    } else {
        QSqlQuery query(QString(), *iDb);
        double elapse = 0;
        if (SKGServices::SKGSqlTraces != -1)  elapse = SKGServices::getMicroTime();

        if (!query.exec(iSqlOrder)) {
            QSqlError sqlError = query.lastError();
            if (sqlError.number() != 19 /*SQLITE_CONSTRAINT*/) {
                SKGTRACE << "WARNING: " << iSqlOrder << endl;
                SKGTRACE << "         returns :" << sqlError.text() << endl;
            }

            err = SKGError(SQLLITEERROR + sqlError.number(), iSqlOrder);
            err.addError(SQLLITEERROR + sqlError.number(), sqlError.text());
        } else {
            if (iLastId) *iLastId = query.lastInsertId().toInt();
        }
        if (SKGServices::SKGSqlTraces != -1) {
            elapse = SKGServices::getMicroTime() - elapse;
            if (elapse >= SKGServices::SKGSqlTraces) {
                SKGTRACE << "executeSqliteOrder :" << iSqlOrder << " TIME=" << elapse << " ms" << endl;
            }
        }
    }
    return err;
}

SKGError SKGServices::dumpSelectSqliteOrder(QSqlDatabase* iDb, const QString& iSqlOrder, QTextStream* oStream, SKGServices::DumpMode iMode)
{
    SKGError err;
    _SKGTRACEINRC(10, "SKGServices::dumpSelectSqliteOrder(QSqlDatabase)", err);
    SKGTRACEL(20) << "Input parameter [iSqlOrder]=[" << iSqlOrder << ']' << endl;

    //initialisation
    QStringList oResult;
    err = SKGServices::dumpSelectSqliteOrder(iDb, iSqlOrder, oResult, iMode);
    if (!err) {
        //dump
        int nb = oResult.size();
        for (int i = 0; i < nb; ++i) {
            if (oStream == NULL)  SKGTRACESUITE << oResult.at(i) << endl;
            else  *oStream << oResult.at(i) << endl;
        }
    }
    return err;
}

SKGError SKGServices::dumpSelectSqliteOrder(QSqlDatabase* iDb, const QString& iSqlOrder, QString& oResult, SKGServices::DumpMode iMode)
{
    SKGError err;
    _SKGTRACEINRC(10, "SKGServices::dumpSelectSqliteOrder(QSqlDatabase)", err);
    //initialisation
    oResult = "";

    QStringList oResultTmp;
    err = SKGServices::dumpSelectSqliteOrder(iDb, iSqlOrder, oResultTmp, iMode);
    if (!err) {
        //dump
        int nb = oResultTmp.size();
        for (int i = 0; i < nb; ++i) {
            oResult += oResultTmp.at(i) % '\n';
        }
    }
    return err;
}

SKGError SKGServices::dumpSelectSqliteOrder(QSqlDatabase* iDb, const QString& iSqlOrder, QStringList& oResult, SKGServices::DumpMode iMode)
{
    SKGError err;
    _SKGTRACEINRC(10, "SKGServices::dumpSelectSqliteOrder(QSqlDatabase)", err);

    //Execution of sql order
    SKGStringListList oResultTmp;
    err = executeSelectSqliteOrder(iDb, iSqlOrder, oResultTmp);
    if (!err) oResult = tableToDump(oResultTmp, iMode);
    return err;
}

SKGError SKGServices::executeSelectSqliteOrder(QSqlDatabase* iDb, const QString& iSqlOrder, SKGStringListList& oResult)
{
    SKGError err;
    _SKGTRACEINRC(10, "SKGServices::executeSelectSqliteOrder(QSqlDatabase)", err);
    //initialisation
    oResult.clear();

    if (iDb == NULL) err = SKGError(ERR_POINTER, i18nc("Error message", "No database defined"));
    else {
        QSqlQuery query(QString(), *iDb);
        double elapse = 0;
        if (SKGServices::SKGSqlTraces != -1)  elapse = SKGServices::getMicroTime();

        if (!query.exec(iSqlOrder)) {
            QSqlError sqlError = query.lastError();
            SKGTRACE << "WARNING: " << iSqlOrder << endl;
            SKGTRACE << "         returns :" << sqlError.text() << endl;
            err = SKGError(SQLLITEERROR + sqlError.number(), iSqlOrder);
            err.addError(SQLLITEERROR + sqlError.number(), sqlError.text());
        } else {
            if (SKGServices::SKGSqlTraces != -1) {
                double elapse1 = SKGServices::getMicroTime() - elapse;
                if (elapse1 >= SKGServices::SKGSqlTraces) {
                    SKGTRACE << "executeSqliteOrder :" << iSqlOrder << " TIME=" << elapse1 << " ms" << endl;
                }
            }

            //Addition of column names
            QSqlRecord rec = query.record();
            QStringList line;
            int index = 0;
            while (index != -1) {
                QString val = rec.fieldName(index);
                if (!val.isEmpty()) {
                    line.push_back(val);
                    ++index;
                } else {
                    index = -1;
                }
            }
            oResult.push_back(line);

            //Addition of rows
            while (query.next()) {
                QStringList line;
                int index = 0;
                while (index != -1) {
                    QVariant val = query.value(index);
                    if (val.isValid()) {
                        line.push_back(val.toString());
                        ++index;
                    } else {
                        index = -1;
                    }
                }
                oResult.push_back(line);
            }
            if (SKGServices::SKGSqlTraces != -1) {
                double elapse1 = SKGServices::getMicroTime() - elapse;
                if (elapse1 >= SKGServices::SKGSqlTraces) {
                    SKGTRACE << "executeSqliteOrder (with fetch) :" << iSqlOrder << " TIME=" << elapse1 << " ms" << endl;
                }
            }
        }
    }
    return err;
}

SKGError SKGServices::readPropertyFile(const QString& iFileName, QHash< QString, QString >& oProperties)
{
    SKGError err;
    oProperties.clear();

    //Open file
    QFile file(iFileName);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        err = SKGError(ERR_FAIL, i18nc("An erro message", "Open file '%1' failed", iFileName));
    } else {
        //Read file
        QTextStream stream(&file);
        while (!stream.atEnd() && !err) {
            //Read line
            QString line = stream.readLine().trimmed();
            if (!line.isEmpty() && !line.startsWith(QLatin1String("#"))) {
                int pos = line.indexOf("=");
                if (pos != -1) {
                    oProperties[line.left(pos).trimmed().toLower()] = line.right(line.count() - pos - 1);
                }
            }
        }

        //close file
        file.close();
    }
    return err;
}

double SKGServices::getMicroTime()
{
#ifdef Q_OS_WIN
    return static_cast<double>(GetTickCount());
#else
    struct timeval tv;
    struct timezone tz;

    //get time
    gettimeofday(&tv, &tz);

    //return time
    return (static_cast<double>(1000.0 * tv.tv_sec)) + (static_cast<double>(tv.tv_usec / 1000));
#endif
}

SKGStringListList SKGServices::getBase100Table(const SKGStringListList& iTable)
{
    SKGTRACEIN(10, "SKGServices::getBase100Table");

    //Build history
    SKGStringListList output;

    output.push_back(iTable.at(0));

    QStringList newLine;
    int nblines = iTable.count();
    int nbCols = 0;
    if (nblines) {
        nbCols = iTable.at(0).count();
    }

    //Create table
    for (int i = 1; i < nblines; ++i) {
        QStringList newLine;
        newLine.push_back(iTable.at(i).at(0));

        double valInitial = 0;

        for (int j = 1; j < nbCols; ++j) {
            double val = SKGServices::stringToDouble(iTable.at(i).at(j));
            if (j == 1) {
                valInitial = val;
                val = 100.0;
            } else {
                if (valInitial != 0.0)val = 100.0 * val / valInitial;
            }
            newLine.push_back(SKGServices::doubleToString(val));
        }
        output.push_back(newLine);
    }

    return output;
}

SKGStringListList SKGServices::getPercentTable(const SKGStringListList& iTable, bool iOfColumns, bool iAbsolute)
{
    SKGTRACEIN(10, "SKGServices::getPercentTable");

    //Build history
    SKGStringListList output;

    output.push_back(iTable.at(0));

    QStringList newLine;
    int nblines = iTable.count();
    int nbCols = 0;
    if (nblines) {
        nbCols = iTable.at(0).count();
    }

    //Compute sums
    QList<double> sums;
    if (iOfColumns) {
        //Compute sum of columns
        for (int j = 1; j < nbCols; ++j) {

            //Compute sum
            double sum = 0;
            for (int i = 1; i < nblines; ++i) {
                double v = SKGServices::stringToDouble(iTable.at(i).at(j));
                sum += (iAbsolute ? qAbs(v) : v);
            }

            sums.push_back(sum);
        }
    } else {
        //Compute sum of lines
        for (int j = 1; j < nblines; ++j) {

            //Compute sum
            double sum = 0;
            for (int i = 1; i < nbCols; ++i) {
                double v = SKGServices::stringToDouble(iTable.at(j).at(i));
                sum += (iAbsolute ? qAbs(v) : v);
            }

            sums.push_back(sum);
        }
    }

    //Create table
    for (int i = 1; i < nblines; ++i) {
        QStringList newLine;
        newLine.push_back(iTable.at(i).at(0));

        for (int j = 1; j < nbCols; ++j) {
            double val = SKGServices::stringToDouble(iTable.at(i).at(j));
            val = (iAbsolute ? qAbs(val) : val);
            double sum = (iOfColumns ? sums.at(j - 1) : sums.at(i - 1));
            newLine.push_back(SKGServices::doubleToString(sum == 0.0 ? 0.0 : 100.0 * val / sum));
        }
        output.push_back(newLine);
    }

    return output;
}

SKGStringListList SKGServices::getHistorizedTable(const SKGStringListList& iTable)
{
    SKGTRACEIN(10, "SKGServices::getHistorizedTable");

    //Build history
    SKGStringListList output;

    output.push_back(iTable.at(0));

    QStringList newLine;
    int nblines = iTable.count();
    int nbCols = 0;
    if (nblines) {
        nbCols = iTable.at(0).count();
    }
    for (int i = 1; i < nblines; ++i) {
        QStringList newLine;
        newLine.push_back(iTable.at(i).at(0));

        double sum = 0;
        for (int j = 1; j < nbCols; ++j) {
            sum += SKGServices::stringToDouble(iTable.at(i).at(j));
            newLine.push_back(SKGServices::doubleToString(sum));
        }
        output.push_back(newLine);
    }

    return output;
}

#include "skgservices.moc"
