/***************************************************************************
*   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 defines classes SKGUnitObject.
*
* @author Stephane MANKOWSKI / Guillaume DE BURE
*/
#include "skgunitobject.h"
#include "skgunitvalueobject.h"
#include "skgdocumentbank.h"
#include "skgoperationobject.h"
#include "skgtransactionmng.h"
#include "skgtraces.h"

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

#include <QRegExp>
#include <QFileInfo>
#include <QFile>
#include <qmath.h>
#include <qdesktopservices.h>

SKGUnitObject::SKGUnitObject(SKGDocument* iDocument, int iID)
    : SKGNamedObject(iDocument, "v_unit", iID)
{
}

SKGUnitObject::~SKGUnitObject()
{
}

SKGUnitObject::SKGUnitObject(const SKGUnitObject& iObject)
    : SKGNamedObject(iObject)
{
}

SKGUnitObject::SKGUnitObject(const SKGNamedObject& iObject)
{
    if (iObject.getRealTable() == "unit") {
        copyFrom(iObject);
    } else {
        *this = SKGNamedObject(iObject.getDocument(), "v_unit", iObject.getID());
    }
}

SKGUnitObject::SKGUnitObject(const SKGObjectBase& iObject)
{
    if (iObject.getRealTable() == "unit") {
        copyFrom(iObject);
    } else {
        *this = SKGNamedObject(iObject.getDocument(), "v_unit", iObject.getID());
    }
}

const SKGUnitObject& SKGUnitObject::operator= (const SKGObjectBase& iObject)
{
    copyFrom(iObject);
    return *this;
}

QString SKGUnitObject::getWhereclauseId() const
{
    QString output = SKGObjectBase::getWhereclauseId();
    if (output.isEmpty()) {
        QString name = getName();
        if (!name.isEmpty()) output = "t_name='" % SKGServices::stringToSqlString(name) % '\'';

        QString symbol = getSymbol();
        if (!symbol.isEmpty()) {
            if (!output.isEmpty()) output += " OR ";
            output += "t_symbol='" % SKGServices::stringToSqlString(symbol) % '\'';
        }
        if (!output.isEmpty()) output = '(' % output % ')';
    }

    return output;
}

QStringList SKGUnitObject::getListofKnownCurrencies(bool iIncludingObsolete)
{
    SKGTRACEIN(10, "SKGUnitObject::getListofKnownCurrencies");
    QStringList output;
    QStringList units = KCurrencyCode::allCurrencyCodesList(iIncludingObsolete ? KCurrencyCode::ActiveCurrency | KCurrencyCode::SuspendedCurrency | KCurrencyCode::ObsoleteCurrency : KCurrencyCode::ActiveCurrency);
    int nb = units.count();
    for (int i = 0; i < nb; ++i) {
        output << KCurrencyCode::currencyCodeToName(units.at(i), KGlobal::locale() == NULL ? "en" : "") % " (" % units.at(i) % ')';
    }
    output.sort();

    output
            << i18nc("Noun, a currency", "CAC 40")
            << i18nc("Noun, a currency", "Dow Jones (DJIA)")
            << i18nc("Noun, a currency", "NASDAQ")
            << i18nc("Noun, a currency", "SBF 120")
            << i18nc("Noun, a currency", "S&P 500")
            << i18nc("Noun, a currency", "FTSE 100")
            << i18nc("Noun, a currency", "DAX")
            << i18nc("Noun, a currency", "NIKKEI 225")
            << i18nc("Noun, a currency", "HANG SENG")
            << i18nc("Noun, a currency", "STRAITS TIMES");

    return output;
}

QString SKGUnitObject::getInternationalCode(const QString& iUnitName)
{
    SKGTRACEIN(10, "SKGUnitObject::getInternationalCode");
    QString output = iUnitName;
    QRegExp rx(".*\\(([^\\(\\)]+)\\)[^\\(\\)]*");
    if (rx.indexIn(iUnitName) != -1) {
        output = rx.cap(1);
    }

    return output;
}

SKGServices::SKGUnitInfo SKGUnitObject::getUnitInfo()
{
    SKGTRACEIN(10, "SKGUnitObject::getUnitInfo");
    SKGServices::SKGUnitInfo info;
    info.Name = getName();
    info.Value = getAmount();
    info.NbDecimal = getNumberDecimal();
    info.Symbol = getSymbol();
    info.Country = getCountry();
    info.Internet = getInternetCode();
    info.Date = QDate::currentDate();

    return info;
}

SKGServices::SKGUnitInfo SKGUnitObject::getUnitInfo(const QString& iUnitName)
{
    SKGTRACEIN(10, "SKGUnitObject::getUnitInfo");
    SKGServices::SKGUnitInfo info;
    info.NbDecimal = 2;
    info.Value = -1;

    QString isoCode = getInternationalCode(iUnitName);

    //Principal units
    if (KCurrencyCode::isValid(isoCode)) {
        KCurrencyCode unitCode(isoCode, KGlobal::locale() == NULL ? "en" : "");
        info.Name = unitCode.name() % " (" % unitCode.isoCurrencyCode() % ')';
        info.Symbol = unitCode.unambiguousSymbol();
        if (info.Symbol.isEmpty()) info.Symbol = unitCode.name();
        QStringList counties = unitCode.countriesUsingCurrency();
        if (counties.count()) info.Country = counties.at(0);
        info.Date = unitCode.dateIntroduced() ;
        if (!info.Date.isValid()) info.Date = QDate::currentDate() ;
        info.Value = 1;
        info.NbDecimal = unitCode.decimalPlaces();
    }
    //Indexes
    else if (iUnitName == i18nc("Noun, a currency", "CAC 40")) {
        //US Dollar
        info.Name = iUnitName;
        info.Symbol = iUnitName;
        info.Country = i18nc("Noun, a country", "France");
        info.Date = QDate(1987, 1, 1);
        info.Internet = "^FCHI";
    } else if (iUnitName == i18nc("Noun, a currency", "NASDAQ")) {
        //NASDAQ
        info.Name = iUnitName;
        info.Symbol = iUnitName;
        info.Country = i18nc("Noun, a country", "United States");
        info.Date = QDate(1971, 2, 5);
        info.Internet = "^IXIC";
    } else if (iUnitName == i18nc("Noun, a currency", "Dow Jones (DJIA)") || iUnitName == "DJIA") {
        //Dow Jones
        info.Name = iUnitName;
        info.Symbol = "DJIA";
        info.Country = i18nc("Noun, a country", "United States");
        info.Date = QDate(1884, 1, 1);
        info.Internet = "^DJI";
    } else if (iUnitName == i18nc("Noun, a currency", "SBF 120")) {
        //SBF 120
        info.Name = iUnitName;
        info.Symbol = iUnitName;
        info.Country = i18nc("Noun, a country", "France");
        info.Date = QDate(1990, 12, 31);
        info.Internet = "^SBF120";
    } else if (iUnitName == i18nc("Noun, a currency", "S&P 500")) {
        //S&P 500
        info.Name = iUnitName;
        info.Symbol = iUnitName;
        info.Country = i18nc("Noun, a country", "United States");
        info.Date = QDate(1920, 1, 1);
        info.Internet = "^GSPC";
    } else if (iUnitName == i18nc("Noun, a currency", "FTSE 100")) {
        //FTSE 100
        info.Name = iUnitName;
        info.Symbol = iUnitName;
        info.Country = i18nc("Noun, a country", "England");
        info.Date = QDate(1984, 1, 3);
        info.Internet = "^FTSE";
    } else if (iUnitName == i18nc("Noun, a currency", "DAX")) {
        //DAX
        info.Name = iUnitName;
        info.Symbol = iUnitName;
        info.Country = i18nc("Noun, a country", "Germany");
        info.Date = QDate(1920, 1, 1);
        info.Internet = "^GDAXI";
    } else if (iUnitName == i18nc("Noun, a currency", "NIKKEI 225")) {
        //NIKKEI 225
        info.Name = iUnitName;
        info.Symbol = iUnitName;
        info.Country = i18nc("Noun, a country", "Japan");
        info.Date = QDate(1920, 1, 1);
        info.Internet = "^N225";
    } else if (iUnitName == i18nc("Noun, a currency", "HANG SENG")) {
        //HANG SENG
        info.Name = iUnitName;
        info.Symbol = iUnitName;
        info.Country = i18nc("Noun, a country", "China");
        info.Date = QDate(1920, 1, 1);
        info.Internet = "^HSI";
    } else if (iUnitName == i18nc("Noun, a currency", "STRAITS TIMES")) {
        //STRAITS TIMES
        info.Name = iUnitName;
        info.Symbol = iUnitName;
        info.Date = QDate(1920, 1, 1);
        info.Country = i18nc("Noun, a country", "Singapore");
        info.Internet = "^STI";
    }

    return info;
}


SKGError SKGUnitObject::createCurrencyUnit(SKGDocumentBank* iDocument, const QString& iUnitName, SKGUnitObject& oUnit)
{
    SKGError err;
    if (iDocument) {
        SKGUnitObject parentUnit;
        oUnit = SKGUnitObject(iDocument);

        SKGUnitObject::UnitType type = SKGUnitObject::CURRENCY;
        SKGServices::SKGUnitInfo prim = iDocument->getPrimaryUnit();
        SKGServices::SKGUnitInfo seco = iDocument->getSecondaryUnit();

        //Get information on the unit
        SKGServices::SKGUnitInfo info = getUnitInfo(iUnitName);
        if (info.Name.isEmpty()) err = SKGError(ERR_INVALIDARG, i18nc("Error message", "Unknown unit '%1'", iUnitName));
        if (!err && !info.Parent.isEmpty()) err = createCurrencyUnit(iDocument, info.Parent, parentUnit);

        //Set the type
        if (info.Name == info.Symbol) {
            //This is an index
            type = SKGUnitObject::INDEX;
        } else if (!info.Parent.isEmpty()) {
            //This is a secondary unit
            type = (seco.Symbol.isEmpty() || seco.Symbol == info.Symbol  ? SKGUnitObject::SECONDARY : SKGUnitObject::CURRENCY);
        } else {
            //As primary
            type = (prim.Symbol.isEmpty() || prim.Symbol == info.Symbol ? SKGUnitObject::PRIMARY : SKGUnitObject::CURRENCY);
        }

        //Point on primary unit
        if (info.Value == 1 && !err && (type == SKGUnitObject::CURRENCY || type == SKGUnitObject::SECONDARY)) {
            SKGUnitObject primunit(iDocument);
            err = primunit.setSymbol(prim.Symbol);
            if (!err) err = primunit.load();
            if (!err) {
                QString codeprimunit = getInternationalCode(primunit.getName());
                QString codeunit = getInternationalCode(info.Name);
                if (!codeprimunit.isEmpty()) {
                    info.Internet = codeunit % codeprimunit % "=X";
                    info.Value = -1;

                    parentUnit = SKGUnitObject(iDocument);
                    err = parentUnit.setSymbol(prim.Symbol);
                    if (!err) err = parentUnit.load();
                }
            }

        }

        if (!err) err = oUnit.setName(info.Name);
        if (!err && oUnit.exist()) err = oUnit.load();
        if (!err) err = oUnit.setType(type);
        if (!err) err = oUnit.setSymbol(info.Symbol);
        if (!err) err = oUnit.setInternetCode(info.Internet);
        if (!err) err = oUnit.setCountry(info.Country);
        if (!err) err = oUnit.setNumberDecimal(info.NbDecimal);
        if (!err && parentUnit.exist()) err = oUnit.setUnit(parentUnit);
        if (!err) err = oUnit.save();

        //Creation of the value
        if (info.Value > 0) {
            SKGUnitValueObject unitValue;
            if (!err) err = oUnit.addUnitValue(unitValue);
            if (!err) err = unitValue.setDate(info.Date);
            if (!err) err = unitValue.setQuantity(info.Value);
            if (!err) err = unitValue.save();
        }
    }
    return err;
}

double SKGUnitObject::convert(double iValue, const SKGUnitObject& iUnitFrom, const SKGUnitObject& iUnitTo)
{
    double output = iValue;
    if (iUnitFrom != iUnitTo) {
        double valFrom = SKGServices::stringToDouble(iUnitFrom.getAttribute("f_CURRENTAMOUNT"));
        double valTo = SKGServices::stringToDouble(iUnitTo.getAttribute("f_CURRENTAMOUNT"));
        output = iValue * valFrom / valTo;
    }
    return output;
}

SKGError SKGUnitObject::setSymbol(const QString& iSymbol)
{
    return setAttribute("t_symbol", iSymbol);
}

QString SKGUnitObject::getSymbol() const
{
    return getAttribute("t_symbol");
}

QString SKGUnitObject::getDownloadSource() const
{
    return getAttribute("t_source");
}

SKGError SKGUnitObject::setDownloadSource(const QString& iSource)
{
    return setAttribute("t_source", iSource);
}

SKGError SKGUnitObject::setNumberDecimal(int iNb)
{
    return setAttribute("i_nbdecimal", SKGServices::intToString(iNb));
}

int SKGUnitObject::getNumberDecimal() const
{
    return SKGServices::stringToInt(getAttribute("i_nbdecimal"));
}

SKGError SKGUnitObject::setCountry(const QString& iCountry)
{
    return setAttribute("t_country", iCountry);
}

QString SKGUnitObject::getCountry() const
{
    return getAttribute("t_country");
}

SKGError SKGUnitObject::setInternetCode(const QString& iCode)
{
    return setAttribute("t_internet_code", iCode);
}

QString SKGUnitObject::getInternetCode() const
{
    return getAttribute("t_internet_code");
}

SKGError SKGUnitObject::setType(SKGUnitObject::UnitType iType)
{
    SKGError err;
    if (getAttribute("t_type").isEmpty() || this->getType() != iType) {
        //Guaranty that PRIMARY and SECONDARY is unique
        if (iType == PRIMARY || iType == SECONDARY) {
            //Set old SECONDARY as CURRENCY
            err = getDocument()->executeSqliteOrder("UPDATE unit SET t_type='C' WHERE t_type='2'");

            //Set old PRIMARY as SECONDARY
            if (!err && iType == PRIMARY) err = getDocument()->executeSqliteOrder("UPDATE unit SET t_type='2' WHERE t_type='1'");
        }
    }
    if (!err) err = setAttribute("t_type", (iType == CURRENCY ? "C" : (iType == PRIMARY ? "1" : (iType == SECONDARY ? "2" : (iType == SHARE ? "S" : (iType == INDEX ? "I" : "O"))))));
    return err;
}

SKGUnitObject::UnitType SKGUnitObject::getType() const
{
    QString typeString = getAttribute("t_type");
    return (typeString == "C" ? CURRENCY : (typeString == "S" ? SHARE : (typeString == "1" ? PRIMARY : (typeString == "2" ? SECONDARY : (typeString == "I" ? INDEX : OBJECT)))));
}

SKGError SKGUnitObject::getUnit(SKGUnitObject& oUnit) const
{
    SKGError err;
    if (getDocument()) err = getDocument()->getObject("v_unit", "id=" % getAttribute("rd_unit_id"), oUnit);
    return err;
}

SKGError SKGUnitObject::setUnit(const SKGUnitObject& iUnit)
{
    SKGError err;
    if (*this == iUnit && iUnit.getID() != 0) err = SKGError(ERR_INVALIDARG, i18nc("Error message", "Reference unit of a unit cannot be itself."));
    else {
        err = setAttribute("rd_unit_id", SKGServices::intToString(iUnit.getID()));
    }
    return err;
}

SKGError SKGUnitObject::removeUnit()
{
    return setAttribute("rd_unit_id", "0");
}

SKGError SKGUnitObject::addUnitValue(SKGUnitValueObject& oUnitValue)
{
    SKGError err;
    if (getID() == 0) err = SKGError(ERR_FAIL, i18nc("Error message", "%1 failed because linked object is not yet saved in the database.", QString("SKGUnitObject::addUnitValue")));
    else {
        oUnitValue = SKGUnitValueObject(static_cast<SKGDocumentBank*>(getDocument()));
        err = oUnitValue.setAttribute("rd_unit_id", SKGServices::intToString(getID()));
    }
    return err;
}

SKGError SKGUnitObject::simplify()
{
    SKGListSKGObjectBase values;
    SKGError err = getUnitValues(values);
    int nb = values.count();
    if (!err && nb) {
        QHash<QString, SKGListSKGObjectBase> groups;

        SKGBEGINPROGRESSTRANSACTION(*getDocument(), "#INTERNAL#" % i18nc("Progression step", "Simplify unit"), err, 2);
        //Build groups
        {
            SKGBEGINPROGRESSTRANSACTION(*getDocument(), "#INTERNAL#" % i18nc("Progression step", "Analyze unit"), err, nb);
            QDate limit1 = QDate::currentDate().addMonths(-3);
            QDate limit2 = QDate::currentDate().addYears(-1);
            QDate limit3 = QDate::currentDate().addYears(-3);
            for (int i = nb - 1; !err && i >= 0 ; --i) {
                SKGUnitValueObject v = values.at(i);
                QDate d = v.getDate();
                if (d >= limit1) {
                    //No simplification ==> nothing to do
                } else if (d >= limit2) {
                    //Simplification by group of 30 days
                    QString key = limit1.addDays(30 * (limit1.daysTo(d) / 30)).toString();
                    SKGListSKGObjectBase group = groups[key];
                    group.push_back(v);
                    groups[key] = group;

                } else if (d >= limit3) {
                    //Simplification by group of 2 months
                    QString key = limit2.addDays(60 * (limit2.daysTo(d) / 60)).toString();
                    SKGListSKGObjectBase group = groups[key];
                    group.push_back(v);
                    groups[key] = group;
                } else {
                    //Simplification by group of 6 months
                    QString key = limit3.addDays(180 * (limit2.daysTo(d) / 180)).toString();
                    SKGListSKGObjectBase group = groups[key];
                    group.push_back(v);
                    groups[key] = group;
                }

                if (!err) err = getDocument()->stepForward(nb - i);
            }
        }
        if (!err) err = getDocument()->stepForward(1);

        //Simplify
        {
            QList<QString> keys = groups.keys();
            nb = keys.count();
            SKGBEGINPROGRESSTRANSACTION(*getDocument(), "#INTERNAL#" % i18nc("Progression step", "Remove useless values"), err, nb);
            for (int i = 0; !err && i < nb ; ++i) {
                QString k = keys.at(i);
                SKGListSKGObjectBase group = groups[k];

                //Simplify the group
                int nb2 = group.count();

                //Compute min and max
                double min = 10e20;
                double max = 0;
                for (int j = 0; j < nb2; ++j) {
                    SKGUnitValueObject v1 = group.at(j);
                    double y1 = v1.getQuantity();
                    min = qMin(y1, min);
                    max = qMax(y1, max);
                }

                //Simplify
                for (int j = 1; !err && j < nb2 - 1; ++j) {
                    SKGUnitValueObject v1 = group.at(j);
                    double y1 = v1.getQuantity();
                    if (y1 != min && y1 != max) {
                        err = v1.remove();
                    }
                }

                if (!err) err = getDocument()->stepForward(i + 1);
            }
        }
        if (!err) err = getDocument()->stepForward(2);
    }
    return err;
}

SKGError SKGUnitObject::getUnitValues(SKGListSKGObjectBase& oUnitValueList) const
{
    SKGError err = getDocument()->getObjects("v_unitvalue",
                   "rd_unit_id=" % SKGServices::intToString(getID()) % " ORDER BY d_date",
                   oUnitValueList);
    return err;
}

SKGError SKGUnitObject::getLastUnitValue(SKGUnitValueObject& oUnitValue) const
{
    return SKGObjectBase::getDocument()->getObject("v_unitvalue",
            "rd_unit_id=" % SKGServices::intToString(getID()) % " AND d_date=(select MAX(u2.d_date) from unitvalue u2 where u2.rd_unit_id=" % SKGServices::intToString(getID()) % ')',
            oUnitValue);
}

SKGError SKGUnitObject::getUnitValue(const QDate& iDate, SKGUnitValueObject& oUnitValue) const
{
    QString ids = SKGServices::intToString(getID());
    QString dates = SKGServices::dateToSqlString(QDateTime(iDate));
    SKGError err = SKGObjectBase::getDocument()->getObject("v_unitvalue",
                   "rd_unit_id=" % ids % " AND d_date<='" % dates %
                   "' AND  ABS(strftime('%s','" % dates %
                   "')-strftime('%s',d_date))=(select MIN(ABS(strftime('%s','" % dates %
                   "')-strftime('%s',u2.d_date))) from unitvalue u2 where u2.rd_unit_id=" % ids %
                   " AND u2.d_date<='" % dates % "')",
                   oUnitValue);

    //If not found then get first
    if (err) err = SKGObjectBase::getDocument()->getObject("v_unitvalue",
                       "rd_unit_id=" % SKGServices::intToString(getID()) % " AND d_date=(select MIN(d_date) from unitvalue where rd_unit_id=" %
                       SKGServices::intToString(getID()) % ')',
                       oUnitValue);
    return err;
}

double SKGUnitObject::getAmount(const QDate& iDate) const
{
    double output = 0;
    if (getType() == SKGUnitObject::PRIMARY) output = 1.0;
    else if (getDocument()) {
        //Search result in cache
        QString ids = SKGServices::intToString(getID());
        QString dates = SKGServices::dateToSqlString(QDateTime(iDate));
        QString key = "unitvalue-" % ids % '-' % dates;
        QString val = getDocument()->getCachedValue(key);
        if (val.isEmpty()) {
            //Get quantity
            double quantity = 1;
            SKGUnitValueObject uv;
            if (getUnitValue(iDate, uv).isSucceeded()) quantity = uv.getQuantity();

            SKGUnitObject unit;
            double coef = 1;
            if (getUnit(unit).isSucceeded()) {
                if (unit != *this) coef = unit.getAmount(iDate);
            }

            output = coef * quantity;
            ((SKGDocument*) getDocument())->addValueInCache(key, SKGServices::doubleToString(output));

            if (getAttribute("i_NBVALUES") == "1") {
                //Store value for this symbol for all date
                ((SKGDocument*) getDocument())->addValueInCache("unitvalue-" % ids, SKGServices::doubleToString(output));
            }
        } else {
            output = SKGServices::stringToDouble(val);
        }
    }
    return output;

}

double SKGUnitObject::getDailyChange(const QDate& iDate) const
{
    double output = 0;
    SKGStringListList result;
    SKGError err = getDocument()->executeSelectSqliteOrder(
                       "SELECT d_date, f_quantity from unitvalue where rd_unit_id=" %
                       SKGServices::intToString(getID()) %
                       " AND d_date<='" % SKGServices::dateToSqlString(QDateTime(iDate)) %
                       "' ORDER BY d_date DESC LIMIT 2",
                       result);
    if (!err && result.count() == 3) {
        double v2 = SKGServices::stringToDouble(result.at(1).at(1));
        double v1 = SKGServices::stringToDouble(result.at(2).at(1));

        QDate d2 = SKGServices::stringToTime(result.at(1).at(0)).date();
        QDate d1 = SKGServices::stringToTime(result.at(2).at(0)).date();

        output = 100 * (qExp(qLn(v2 / v1) / (SKGServices::nbWorkingDays(d1, d2))) - 1);
    }
    return output;
}

SKGError SKGUnitObject::split(double iRatio) const
{
    SKGError err;
    if (iRatio > 0) {

        err = getDocument()->executeSqliteOrder("UPDATE unitvalue SET f_quantity=f_quantity/" % SKGServices::doubleToString(iRatio) %
                                                " WHERE rd_unit_id=" % SKGServices::intToString(getID()));
        if (!err) err = getDocument()->executeSqliteOrder("UPDATE suboperation SET f_value=f_value*" % SKGServices::doubleToString(iRatio) %
                            " WHERE rd_operation_id IN (SELECT id FROM operation WHERE rc_unit_id=" % SKGServices::intToString(getID()) % ')');
    } else err = SKGError(ERR_INVALIDARG, i18nc("Error message", "Invalid ratio. Ratio must be greater than 0."));
    return err;
}

QStringList SKGUnitObject::downloadSources()
{
    QStringList sources;
    foreach(const QString & file, KStandardDirs().findAllResources("data", QString::fromLatin1("skrooge/quotes/*.txt"))) {
        QFileInfo f(file);
        QString file2 = f.completeBaseName();
        if (!sources.contains(file2)) sources.push_back(file2);
    }

    return sources;
}

SKGError SKGUnitObject::addSource(const QString& iNewSource)
{
    SKGError err;

    QString newfile = KStandardDirs::locateLocal("data", QString::fromLatin1("skrooge/quotes/")) % iNewSource % ".txt";

    //Create the new file
    KSaveFile file(newfile);
    //Check if file already existing
    if (!QFile(newfile).exists()) {
        //Creation of the template
        if (!file.open()) {
            err.setReturnCode(ERR_INVALIDARG);
            err.setMessage(i18nc("Error message",  "Save file '%1' failed", newfile));
        } else {
            QTextStream out(&file);

            out << "#" << i18nc("Description test for a text file used to define a source of download",
                                "The URL of the source. %1 will be replaced by the internet code of the unit", "%1") << endl;
            out << "url=http://server/?s=%1" << endl << endl;

            out << "#" << i18nc("Description test for a text file used to define a source of download",
                                "The mode (HTML or CSV). In HTML mode, only one value will be extracted from downloaded page. In CSV mode, a value per line will be extracted.") << endl;
            out << "mode=CSV or HTML" << endl << endl;

            out << "#" << i18nc("Description test for a text file used to define a source of download",
                                "The regular expression for the price (see http://doc.qt.nokia.com/latest/qregexp.html)") << endl;
            out << "price=" << endl << endl;

            out << "#" << i18nc("Description test for a text file used to define a source of download",
                                "The regular expression for the date (see http://doc.qt.nokia.com/latest/qregexp.html)") << endl;
            out << "date=" << endl << endl;

            out << "#" << i18nc("Description test for a text file used to define a source of download",
                                "The format of the date (see http://doc.qt.nokia.com/latest/qdate.html#fromString-2)") << endl;
            out << "dateformat=yyyy-MM-dd" << endl;

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

    //Open the created or already existing file
    QDesktopServices::openUrl(KUrl(newfile));
    return err;
}

SKGError SKGUnitObject::deleteSource(const QString& iSource)
{
    SKGError err;

    QString fileName = KStandardDirs::locateLocal("data", QString::fromLatin1("skrooge/quotes/")) % iSource % ".txt";

    //Delete the file
    QFile file(fileName);
    if (!file.remove()) {
        err.setReturnCode(ERR_INVALIDARG);
        err.setMessage(i18nc("Error message",  "Deletion of '%1' failed", fileName));
    }
    return err;
}

bool SKGUnitObject::isWritable(const QString& iSource)
{
    QString fileName = KStandardDirs::locateLocal("data", QString::fromLatin1("skrooge/quotes/")) % iSource % ".txt";
    return QFileInfo(fileName).isWritable();
}

SKGError SKGUnitObject::downloadUnitValue(UnitDownloadMode iMode, int nbMaxValues, bool iAdditionalInfo)
{
    SKGError err;
    SKGTRACEINRC(10, "SKGUnitObject::downloadUnitValue", err);

    QString unitname = getName();
    QString code = getInternetCode();
    bool invert = code.contains(" /");
    code.remove(" /");
    QString source = getDownloadSource();
    SKGDocumentBank* doc = static_cast<SKGDocumentBank*>(getDocument());
    if (!code.isEmpty() && doc) {
        //Get last date
        QDate firstDate;
        SKGStringListList result;
        QString dateParameters;
        doc->executeSelectSqliteOrder("SELECT MAX(d_date) FROM unitvalue where rd_unit_id=(SELECT id from unit where t_name='" % SKGServices::stringToSqlString(unitname) % "')", result);
        if (result.count() == 2) {
            firstDate = SKGServices::stringToTime(result.at(1).at(0)).date();
            dateParameters = "a=" % SKGServices::intToString(firstDate.month() - 1) % "&b=" % SKGServices::intToString(firstDate.day()) % "&c=" % SKGServices::intToString(firstDate.year());
        }

        if (code.startsWith(QLatin1String("="))) {
            //MODE FORMULAR
            //Set 1st january if date is not found
            if (!firstDate.isValid()) firstDate.setDate(QDate::currentDate().year(), 1, 1);

            //Compute yearly rate
            double rate = SKGServices::stringToDouble(code.right(code.length() - 1));

            //Compute rate for step
            double step = (iMode == LAST_MONTHLY || ALL_MONTHLY ? 12.0 : (iMode == LAST_WEEKLY || ALL_WEEKLY ? 365.0 / 7.0 : 365));
            double rate2 = 100.0 * (qExp(qLn(1 + rate / 100.0) / step) - 1.0);

            //Get last value
            SKGStringListList result;
            double value = 100;
            doc->executeSelectSqliteOrder("SELECT f_quantity FROM unitvalue where rd_unit_id=(SELECT id from unit where t_name='" % SKGServices::stringToSqlString(unitname) % "') AND d_date='" % SKGServices::dateToSqlString(QDateTime(firstDate)) % '\'', result);
            if (result.count() == 2) value = SKGServices::stringToDouble(result.at(1).at(0));

            //Compute and add values
            while (!err && firstDate <= QDate::currentDate()) {
                //Compute next date and value
                if (iMode == LAST_MONTHLY || ALL_MONTHLY) firstDate = firstDate.addMonths(1);
                else if (iMode == LAST_WEEKLY || ALL_WEEKLY) firstDate = firstDate.addDays(7);
                else firstDate = firstDate.addDays(1);

                value *= (1 + rate2 / 100.0);

                //Create value
                SKGUnitValueObject val;
                err = addUnitValue(val);
                if (!err) err = val.setDate(firstDate);
                if (!err) err = val.setQuantity(value);
                if (!err) err = val.save();

            }

        } else if (source.isEmpty() || source == i18nc("Native download source (Yahoo)", "Yahoo")) {
            //NATIVE MODE
            //Example of code: DSY.PA
            //http://ichart.yahoo.com/table.csv?s=DSY.PA&d=7&e=31&f=2008&g=d&a=0&b=1&c=2003&ignore=.csv
            //&g=w = weekly
            //&g=d = daily
            //&g=m = monthly
            QString parameters;
            bool last = (iMode == LAST);
            if (last) parameters = dateParameters % "&g=d";
            else if (iMode == LAST_MONTHLY) parameters = dateParameters % "&g=m";
            else if (iMode == LAST_WEEKLY)  parameters = dateParameters % "&g=w";
            else if (iMode == LAST_DAILY)   parameters = dateParameters % "&g=d";
            else if (iMode == ALL_MONTHLY)  parameters = "g=m";
            else if (iMode == ALL_WEEKLY)   parameters = "g=w";
            else if (iMode == ALL_DAILY)    parameters = "g=d";

            //Download history
            if (!last) {
                QString path = "http://ichart.finance.yahoo.com/table.csv?s=" % code % "&ignore=.csv&" % parameters;
                KUrl url(path);
                QString tmpFile;
                if (KIO::NetAccess::download(url, tmpFile, NULL)) {

                    //Open file
                    QFile file(tmpFile);
                    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
                        doc->sendMessage(i18nc("An information message", "Open file '%1' failed", tmpFile));
                    } else {
                        QTextStream stream(&file);

                        //The file is something like this
                        //Date,Open,High,Low,Close,Volume,Adj Close
                        //2008-08-29,41.64,41.86,41.04,41.32,308800,41.32
                        //2008-08-28,40.43,41.93,40.25,41.67,362000,41.67
                        //2008-08-27,41.00,41.00,40.11,40.49,174200,40.49
                        //2008-08-26,40.44,41.00,40.20,40.95,228200,40.95
                        //2008-08-25,40.52,41.02,40.52,40.80,100100,40.80
                        stream.readLine(); //to forget column names

                        int nbLoaded = 0;
                        while (!stream.atEnd() && !err && (nbLoaded < nbMaxValues - 1 || nbMaxValues == 0)) { //-1 because the value of the day will be loaded after
                            //Read line
                            QString line = stream.readLine().trimmed();
                            QStringList fields = SKGServices::splitCSVLine(line, ',');
                            if (fields.count() == 7) {
                                QDate ds = QDate::fromString(fields[0], "yyyy-MM-dd");
                                double val = SKGServices::stringToDouble(fields[6]);

                                //Creation or update of the value
                                SKGUnitValueObject value;
                                if (!err) err = addUnitValue(value);
                                if (!err) err = value.setDate(ds);
                                if (!err) err = value.setQuantity(invert && val != 0 ? 1 / val : val);
                                if (!err) err = value.save();
                            } else {
                                err.setReturnCode(ERR_ABORT);
                                err.setMessage(i18nc("An error message", "Invalid line '%1' found in downloaded file.\nCheck this page: %2", line, path));
                            }
                            nbLoaded++;
                        }

                        //close file
                        file.close();
                    }

                    //Remove tmp file
                    KIO::NetAccess::removeTempFile(tmpFile);
                } else {
                    //283840: doc->sendMessage(KIO::NetAccess::lastErrorString());
                    doc->sendMessage(i18nc("A warning message", "History for %1 not found on Yahoo", code));
                }
            }
            if (!err) {
                //Build list of additional download information
                //http://www.etraderzone.com/free-scripts/50-yahoo-stock-quotes.html?catid=34%3Aexcel-script
                QStringList unitYahooCode;
                QStringList unitYahooTitle;
                QString yahooString;
                if (iAdditionalInfo) {
                    unitYahooCode.push_back("a");//1
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Ask"));
                    unitYahooCode.push_back("a2");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Average Daily Volume"));
                    //Sometimes contains a , ==> KO: unitYahooCode.push_back("a5");
                    //Sometimes contains a , ==> KO: unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Ask Size"));
                    unitYahooCode.push_back("b");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Bid"));
                    unitYahooCode.push_back("b4");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Book Value"));
                    //Can block due to , unitYahooCode.push_back("b6");
                    //Can block due to , unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Bid Size"));
                    //unitYahooCode.push_back("c");
                    //unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "% Change"));
                    unitYahooCode.push_back("c1");//10
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Change"));
                    unitYahooCode.push_back("c3");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Commission"));
                    unitYahooCode.push_back("d");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Dividend/Share"));
                    unitYahooCode.push_back("d2");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Trade Date"));
                    unitYahooCode.push_back("e");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Earning/Share"));
                    //unitYahooCode.push_back("e1");
                    //unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Error Indication"));
                    unitYahooCode.push_back("e7");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "EPS Estimate Current Year"));
                    unitYahooCode.push_back("e8");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "EPS Estimate Next Year"));
                    unitYahooCode.push_back("e9");//20
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "EPS Estimate Next Quarter"));
                    //unitYahooCode.push_back("f6");
                    //unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Float Shares"));
                    unitYahooCode.push_back("g1");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Holding Gain %"));
                    unitYahooCode.push_back("g3");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Annualized Gain"));
                    unitYahooCode.push_back("g4");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Holding Gain"));
                    unitYahooCode.push_back("i");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "More Info"));
                    unitYahooCode.push_back("j1");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Market Capitalization"));
                    unitYahooCode.push_back("j4");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "EBITDA"));
                    unitYahooCode.push_back("j5");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Change from 52 weeks Low"));
                    unitYahooCode.push_back("j6");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share",
                                                   // xgettext: no-c-format
                                                   "% Change from 52 weeks Low"));
                    unitYahooCode.push_back("k3");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Last Trade Size"));
                    unitYahooCode.push_back("k4");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Change from 52 weeks High"));
                    unitYahooCode.push_back("k5");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share",
                                                   // xgettext: no-c-format
                                                   "% Change from 52 weeks"));
                    //unitYahooCode.push_back("l");
                    //unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Last Trade with Time"));
                    unitYahooCode.push_back("l2");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "High Limit"));
                    unitYahooCode.push_back("l3");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Low Limit"));
                    unitYahooCode.push_back("m");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Day's Range"));
                    unitYahooCode.push_back("m3");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "50 days MA"));
                    unitYahooCode.push_back("m4");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "200 days MA"));
                    unitYahooCode.push_back("m5");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Change from 200 days MA"));
                    unitYahooCode.push_back("m6");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share",
                                                   // xgettext: no-c-format
                                                   "% Change from 200 days MA"));
                    unitYahooCode.push_back("m7");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Change from 50 days MA"));
                    unitYahooCode.push_back("m8");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share",
                                                   // xgettext: no-c-format
                                                   "% Change from 50 days MA"));
                    unitYahooCode.push_back("n4");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Notes"));
                    unitYahooCode.push_back("o");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Open"));
                    unitYahooCode.push_back("p");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Previous Close"));
                    unitYahooCode.push_back("p1");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Price Paid"));
                    unitYahooCode.push_back("p2");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Change in %"));
                    unitYahooCode.push_back("p5");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Price/Sales"));
                    unitYahooCode.push_back("p6");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Price/Book"));
                    unitYahooCode.push_back("q");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Ex-Dividend Date"));
                    unitYahooCode.push_back("r");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "P/E Ratio"));
                    unitYahooCode.push_back("r1");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Dividend Pay Date"));
                    unitYahooCode.push_back("r5");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "PEG Ratio"));
                    unitYahooCode.push_back("r6");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Price/EPS Estimate Current Year"));
                    unitYahooCode.push_back("r7");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Price/EPS Estimate Next Year"));
                    //unitYahooCode.push_back("s");
                    //unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Symbol"));
                    unitYahooCode.push_back("s1");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Shares Owned"));
                    unitYahooCode.push_back("s7");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Short Ratio"));
                    unitYahooCode.push_back("t1");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Last Trade Time"));
                    unitYahooCode.push_back("t8");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "1 Year Target"));
                    unitYahooCode.push_back("v");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Volume"));
                    unitYahooCode.push_back("v1");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Holding Value"));
                    unitYahooCode.push_back("w");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "52 weeks Range"));
                    unitYahooCode.push_back("w1");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Day's Value Change"));
                    unitYahooCode.push_back("x");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Stock Exchange"));
                    unitYahooCode.push_back("y");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Dividend Yield"));
                    unitYahooCode.push_back("n"); //Must be the last
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Name"));

                    /*unitYahooCode.push_back("c6");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Change (RT)"));
                    unitYahooCode.push_back("c8");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "After Hour Change (RT)"));
                    unitYahooCode.push_back("g5");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Holding Gain % (RT)"));
                    unitYahooCode.push_back("g6");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Holding Gain (RT)"));
                    unitYahooCode.push_back("i5");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Order Book (RT)"));
                    unitYahooCode.push_back("j3");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Market Cap (RT)"));
                    unitYahooCode.push_back("k1");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Last Trade with Time (RT)"));
                    unitYahooCode.push_back("k2");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Change % (RT)"));
                    unitYahooCode.push_back("r2");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "P/E Ratio (RT)"));
                    unitYahooCode.push_back("b2");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Ask (RT)"));
                    unitYahooCode.push_back("b3");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Bid (RT)"));
                    unitYahooCode.push_back("v7");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Holding Value (RT)"));
                    unitYahooCode.push_back("m2");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Day's Range (RT)"));
                    unitYahooCode.push_back("w4");
                    unitYahooTitle.push_back(i18nc("Yahoo title for a downloaded information of a share", "Day's Value Change (RT)"));*/

                    foreach(const QString & key, unitYahooCode) {
                        yahooString += key;
                    }

                    //Add yahoo url
                    err = setProperty(i18nc("Yahoo title for a downloaded information of a share", "Yahoo page"), "http://finance.yahoo.com/q?s=" % code);
                }

                QString path = "http://download.finance.yahoo.com/d/quotes.csv?s=" % code % "&f=l1d1" % yahooString;//l1=val d1=date
                KUrl url(path);
                QString tmpFile;
                if (!err && KIO::NetAccess::download(url, tmpFile, NULL)) {
                    //Open file
                    QFile file(tmpFile);
                    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
                        doc->sendMessage(i18nc("An information message", "Open file '%1' failed", tmpFile));
                    } else {
                        QTextStream stream(&file);

                        //Read line
                        QString line = stream.readLine().trimmed();
                        QStringList fields = SKGServices::splitCSVLine(line, ',');
                        QDate ds;
                        int nb = fields.count();
                        if (nb != unitYahooTitle.count() + 2) {
                            err = getDocument()->sendMessage(i18nc("An error message", "Additional information was not downloaded because of invalid number of fields (found=%1, expected=%2) for %3 in following string:\n%4", SKGServices::intToString(nb), SKGServices::intToString(unitYahooTitle.count() + 2), unitname, line));

                            if (!err) err = downloadUnitValue(iMode, nbMaxValues, false);
                        } else {
                            if ((ds = QDate::fromString(fields[1], "M/d/yyyy")).isValid()) {
                                double val = SKGServices::stringToDouble(fields[0]);

                                //Creation or update of the value
                                SKGUnitValueObject value;
                                if (!err) err = addUnitValue(value);
                                if (!err) err = value.setDate(ds);
                                if (!err) err = value.setQuantity(invert && val != 0 ? 1 / val : val);
                                if (!err) err = value.save();
                            } else {
                                err.setReturnCode(ERR_ABORT);
                                err.setMessage(i18nc("An error message", "Invalid line '%1' found in downloaded file.\nCheck this page: %2", line, path));
                            }

                            for (int j = 2; !err && j < nb; ++j) {
                                QString val = fields[j];
                                if (!val.contains("N/A") && val != "-" && val != "- - -" && val != "0" && val != "0.00") err = setProperty(unitYahooTitle[j - 2], val);
                            }
                        }

                        //close file
                        file.close();
                    }

                    //Remove tmp file
                    KIO::NetAccess::removeTempFile(tmpFile);
                } else {
                    doc->sendMessage(i18nc("An information message", "Open url '%1' failed: %2", path, KIO::NetAccess::lastErrorString()));
                }
            }
        } else {
            //ALTERNATIVE MODE
            QString fileName = KStandardDirs().findResource("data", "skrooge/quotes/" % source % ".txt");
            if (fileName.isEmpty()) {
                err = SKGError(ERR_FAIL, i18nc("Error message", "Source of download %1 is not installed.", source));
            } else {
                //Read source file
                QHash< QString, QString > properties;
                err = SKGServices::readPropertyFile(fileName, properties);

                if (!err) {
                    QString path = properties["url"].replace("%1", code);
                    QString mode = properties["mode"].toUpper();
                    QString price = properties["price"].replace("%1", code);
                    QString date = properties["date"].replace("%1", code);
                    QString dateFormat = properties["dateformat"];

                    //Download url
                    KUrl url(path);
                    QString tmpFile;
                    if (!err && KIO::NetAccess::download(url, tmpFile, NULL)) {
                        //Open file
                        QFile file(tmpFile);
                        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
                            doc->sendMessage(i18nc("An information message", "Open file '%1' failed", tmpFile));
                        } else {
                            QTextStream stream(&file);

                            //Parse data
                            QRegExp priceRegExp(price, Qt::CaseInsensitive);
                            QRegExp dateRegExp(date, Qt::CaseInsensitive);

                            //Read data
                            int nbLoaded = 0;
                            while (!stream.atEnd() && !err && (nbLoaded < nbMaxValues || nbMaxValues == 0)) {
                                //Read line
                                QString data = (mode == "CSV" ? stream.readLine() : stream.readAll()).trimmed();
                                if (!data.isEmpty()) {
                                    SKGTRACEL(1) << "Downloaded data from [" << path << "]=[" << data << "]" << endl;

                                    double val = 0.0;
                                    if (priceRegExp.indexIn(data) > -1) val = SKGServices::stringToDouble(priceRegExp.cap(1));
                                    QString d;
                                    if (dateRegExp.indexIn(data) > -1) d = dateRegExp.cap(1);
                                    SKGTRACEL(1) << "Price found=[" << val << "]" << endl;
                                    SKGTRACEL(1) << "Date found=[" << d << "]" << endl;

                                    //Set value
                                    if (val) {
                                        QDate ds = QDate::fromString(d, dateFormat);
                                        if (!ds.isValid()) ds = QDate::currentDate();

                                        //Creation or update of the value
                                        SKGUnitValueObject value;
                                        if (!err) err = addUnitValue(value);
                                        if (!err) err = value.setDate(ds);
                                        if (!err) err = value.setQuantity(invert && val != 0 ? 1 / val : val);
                                        if (!err) err = value.save();

                                        nbLoaded++;
                                    }
                                }
                            }
                            if (nbLoaded == 0) err = doc->sendMessage(i18nc("Information message", "Price not found for '%1' with regular expression '%2'", getName(), SKGServices::stringToHtml(price))); //TODO does not work with html

                            //close file
                            file.close();
                        }

                        //Remove tmp file
                        KIO::NetAccess::removeTempFile(tmpFile);
                    } else {
                        doc->sendMessage(i18nc("An information message", "Open url '%1' failed: %2", path, KIO::NetAccess::lastErrorString()));
                    }
                }
            }
        }
    }

    if (err) {
        err.addError(ERR_FAIL, i18nc("Error message", "Impossible to download unit %1 with Internet code %2.", unitname, code));
    }

    return err;
}

SKGError SKGUnitObject::getOperations(SKGObjectBase::SKGListSKGObjectBase& oOperations) const
{
    SKGError err = getDocument()->getObjects("v_operation",
                   "rc_unit_id=" % SKGServices::intToString(getID()) ,
                   oOperations);
    return err;
}

SKGError SKGUnitObject::merge(const SKGUnitObject& iUnit)
{
    SKGError err;

    SKGObjectBase::SKGListSKGObjectBase ops;
    if (!err) err = iUnit.getOperations(ops);
    int nb = ops.count();
    for (int i = 0; !err && i < nb; ++i) {
        SKGOperationObject op = ops.at(i);
        err = op.setUnit(*this);
        if (!err) err = op.save(true, false);
    }

    if (!err) err = iUnit.remove();
    return err;
}

#include "skgunitobject.moc"
