/***************************************************************************
 *   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 SKGObjectModel.
*
* @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgobjectmodel.h"

#include <math.h>

#include <klocalizedstring.h>
#include <KConfigGroup>

#include <qapplication.h>
#include <qdir.h>
#include <QMimeData>
#include <QFont>
#include <QColor>
#include <QIcon>
#include <qstandardpaths.h>

#include "skgdocumentbank.h"
#include "skgaccountobject.h"
#include "skgcategoryobject.h"
#include "skgpayeeobject.h"
#include "skgoperationobject.h"
#include "skgtrackerobject.h"
#include "skgrecurrentoperationobject.h"
#include "skgunitobject.h"
#include "skgunitvalueobject.h"
#include "skgbudgetruleobject.h"
#include "skgbudgetobject.h"
#include "skgsuboperationobject.h"
#include "skgtransactionmng.h"
#include "skgtraces.h"
#include "skgmainpanel.h"

SKGObjectModel::
SKGObjectModel(SKGDocumentBank* iDocument,
               const QString& iTable,
               const QString& iWhereClause,
               QWidget* iParent,
               const QString& iParentAttribute,
               bool iResetOnCreation)
    : SKGObjectModelBase(iDocument, iTable, iWhereClause, iParent, iParentAttribute, false)
{
    SKGTRACEINFUNC(1);

    m_operationTable = "";
    m_recurrentoperationTable = "";
    m_trackerTable = "";
    m_accountTable = "";
    m_unitTable = "";
    m_unitvalueTable = "";
    m_suboperationTable = "";
    m_categoryTable = "";
    m_ruleTable = "";
    m_interestTable = "";
    m_interestResultTable = "";
    m_payeeTable = "";
    m_budgetTable = "";
    m_budgetRuleTable = "";
    m_isResetRealyNeeded = iResetOnCreation;
    refresh();
}

void SKGObjectModel::buidCache()
{
    SKGObjectModelBase::buidCache();
    m_operationTable = (getRealTable() == "operation" || getRealTable() == "suboperation");
    m_payeeTable = (getRealTable() == "payee");
    m_trackerTable = (getRealTable() == "refund");
    m_recurrentoperationTable = (getRealTable() == "recurrentoperation");
    m_accountTable = (getRealTable() == "account");
    m_unitTable = (getRealTable() == "unit");
    m_unitvalueTable = (getRealTable() == "unitvalue");
    m_suboperationTable = (getTable() == "v_suboperation_consolidated");
    m_ruleTable = (getRealTable() == "rule");
    m_categoryTable = (getRealTable() == "category");
    m_interestTable = (getRealTable() == "interest");
    m_interestResultTable = (getRealTable() == "interest_result");
    m_budgetTable = (getRealTable() == "budget");
    m_budgetRuleTable = (getRealTable() == "budgetrule");

    if (m_unitvalueTable) {
        SKGUnitValueObject unitValObject(getObject(this->index(0, 0)));
        SKGUnitObject unitObject;
        unitValObject.getUnit(unitObject);
        SKGUnitObject parentUnit;
        unitObject.getUnit(parentUnit);
        if (parentUnit.exist()) {
            m_cacheUnit.Name = parentUnit.getName();
            m_cacheUnit.Symbol = parentUnit.getSymbol();
            m_cacheUnit.Value = 1;
            m_cacheUnit.NbDecimal = unitObject.getNumberDecimal();
        } else {
            m_cacheUnit.Name = "";
            m_cacheUnit.Symbol = "";
            m_cacheUnit.Value = 1;
            m_cacheUnit.NbDecimal = unitObject.getNumberDecimal();
        }
        if (m_cacheUnit.Symbol.isEmpty()) {
            m_cacheUnit.Symbol = ' ';
        }

        // Bug 209672 vvvv
        if (unitObject.getType() == SKGUnitObject::INDEX) {
            m_cacheUnit.Symbol = ' ';
            m_cacheUnit.Name = ' ';
        }
        // Bug 209672 ^^^^
    }

    if (m_operationTable) {
        // Read Setting
        KSharedConfigPtr config = KSharedConfig::openConfig();
        KConfigGroup pref = config->group("skrooge_operation");
        m_fontFutureOperationsColor = QVariant::fromValue(pref.readEntry("fontFutureColor", QColor(Qt::gray)));
        m_fontNotValidatedOperationsColor = QVariant::fromValue(pref.readEntry("fontNotValidatedColor", QColor(Qt::blue)));
        m_fontSubOperationsColor = QVariant::fromValue(pref.readEntry("fontSubOperationColor", QColor(Qt::darkGreen)));
    }

    if (m_recurrentoperationTable) {
        // Read Setting
        KSharedConfigPtr config = KSharedConfig::openConfig();
        KConfigGroup pref = config->group("skrooge_scheduled");
        m_fontDisabledScheduleColor = QVariant::fromValue(pref.readEntry("fontFutureColor", QColor(Qt::gray)));
    }

    m_iconFavorite = QIcon::fromTheme("bookmarks");

    if (m_operationTable || m_recurrentoperationTable) {
        m_iconTransfer = QIcon::fromTheme("view-financial-transfer");
        QStringList overlays;
        overlays.push_back("list-remove");
        m_iconGroup = SKGServices::fromTheme("view-financial-transfer", overlays);
        m_iconSplit = QIcon::fromTheme("split");
        m_iconImported = QIcon::fromTheme("utilities-file-archiver");
        {
            QStringList overlay;
            overlay.push_back("dialog-ok");
            m_iconImportedChecked = SKGServices::fromTheme("utilities-file-archiver", overlay);
        }

        m_iconRecurrent = QIcon::fromTheme("chronometer");
        {
            QStringList overlay;
            overlay.push_back("bookmarks");
            m_iconRecurrentMaster = SKGServices::fromTheme("chronometer", overlay);
        }
    }

    if (m_budgetTable) {
        m_iconGreen = QIcon::fromTheme("security-high");
        m_iconRed = QIcon::fromTheme("security-low");
        m_iconAnber = QIcon::fromTheme("security-medium");
    }

    if (m_unitTable) {
        m_iconMuchMore = QIcon::fromTheme("skrooge_much_more");
        m_iconMuchLess = QIcon::fromTheme("skrooge_much_less");
        m_iconMore = QIcon::fromTheme("skrooge_more");
        m_iconLess = QIcon::fromTheme("skrooge_less");
    }

    if (m_ruleTable) {
        m_iconSearch = QIcon::fromTheme("edit-find");
        m_iconUpdate = QIcon::fromTheme("view-refresh");
        m_iconAlarm = QIcon::fromTheme("dialog-warning");
        m_iconTemplate = QIcon::fromTheme("edit-guides");
    }
    m_iconClosed = QIcon::fromTheme("dialog-close");

    if (m_categoryTable) {
        m_iconCategory = QIcon::fromTheme("view-categories");
        m_iconCategoryMoins = QIcon::fromTheme("view-categories-expenditures");
        m_iconCategoryPlus = QIcon::fromTheme("view-categories-incomes");
    }
}

SKGObjectModel::~SKGObjectModel()
{
    SKGTRACEINFUNC(1);
}

QVariant SKGObjectModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    _SKGTRACEINFUNC(10);

    if (orientation == Qt::Horizontal) {
        if (role == Qt::DisplayRole) {
            QString att;
            if (section >= 0 && section < m_listAttibutes.count()) {
                att = m_listAttibutes[section];
            } else {
                att = SKGServices::intToString(section);
            }

            if (att == "t_bookmarked" || att == "i_NBRECURRENT" || att == "t_status" || att == "t_close" || att == "t_imported") {
                return "";
            }
        }
    }
    return SKGObjectModelBase::headerData(section, orientation, role);
}

QString SKGObjectModel::getAttributeForGrouping(const SKGObjectBase& iObject, const QString& iAttribute) const
{
    if (m_recurrentoperationTable && iAttribute == "i_nb_times") {
        if (iObject.getAttribute("t_times") != "Y") {
            return QChar(8734);
        }
    } else if (m_ruleTable && iAttribute == "t_action_type") {
        QString val = iObject.getAttribute(iAttribute);
        if (val == "S") {
            val = i18nc("Noun, a search", "Search");
        } else if (val == "U") {
            val = i18nc("Noun, a modification", "Update");
        } else {
            val = i18nc("Noun, an alarm", "Alarm");
        }
        return val;
    } else if (iAttribute == "t_bookmarked" || iAttribute == "t_close") {
        QString val = iObject.getAttribute(iAttribute);
        return val == "Y" ? i18n("Yes") : i18n("No");
    } else if (iAttribute == "t_status") {
        QString val = iObject.getAttribute(iAttribute);
        return val == "N" ? i18n("None") : val == "P" ? i18n("Pointed") : i18n("Checked");
    }
    return SKGObjectModelBase::getAttributeForGrouping(iObject, iAttribute);
}

QVariant SKGObjectModel::data(const QModelIndex& iIndex, int iRole) const
{
    if (!iIndex.isValid()) {
        return QVariant();
    }
    _SKGTRACEINFUNC(10);
    SKGObjectBase* obj = getObjectPointer(iIndex);
    if (obj->getTable().isEmpty()) {
        return SKGObjectModelBase::data(iIndex, iRole);
    }

    switch (iRole) {
    case Qt::DisplayRole:
    case Qt::EditRole:
    case Qt::UserRole: {
        QString att = m_listAttibutes[iIndex.column()];
        QString val = obj->getAttribute(att);
        if (((att == "t_bookmarked" || att == "t_status" || att == "i_NBRECURRENT" || att == "t_imported"))
            || (att == "t_close")
            || (att == "t_action_type")
           ) {
            if (iRole == Qt::UserRole) {
                if (m_ruleTable && att == "t_action_type") {
                    if (val == "S") {
                        return i18nc("Noun, a search", "Search");
                    } else if (val == "U") {
                        return i18nc("Noun, a modification", "Update");
                    } else {
                        return i18nc("Noun, an alarm", "Alarm");
                    }
                }
                return val;
            } else {
                return "";
            }
        } else if (m_interestTable && att == "t_expenditure_value_date_mode") {
            if (val == "0") {
                return i18nc("Noun", "Day -0");
            } else if (val == "1") {
                return i18nc("Noun", "Day -1");
            } else if (val == "2") {
                return i18nc("Noun", "Day -2");
            } else if (val == "3") {
                return i18nc("Noun", "Day -3");
            } else if (val == "4") {
                return i18nc("Noun", "Day -4");
            } else if (val == "5") {
                return i18nc("Noun", "Day -5");
            } else {
                return i18nc("Noun", "Fifteen");
            }
        } else if (m_accountTable && att == "d_reconciliationdate") {
            if (val.isEmpty() && iRole == Qt::DisplayRole) {
                return i18nc("Noun", "Never");
            }
        } else if (m_interestTable && att == "t_income_value_date_mode") {
            if (val == "0") {
                return i18nc("Noun", "Day +0");
            } else if (val == "1") {
                return i18nc("Noun", "Day +1");
            } else if (val == "2") {
                return i18nc("Noun", "Day +2");
            } else if (val == "3") {
                return i18nc("Noun", "Day +3");
            } else if (val == "4") {
                return i18nc("Noun", "Day +4");
            } else if (val == "5") {
                return i18nc("Noun", "Day +5");
            } else {
                return i18nc("Noun", "Fifteen");
            }
        } else if (getAttributeType(iIndex.column()) == SKGServices::FLOAT) {
            double dval = SKGServices::stringToDouble(val);
            if (iRole == Qt::DisplayRole) {
                if (att.endsWith(QLatin1String("_INCOME")) ||
                    att.endsWith(QLatin1String("_EXPENSE")) ||
                    (m_categoryTable && (att == "f_REALCURRENTAMOUNT" ||
                                         att == "f_SUMCURRENTAMOUNT"))) {
                    if (dval == 0) {
                        return "";
                    }
                }

                SKGServices::SKGUnitInfo unit;
                unit.Symbol = "";
                unit.NbDecimal = 2;
                if (!att.contains("QUANTITY") && !att.contains("f_BALANCE_ENTERED")) {
                    unit = static_cast<SKGDocumentBank*>(getDocument())->getPrimaryUnit();
                    if (m_unitvalueTable && !m_cacheUnit.Symbol.isEmpty()) {
                        unit = m_cacheUnit;
                    }
                } else {
                    unit.NbDecimal = SKGServices::stringToInt(obj->getAttribute("i_NBDEC"));
                    if (unit.NbDecimal == 0) {
                        unit.NbDecimal = 2;
                    }
                    if (att != "f_QUANTITYOWNED") {
                        unit.Symbol = obj->getAttribute("t_UNIT");
                    }
                }

                // Bug 209672 vvvv
                if (m_unitTable) {
                    if (obj->getAttribute("t_type") == "I") {
                        unit.Symbol = ' ';
                    }
                }
                // Bug 209672 ^^^

                if (QString::compare(att, "f_rate", Qt::CaseInsensitive) == 0) {
                    unit.Symbol = '%';
                    unit.NbDecimal = 2;
                } else if (att == "f_coef") {
                    unit.Symbol = "";
                    unit.NbDecimal = 2;
                }

                if (unit.Symbol.isEmpty()) {
                    unit.Symbol = ' ';
                }

                return SKGServices::toCurrencyString(dval, unit.Symbol, unit.NbDecimal);
            } else {
                return dval;
            }
        } else if (getAttributeType(iIndex.column()) == SKGServices::INTEGER) {
            if (m_recurrentoperationTable && att == "i_nb_times") {
                QString t_times = obj->getAttribute("t_times");
                if (t_times != "Y") {
                    return QChar(8734);
                }
            } else if ((att == "i_number" || att == "i_NBOPERATIONS" || att == "i_SUMNBOPERATIONS") && val == "0") {
                return "";
            }

            return SKGServices::stringToInt(val);
        } else if (m_suboperationTable && att.startsWith(QLatin1String("p_"))) {
            val = obj->getProperty(att.right(att.count() - 2));
            if (val.isEmpty()) {
                val = obj->getDocument()->getParameter(att.right(att.count() - 2), obj->getAttribute("i_OPID") % "-operation");
            }
            return val;
        }
        break;
    }
    case Qt::DecorationRole: {
        // decoration
        QString att = m_listAttibutes[iIndex.column()];
        if (att == "t_bookmarked") {
            if (obj->getAttribute("t_bookmarked") == "Y") {
                return m_iconFavorite;
            }
        } else if (m_operationTable || m_recurrentoperationTable) {
            if (att == "t_mode") {
                if (obj->getAttribute("t_TRANSFER") == "Y") {
                    return m_iconTransfer;
                }
                if (obj->getAttribute("i_group_id") != "0") {
                    return m_iconGroup;
                }
            } else if (att == "t_CATEGORY") {
                if (SKGServices::stringToInt(obj->getAttribute("i_NBSUBOPERATIONS")) > 1) {
                    return m_iconSplit;
                }
            } else if (att == "i_NBRECURRENT" && m_operationTable) {
                if (obj->getAttribute("i_NBRECURRENT") != "0") {
                    return m_iconRecurrentMaster;
                } else if (obj->getAttribute("r_recurrentoperation_id") != "0") {
                    return m_iconRecurrent;
                }
            } else if (att == "t_imported") {
                QString impStatus = obj->getAttribute("t_imported");
                if (impStatus == "Y") {
                    return m_iconImported;
                } else if (impStatus == "P") {
                    return m_iconImportedChecked;
                }
            } else if (att == "t_REFUND" || att == "t_REALREFUND") {
                QString trackerName;
                trackerName = obj->getAttribute(att);
                if (!trackerName.isEmpty()) {
                    SKGTrackerObject tracker(SKGObjectBase(getDocument(), "refund"));  // Better performance if v_refund is not used
                    tracker.setName(trackerName);
                    tracker.load();

                    if (tracker.isClosed()) {
                        return m_iconClosed;
                    }
                }
            }
        } else if (m_ruleTable) {
            if (att == "t_action_type") {
                QString val = obj->getAttribute(att);
                if (val == "S") {
                    return m_iconSearch;
                } else if (val == "U") {
                    return m_iconUpdate;
                } else if (val == "T") {
                    return m_iconTemplate;
                } else {
                    return m_iconAlarm;
                }
            }
        } else if (m_unitTable) {
            if (att == "f_CURRENTAMOUNT") {
                SKGUnitObject unit(*obj);
                double annualchange = 365 * unit.getDailyChange();
                if (annualchange >= 15) {
                    return m_iconMuchMore;
                } else if (annualchange <= -15) {
                    return m_iconMuchLess;
                } else if (annualchange >= 5) {
                    return m_iconMore;
                } else if (annualchange <= -5) {
                    return m_iconLess;
                }
            }
        } else if (m_accountTable) {
            if (att == "t_BANK") {
                QString t_icon = obj->getAttribute("t_icon");
                if (t_icon.isEmpty()) {
                    t_icon = obj->getAttribute("t_ICON");
                }
                if (!t_icon.isEmpty()) {
                    QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "skrooge/images/logo/" % t_icon);
                    return QVariant::fromValue(QIcon(fileName));
                }
            }
#if 0
            else if (att == "t_TYPENLS") {
                QString type = obj->getAttribute("t_type");
                if (type == "C") {
                    return QIcon::fromTheme("view-bank-account-savings");
                } else if (type == "D")  {
                    return QIcon::fromTheme("view-credit-card-account");
                } else if (type == "I")  {
                    return QIcon::fromTheme("view-stock-account");
                } else if (type == "A")  {
                    return QIcon::fromTheme("view-loan-asset");
                } else if (type == "W")  {
                    return QIcon::fromTheme("kwalletmanager");
                } else {
                    return QIcon::fromTheme();
                }
            }
#endif
        } else if (m_categoryTable) {
            if (iIndex.column() == 0) {
                QString t_TYPEEXPENSE = obj->getAttribute("t_TYPEEXPENSE");
                if (t_TYPEEXPENSE == "-") {
                    return m_iconCategoryMoins;
                } else if (t_TYPEEXPENSE == "+") {
                    return m_iconCategoryPlus;
                }
                return m_iconCategory;
            }
        } else if (m_budgetTable) {
            if (att == "f_DELTA" || att == "f_DELTABEFORETRANSFER") {
                double val = SKGServices::stringToDouble(obj->getAttribute(att));
                if (val < -EPSILON) {
                    return m_iconRed;
                } else if (val > EPSILON) {
                    return m_iconGreen;
                } else {
                    double transferred = SKGServices::stringToDouble(obj->getAttribute("f_transferred"));
                    if (transferred < -EPSILON) {
                        return m_iconAnber;
                    } else {
                        return m_iconGreen;
                    }
                }
            }
        }
        break;
    }
    case Qt::TextColorRole: {
        // Text color
        QString att = m_listAttibutes[iIndex.column()];
        if (m_recurrentoperationTable) {
            if (obj->getAttribute("i_nb_times") == "0") {
                return  m_fontDisabledScheduleColor;
            } else if (att == "d_date" && SKGServices::stringToTime(obj->getAttribute("d_date")).date() <= QDate::currentDate()) {
                return m_fontNegativeColor;
            }
        } else if (m_operationTable) {
            if (SKGServices::stringToTime(obj->getAttribute("d_date")).date() > QDate::currentDate()) {
                return m_fontFutureOperationsColor;
            } else if (getAttributeType(iIndex.column()) != SKGServices::FLOAT) {
                if (obj->getAttribute("t_imported") == "P") {
                    return  m_fontNotValidatedOperationsColor;
                } else if (m_suboperationTable) {
                    return m_fontSubOperationsColor;
                }
            }
        }
        break;
    }
    case Qt::TextAlignmentRole: {
        // Text alignment
        if (m_recurrentoperationTable) {
            QString att = m_listAttibutes[iIndex.column()];

            if (att == "i_auto_write_days" || att == "i_warn_days" || att == "i_nb_times") {
                return static_cast<int>(Qt::AlignVCenter | Qt::AlignLeft);
            }
        }
        break;
    }
    case Qt::CheckStateRole: {
        // CheckState
        QString att = m_listAttibutes[iIndex.column()];
        if (m_operationTable && att == "t_status") {
            QString cond = obj->getAttribute("t_status");
            if (cond == "P") {
                return Qt::PartiallyChecked;
            } else if (cond == "Y") {
                return Qt::Checked;
            } else if (cond == "N") {
                return Qt::Unchecked;
            }
        } else if (att == "t_close") {
            QString cond = obj->getAttribute("t_close");
            if (cond == "Y") {
                return Qt::Checked;
            } else {
                return Qt::Unchecked;
            }
        } else  if (m_recurrentoperationTable && att == "i_auto_write_days") {
            QString cond = obj->getAttribute("t_auto_write");
            if (cond == "Y") {
                return Qt::Checked;
            } else {
                return Qt::Unchecked;
            }
        } else  if (m_recurrentoperationTable && att == "i_warn_days") {
            QString cond = obj->getAttribute("t_warn");
            if (cond == "Y") {
                return Qt::Checked;
            } else {
                return Qt::Unchecked;
            }
        } else  if (m_recurrentoperationTable && att == "i_nb_times") {
            QString cond = obj->getAttribute("t_times");
            if (cond == "Y") {
                return Qt::Checked;
            } else {
                return Qt::Unchecked;
            }
        } else  if (m_budgetRuleTable && att == "t_CATEGORYCONDITION") {
            QString cond = obj->getAttribute("t_category_condition");
            if (cond == "Y") {
                return Qt::Checked;
            } else {
                return Qt::Unchecked;
            }
        } else  if (m_budgetRuleTable && att == "i_year") {
            QString cond = obj->getAttribute("t_year_condition");
            if (cond == "Y") {
                return Qt::Checked;
            } else {
                return Qt::Unchecked;
            }
        } else  if (m_budgetRuleTable && att == "i_month") {
            QString cond = obj->getAttribute("t_month_condition");
            if (cond == "Y") {
                return Qt::Checked;
            } else {
                return Qt::Unchecked;
            }
        } else  if (m_budgetRuleTable && att == "t_CATEGORY") {
            QString cond = obj->getAttribute("t_category_target");
            if (cond == "Y") {
                return Qt::Checked;
            } else {
                return Qt::Unchecked;
            }
        } else  if (m_budgetTable && att == "t_CATEGORY") {
            QString cond = obj->getAttribute("t_including_subcategories");
            if (cond == "Y") {
                return Qt::Checked;
            } else {
                return Qt::Unchecked;
            }
        }
        break;
    }
    case Qt::ToolTipRole: {
        // Tooltip
        QString toolTipString;
        QString att = m_listAttibutes[iIndex.column()];
        if (getAttributeType(iIndex.column()) == SKGServices::FLOAT) {
            // Add secondary unit
            if (!att.contains("QUANTITY") && att != "f_coef" && att != "f_rate") {
                SKGServices::SKGUnitInfo secondaryUnit = static_cast<SKGDocumentBank*>(getDocument())->getSecondaryUnit();
                if (!secondaryUnit.Symbol.isEmpty()) {
                    double val = SKGServices::stringToDouble(obj->getAttribute(att));
                    if ((!att.endsWith(QLatin1String("_INCOME")) && !att.endsWith(QLatin1String("_EXPENSE"))) || val != 0) {
                        if (secondaryUnit.Value) {
                            toolTipString = SKGServices::toCurrencyString(val / secondaryUnit.Value, secondaryUnit.Symbol, secondaryUnit.NbDecimal);
                        }
                    }
                }
            }

            // Add balance
            if (m_operationTable) {
                SKGOperationObject op(*obj);
                if (!op.isTemplate()) {
                    SKGServices::SKGUnitInfo primaryUnit = static_cast<SKGDocumentBank*>(getDocument())->getPrimaryUnit();

                    // Add original amount
                    QString originalAmount = obj->getProperty("SKG_OP_ORIGINAL_AMOUNT");
                    if (!originalAmount.isEmpty()) {
                        if (!toolTipString.isEmpty()) {
                            toolTipString += "\n\n";
                        }
                        double val1 = SKGServices::stringToDouble(obj->getAttribute("f_CURRENTAMOUNT"));
                        double val2 = SKGServices::stringToDouble(originalAmount);
                        double gain = (val2 != 0 ? 100.0 * (val1 - val2) / val2 : 0);
                        double gainperyear = gain;

                        int nbDays = op.getDate().daysTo(QDate::currentDate());
                        if (nbDays != 0 && val2 != 0) {
                            double gainperday = 100.0 * expm1(log(val1 / val2) / static_cast<double>(nbDays));
                            gainperyear = 100.0 * (pow(1.0 + gainperday / 100.0, 365.0) - 1);
                        }
                        toolTipString += i18nc("Noun", "Original amount=%1 (%2 = %3 / year)",
                                               SKGServices::toCurrencyString(val2, primaryUnit.Symbol, primaryUnit.NbDecimal),
                                               (gain >= 0 ? "+" : "-") % SKGServices::toPercentageString(gain, 2),
                                               (gainperyear >= 0 ? "+" : "-") % SKGServices::toPercentageString(gainperyear, 2));
                    } else {
                        if (!toolTipString.isEmpty()) {
                            toolTipString += "\n\n";
                        }
                        double val1 = SKGServices::stringToDouble(obj->getAttribute("f_CURRENTAMOUNT"));
                        double val2 = op.getAmount(op.getDate());
                        double gain = (val2 != 0 ? 100.0 * (val1 - val2) / val2 : 0);
                        double gainperyear = gain;

                        int nbDays = op.getDate().daysTo(QDate::currentDate());
                        if (nbDays != 0 && val2 != 0) {
                            double gainperday = 100.0 * expm1(log(val1 / val2) / static_cast<double>(nbDays));
                            gainperyear = 100.0 * (pow(1.0 + gainperday / 100.0, 365.0) - 1);
                        }

                        QString sval1 = SKGServices::toCurrencyString(val1, primaryUnit.Symbol, primaryUnit.NbDecimal);
                        QString sval2 = SKGServices::toCurrencyString(val2, primaryUnit.Symbol, primaryUnit.NbDecimal);
                        if (sval1 != sval2) {
                            toolTipString += i18nc("Noun", "Amount at creation date=%1 (%2 = %3 / year)",
                                                   sval2,
                                                   (gain >= 0 ? "+" : "-") % SKGServices::toPercentageString(gain, 2),
                                                   (gainperyear >= 0 ? "+" : "-") % SKGServices::toPercentageString(gainperyear, 2));

                            toolTipString += '\n';
                        }
                    }
                    toolTipString += i18nc("Noun", "Account balance=%1",
                                           SKGServices::toCurrencyString(op.getBalance(), primaryUnit.Symbol, primaryUnit.NbDecimal));
                }
            }

            if (m_budgetTable) {
                if (att == "f_DELTA") {
                    double val = SKGServices::stringToDouble(obj->getAttribute("f_DELTABEFORETRANSFER"));

                    if (!toolTipString.isEmpty()) {
                        toolTipString += "\n\n";
                    }

                    SKGServices::SKGUnitInfo primaryUnit = static_cast<SKGDocumentBank*>(getDocument())->getPrimaryUnit();
                    toolTipString += i18nc("Noun", "Original delta=%1",
                                           SKGServices::toCurrencyString(val, primaryUnit.Symbol, primaryUnit.NbDecimal));
                }
            }
        } else if (m_operationTable || m_recurrentoperationTable) {
            if (att == "t_imported") {
                SKGOperationObject op(*obj);
                if (m_recurrentoperationTable) {
                    SKGRecurrentOperationObject rop(*obj);
                    rop.getParentOperation(op);
                }
                toolTipString = op.getImportID();
            } else if (att == "t_REFUND" || att == "t_REALREFUND") {
                QString trackerName;
                if (obj) {
                    trackerName = obj->getAttribute(att);
                }
                if (!trackerName.isEmpty()) {
                    SKGTrackerObject tracker(SKGObjectBase(getDocument(), "v_refund"));
                    tracker.setName(trackerName);
                    tracker.load();
                    SKGServices::SKGUnitInfo unit = static_cast<SKGDocumentBank*>(getDocument())->getPrimaryUnit();
                    toolTipString = SKGServices::toCurrencyString(tracker.getCurrentAmount(), unit.Symbol, unit.NbDecimal);
                }
            } else if (att == "t_CATEGORY") {
                SKGOperationObject op(*obj);
                if (m_recurrentoperationTable) {
                    SKGRecurrentOperationObject rop(*obj);
                    rop.getParentOperation(op);
                }
                if (SKGServices::stringToInt(op.getAttribute("i_NBSUBOPERATIONS")) > 1) {
                    SKGObjectBase::SKGListSKGObjectBase subOps;
                    op.getSubOperations(subOps);
                    foreach(const SKGObjectBase & subOp, subOps) {
                        toolTipString += subOp.getDisplayName() % '\n';
                    }
                }
            } else if (att == "t_mode") {
                SKGOperationObject op(*obj);
                if (m_recurrentoperationTable) {
                    SKGRecurrentOperationObject rop(*obj);
                    rop.getParentOperation(op);
                }
                if (op.getAttribute("i_group_id") != "0") {
                    SKGOperationObject gop;
                    op.getGroupOperation(gop);

                    SKGObjectBase::SKGListSKGObjectBase gOps;
                    op.getGroupedOperations(gOps);
                    foreach(const SKGObjectBase & item, gOps) {
                        SKGOperationObject gOp(item);
                        SKGAccountObject account;
                        gOp.getParentAccount(account);
                        toolTipString += account.getDisplayName() % '\n';
                    }
                }
            }

            if (m_operationTable && toolTipString.isEmpty() && obj) {
                SKGOperationObject op(*obj);
                if (op.getStatus() == SKGOperationObject::POINTED) {
                    toolTipString = i18nc("A tool tip", "This operation is pointed but not checked yet.");
                    toolTipString += '\n';
                    toolTipString += i18nc("A tool tip", "You can use the reconciliation mode to validate pointed operations.");
                    toolTipString += '\n';
                    toolTipString += i18nc("A tool tip", "Click to switch back status.");
                    toolTipString += '\n';
                    toolTipString += i18nc("A tool tip", "Ctrl+click to force checked status.");
                } else if (op.getStatus() == SKGOperationObject::CHECKED) {
                    toolTipString = i18nc("A tool tip", "This operation is already checked.");
                } else if (op.getStatus() == SKGOperationObject::NONE) {
                    toolTipString = i18nc("A tool tip", "This operation is not pointed yet.");
                    toolTipString += '\n';
                    toolTipString += i18nc("A tool tip", "Click to set pointed status.");
                    toolTipString += '\n';
                    toolTipString += i18nc("A tool tip", "Ctrl+click to force checked status.");
                }
            }
        } else if (m_ruleTable && att == "t_action_type") {
            QString val = obj->getAttribute(att);
            if (val == "S") {
                toolTipString = i18nc("Noun, a search", "Search");
            } else if (val == "U") {
                toolTipString = i18nc("Noun, a modification", "Update");
            } else {
                toolTipString = i18nc("Noun, an alarm", "Alarm");
            }
        }

        QString toolTipStringBase = SKGObjectModelBase::data(iIndex, iRole).toString();
        if (!toolTipStringBase.isEmpty()) {
            if (!toolTipString.isEmpty()) {
                toolTipString += "\n\n";
            }
            toolTipString += toolTipStringBase;
        }
        return toolTipString;
    }
    default: {
    }
    }
    return SKGObjectModelBase::data(iIndex, iRole);
}

bool SKGObjectModel::setData(const QModelIndex& iIndex, const QVariant& iValue, int iRole)
{
    if (!iIndex.isValid()) {
        return false;
    }

    if (iRole == Qt::CheckStateRole) {
        SKGError err;
        {
            if (m_accountTable) {
                SKGAccountObject obj(getObject(iIndex));
                SKGBEGINLIGHTTRANSACTION(*getDocument(),
                                         (static_cast<Qt::CheckState>(iValue.toInt()) == Qt::Checked ? i18nc("Noun, name of the user action", "Close account '%1'", obj.getName()) : i18nc("Noun, name of the user action", "Open account '%1'", obj.getName())), err);
                if (qAbs(obj.getCurrentAmount()) > 0.01) {
                    err = getDocument()->sendMessage(i18nc("An information message",  "Warning, you closed an account with money"), SKGDocument::Warning);
                }
                IFOKDO(err, obj.setClosed(static_cast<Qt::CheckState>(iValue.toInt()) == Qt::Checked))
                IFOKDO(err, obj.save())
            } else if (m_trackerTable) {
                SKGTrackerObject obj(getObject(iIndex));
                SKGBEGINLIGHTTRANSACTION(*getDocument(),
                                         (static_cast<Qt::CheckState>(iValue.toInt()) == Qt::Checked ? i18nc("Noun, name of the user action", "Close tracker '%1'", obj.getName()) : i18nc("Noun, name of the user action", "Open tracker '%1'", obj.getName())), err);
                err = obj.setClosed(static_cast<Qt::CheckState>(iValue.toInt()) == Qt::Checked);
                IFOKDO(err, obj.save())
            } else if (m_operationTable) {
                // Get the real object, not the object from the view
                SKGObjectBase* objtmp = getObjectPointer(iIndex);
                if (objtmp) {
                    SKGOperationObject obj = SKGOperationObject(objtmp->getDocument(), objtmp->getID());
                    SKGBEGINLIGHTTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Change operation status"), err);
                    SKGOperationObject::OperationStatus statusinitial = obj.getStatus();
                    SKGOperationObject::OperationStatus t_status = statusinitial;
                    if (QApplication::keyboardModifiers() &Qt::ControlModifier) {
                        // 2747379: NONE ==> CHECKED, POINTED ==> CHECKED, CHECKED ==> CHECKED
                        t_status = SKGOperationObject::CHECKED;
                        // t_status= ( t_status==SKGOperationObject::POINTED ? SKGOperationObject::NONE : ( t_status==SKGOperationObject::CHECKED ? SKGOperationObject::POINTED : SKGOperationObject::NONE ) );
                    } else {
                        // 2747379: NONE ==> POINTED, POINTED ==> NONE, CHECKED ==> POINTED
                        t_status = (t_status == SKGOperationObject::NONE ? SKGOperationObject::POINTED : (t_status == SKGOperationObject::POINTED ? SKGOperationObject::NONE : SKGOperationObject::POINTED));
                        // t_status=(t_status==SKGOperationObject::POINTED ? SKGOperationObject::CHECKED : (t_status==SKGOperationObject::CHECKED ? SKGOperationObject::CHECKED : SKGOperationObject::POINTED ));
                    }
                    if (t_status != statusinitial) {
                        err = obj.setStatus(t_status);
                        IFOKDO(err, obj.save())

                        // Send message
                        IFOKDO(err, getDocument()->sendMessage(i18nc("An information to the user", "The status of the operation '%1' has been changed", obj.getDisplayName()), SKGDocument::Hidden));
                    }
                }
            } else if (m_recurrentoperationTable) {
                QString att = m_listAttibutes[iIndex.column()];

                // Get the real object, not the object from the view
                SKGObjectBase* objtmp = getObjectPointer(iIndex);
                if (objtmp) {
                    SKGRecurrentOperationObject obj = SKGRecurrentOperationObject(objtmp->getDocument(), objtmp->getID());

                    SKGBEGINLIGHTTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Recurrent operation update"), err);
                    if (att == "i_warn_days") {
                        err = obj.warnEnabled(!obj.isWarnEnabled());
                    } else if (att == "i_auto_write_days") {
                        err = obj.autoWriteEnabled(!obj.isAutoWriteEnabled());
                    } else if (att == "i_nb_times") {
                        err = obj.timeLimit(!obj.hasTimeLimit());
                    }
                    IFOKDO(err, obj.save())

                    // Send message
                    IFOKDO(err, getDocument()->sendMessage(i18nc("An information to the user", "The recurrent operation '%1' has been updated", obj.getDisplayName()), SKGDocument::Hidden));
                }
            } else if (m_budgetRuleTable) {
                QString att = m_listAttibutes[iIndex.column()];

                // Get the real object, not the object from the view
                SKGObjectBase* objtmp = getObjectPointer(iIndex);
                if (objtmp) {
                    SKGBudgetRuleObject obj = SKGBudgetRuleObject(objtmp->getDocument(), objtmp->getID());

                    SKGBEGINLIGHTTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Budget rule update"), err);
                    if (att == "i_year") {
                        err = obj.enableYearCondition(!obj.isYearConditionEnabled());
                    } else if (att == "i_month") {
                        err = obj.enableMonthCondition(!obj.isMonthConditionEnabled());
                    } else if (att == "t_CATEGORYCONDITION") {
                        err = obj.enableCategoryCondition(!obj.isCategoryConditionEnabled());
                    } else if (att == "t_CATEGORY") {
                        err = obj.enableCategoryChange(!obj.isCategoryChangeEnabled());
                    }
                    IFOKDO(err, obj.save())

                    // Send message
                    IFOKDO(err, getDocument()->sendMessage(i18nc("An information to the user", "The budget rule '%1' has been updated", obj.getDisplayName()), SKGDocument::Hidden));
                }
            } else if (m_budgetTable) {
                QString att = m_listAttibutes[iIndex.column()];

                // Get the real object, not the object from the view
                SKGObjectBase* objtmp = getObjectPointer(iIndex);
                if (objtmp) {
                    SKGBudgetObject obj = SKGBudgetObject(objtmp->getDocument(), objtmp->getID());

                    SKGBEGINTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Budget update"), err);
                    if (att == "t_CATEGORY") {
                        IFOKDO(err, obj.enableSubCategoriesInclusion(!obj.isSubCategoriesInclusionEnabled()))
                        IFOKDO(err, obj.save())

                        // Send message
                        IFOKDO(err, getDocument()->sendMessage(i18nc("An information to the user", "The budget '%1' have been updated", obj.getDisplayName()), SKGDocument::Hidden));
                    }
                }
            }
        }

        SKGMainPanel::displayErrorMessage(err);
        return !err;
    }
    return SKGObjectModelBase::setData(iIndex, iValue, iRole);
}

Qt::ItemFlags SKGObjectModel::flags(const QModelIndex& iIndex) const
{
    _SKGTRACEINFUNC(10);

    Qt::ItemFlags f = SKGObjectModelBase::flags(iIndex);

    if (iIndex.isValid()) {
        QString att = m_listAttibutes[iIndex.column()];
        if (att == "t_bookmarked" || m_ruleTable || m_recurrentoperationTable || m_interestTable || m_interestResultTable) {
            f = f & ~Qt::ItemIsEditable;
        }
    }

    if (m_categoryTable || m_payeeTable || m_accountTable || m_unitTable || m_trackerTable) {
        if (iIndex.isValid()) {
            f |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
        } else {
            f |= Qt::ItemIsDropEnabled;
        }
    }

    return f;
}

Qt::DropActions SKGObjectModel::supportedDragActions() const
{
    if (m_categoryTable || m_payeeTable || m_accountTable || m_unitTable || m_trackerTable) {
        return Qt::MoveAction;
    }
    return SKGObjectModelBase::supportedDragActions();
}

Qt::DropActions SKGObjectModel::supportedDropActions() const
{
    if (m_categoryTable || m_payeeTable || m_accountTable || m_unitTable || m_trackerTable) {
        return Qt::MoveAction;
    }
    return SKGObjectModelBase::supportedDropActions();
}

bool SKGObjectModel::dropMimeData(const QMimeData* iData,
                                  Qt::DropAction iAction,
                                  int iRow, int iColumn,
                                  const QModelIndex& iParent)
{
    if (SKGObjectModelBase::dropMimeData(iData, iAction, iRow, iColumn, iParent)) {
        return true;
    }
    if (iAction == Qt::IgnoreAction) {
        return true;
    }
    if (!iData || !(iData->hasFormat("application/skg.category.ids") ||
                    iData->hasFormat("application/skg.payee.ids") ||
                    iData->hasFormat("application/skg.account.ids") ||
                    iData->hasFormat("application/skg.refund.ids") ||
                    iData->hasFormat("application/skg.unit.ids"))) {
        return false;
    }
    if (iColumn > 0) {
        return false;
    }

    SKGError err;
    if (iData->hasFormat("application/skg.category.ids")) {
        QByteArray encodedData = iData->data("application/skg.category.ids");
        QDataStream stream(&encodedData, QIODevice::ReadOnly);
        QStringList newItems;

        SKGCategoryObject parentCategory;
        if (iParent.isValid()) {
            parentCategory = getObject(iParent);
        }
        {
            SKGBEGINTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Move category"), err);

            while (!stream.atEnd() && !err) {
                int o_id;
                QString o_table;
                stream >> o_table;
                stream >> o_id;

                SKGCategoryObject child(getDocument(), o_id);
                err = child.load();
                QString oldName = child.getDisplayName();
                IFOK(err) {
                    if (iParent.isValid()) {
                        err = child.setParentCategory(parentCategory);
                    } else {
                        err = child.removeParentCategory();
                    }
                }
                IFOKDO(err, child.save())

                // Send message
                IFOKDO(err, getDocument()->sendMessage(i18nc("An information to the user", "The category '%1' has been moved to '%2'", oldName, child.getDisplayName()), SKGDocument::Hidden));
            }
        }
    } else if (iData->hasFormat("application/skg.payee.ids")) {
        QByteArray encodedData = iData->data("application/skg.payee.ids");
        QDataStream stream(&encodedData, QIODevice::ReadOnly);
        QStringList newItems;

        if (iParent.isValid()) {
            SKGBEGINTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Merge payees"), err);
            SKGPayeeObject parentPayee(getObject(iParent));
            while (!stream.atEnd() && !err) {
                int o_id;
                QString o_table;
                stream >> o_table;
                stream >> o_id;

                SKGPayeeObject child(getDocument(), o_id);
                err = child.load();

                // Send message
                IFOKDO(err, getDocument()->sendMessage(i18nc("An information to the user", "The payee '%1' has been merged with payee '%2'", child.getDisplayName(), parentPayee.getDisplayName()), SKGDocument::Hidden));

                IFOKDO(err, parentPayee.merge(child))
            }
        }
    } else if (iData->hasFormat("application/skg.account.ids")) {
        QByteArray encodedData = iData->data("application/skg.account.ids");
        QDataStream stream(&encodedData, QIODevice::ReadOnly);
        QStringList newItems;

        if (iParent.isValid()) {
            SKGBEGINTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Merge accounts"), err);
            SKGAccountObject parentAccount(getObject(iParent));
            while (!stream.atEnd() && !err) {
                int o_id;
                QString o_table;
                stream >> o_table;
                stream >> o_id;

                SKGAccountObject child(getDocument(), o_id);
                err = child.load();

                // Send message
                IFOKDO(err, getDocument()->sendMessage(i18nc("An information to the user", "The account '%1' has been merged with account '%2'", child.getDisplayName(), parentAccount.getDisplayName()), SKGDocument::Hidden));

                IFOKDO(err, parentAccount.merge(child))
            }
        }
    } else if (iData->hasFormat("application/skg.unit.ids")) {
        QByteArray encodedData = iData->data("application/skg.unit.ids");
        QDataStream stream(&encodedData, QIODevice::ReadOnly);
        QStringList newItems;

        if (iParent.isValid()) {
            SKGBEGINTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Merge units"), err);
            SKGUnitObject parentUnit(getObject(iParent));
            while (!stream.atEnd() && !err) {
                int o_id;
                QString o_table;
                stream >> o_table;
                stream >> o_id;

                SKGUnitObject child(getDocument(), o_id);
                err = child.load();

                // Send message
                IFOKDO(err, getDocument()->sendMessage(i18nc("An information to the user", "The unit '%1' has been merged with unit '%2'", child.getDisplayName(), parentUnit.getDisplayName()), SKGDocument::Hidden));

                IFOKDO(err, parentUnit.merge(child))
            }
        }
    } else if (iData->hasFormat("application/skg.refund.ids")) {
        QByteArray encodedData = iData->data("application/skg.refund.ids");
        QDataStream stream(&encodedData, QIODevice::ReadOnly);
        QStringList newItems;

        if (iParent.isValid()) {
            SKGBEGINTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Merge trackers"), err);
            SKGTrackerObject parentTracker(getObject(iParent));
            while (!stream.atEnd() && !err) {
                int o_id;
                QString o_table;
                stream >> o_table;
                stream >> o_id;

                SKGTrackerObject child(getDocument(), o_id);
                err = child.load();

                // Send message
                IFOKDO(err, parentTracker.getDocument()->sendMessage(i18nc("An information to the user", "The tracker '%1' has been merged with tracker '%2'", child.getDisplayName(), parentTracker.getDisplayName()), SKGDocument::Hidden));

                IFOKDO(err, parentTracker.merge(child))
            }
        }
    }
    SKGMainPanel::displayErrorMessage(err);
    return !err;
}

void SKGObjectModel::dataModified(const QString& iTableName, int iIdTransaction)
{
    if (getRealTable() == iTableName || iTableName.isEmpty() || getRealTable() == "doctransaction") {
        SKGTRACEINFUNC(1);
        if (iTableName == "category") {
            // Full refresh
            m_isResetRealyNeeded = true;
            refresh();
        } else {
            SKGObjectModelBase::dataModified(iTableName, iIdTransaction);
        }
    } else {
        SKGObjectModelBase::dataModified(iTableName, iIdTransaction);
    }
}

QString SKGObjectModel::formatMoney(double iValue) const
{
    return getDocument()->formatMoney(iValue, static_cast<SKGDocumentBank*>(getDocument())->getPrimaryUnit(), false);
}



