/***************************************************************************
 *   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 SKGObjectModelBase.
*
* @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgobjectmodelbase.h"
#include "skgtraces.h"
#include "skgdocument.h"
#include "skgnodeobject.h"
#include "skgtransactionmng.h"
#include "skgmainpanel.h"

#include <kstandarddirs.h>
#include <klocalizedstring.h>
#include <klocale.h>
#include <kicon.h>
#include <kcolorscheme.h>

#include <QApplication>
#include <QMimeData>
#include <QDir>
#include <QColor>

SKGObjectModelBase::SKGObjectModelBase(SKGDocument* iDocument,
                                       const QString& iTable,
                                       const QString& iWhereClause,
                                       QWidget* parent,
                                       const QString& iParentAttribute,
                                       bool iResetOnCreation)
    : QAbstractItemModel(parent), m_isResetRealyNeeded(iResetOnCreation), m_document(iDocument),
      m_whereClause(iWhereClause),
      m_parentAttribute(iParentAttribute), m_refreshBlocked(false)
{
    SKGTRACEIN(1, "SKGObjectModelBase::SKGObjectModelBase");

    setTable(iTable);

    connect((const QObject*) m_document, SIGNAL(tableModified(QString,int)), this, SLOT(dataModified(QString,int)));
    connect(SKGMainPanel::getMainPanel(), SIGNAL(currentPageChanged()), this, SLOT(pageChanged()), Qt::QueuedConnection);
}

SKGObjectModelBase::~SKGObjectModelBase()
{
    SKGTRACEIN(1, "SKGObjectModelBase::~SKGObjectModelBase");
    clear();
    m_document = NULL;
}

void SKGObjectModelBase::clear()
{
    SKGTRACEIN(1, "SKGObjectModelBase::clear");
    QHashIterator<int, SKGObjectBase*> i(m_objectsHashTable);
    while (i.hasNext()) {
        i.next();
        SKGObjectBase* val = i.value();
        delete val;
        val = NULL;
    }

    m_listObjects.clear();
    m_parentChildRelations.clear();
    m_childParentRelations.clear();
    m_objectsHashTable.clear();
    m_objectsHashTableRows.clear();
}

int SKGObjectModelBase::getID(const SKGObjectBase* obj, int row) const
{
    if (!m_parentAttribute.isEmpty()) return (obj ? obj->getID() : 0);
    return (obj ? 100 * (obj->getID()) + row : 0);
}

QList<SKGDocument::SKGModelTemplate> SKGObjectModelBase::getSchemas() const
{
    return m_listSchema;
}

void SKGObjectModelBase::setSupportedAttributes(const QStringList& iListAttribute)
{
    SKGTRACEIN(1, "SKGObjectModelBase::setSupportedAttributes");
    m_listSupported.clear();
    m_listVisibility.clear();
    m_listSize.clear();

    QStringList l = iListAttribute;
    if (m_listSchema.count())  l += SKGServices::splitCSVLine(m_listSchema.at(0).schema);

    QStringList attributesAvailables;
    QStringList attributesAvailablesTmp;
    if (m_listSchema.count())  attributesAvailablesTmp = SKGServices::splitCSVLine(m_listSchema.at(0).schema);
    int nb = attributesAvailablesTmp.count();
    for (int i = 0; i < nb; ++i) {
        attributesAvailables.push_back(attributesAvailablesTmp.at(i).split('|').at(0));
    }

    nb = l.count();
    for (int i = 0; i < nb; ++i) {
        QStringList values = l.at(i).split('|');
        int nbValues = values.count();
        QString att = values.at(0);

        if (nbValues > 0 && !m_listSupported.contains(att) && attributesAvailables.contains(att)) {
            m_listSupported.push_back(att);
            bool visible = true;
            if (nbValues > 1) visible = (i == 0 || values.at(1) == "Y"); //First column is always visible to support grouping
            m_listVisibility.push_back(visible);
            if (nbValues > 2) m_listSize.push_back(SKGServices::stringToInt(values.at(2)));
            else m_listSize.push_back(-1);
        }
    }

    m_isResetRealyNeeded = true;
}

bool SKGObjectModelBase::setFilter(const QString& iWhereClause)
{
    if (iWhereClause != m_whereClause) m_isResetRealyNeeded = true;
    m_whereClause = iWhereClause;
    return m_isResetRealyNeeded;
}

void SKGObjectModelBase::setTable(const QString& iTable)
{
    if (iTable != m_table) {
        if (!m_table.isEmpty()) m_isResetRealyNeeded = true;
        m_table = iTable;
        m_realTable = SKGServices::getRealTable(m_table);
        if (m_document) m_listSchema = m_document->getDisplaySchemas(m_realTable);
    }
}

QString SKGObjectModelBase::getTable() const
{
    return m_table;
}

void SKGObjectModelBase::setGroupBy(const QString& iAttribute)
{
    if (iAttribute != m_groupby) {
        m_isResetRealyNeeded = true;
        m_groupby = iAttribute;
    }
}

QString SKGObjectModelBase::getGroupBy() const
{
    return m_groupby;
}

QString SKGObjectModelBase::getParentChildAttribute() const
{
    return m_parentAttribute;
}

QString SKGObjectModelBase::getRealTable() const
{
    return m_realTable;
}

QString SKGObjectModelBase::getWhereClause() const
{
    return m_whereClause;
}

SKGDocument* SKGObjectModelBase::getDocument() const
{
    return m_document;
}

void SKGObjectModelBase::buidCache()
{
    SKGTRACEIN(1, "SKGObjectModelBase::buidCache");
    m_doctransactionTable = (getRealTable() == "doctransaction");
    m_nodeTable = (getRealTable() == "node");

    //Get std colors
    KColorScheme scheme(QPalette::Normal);
    m_fontNegativeColor = qVariantFromValue(scheme.foreground(KColorScheme::NegativeText).color());
}

bool SKGObjectModelBase::blockRefresh(bool iBlocked)
{
    bool previous = m_refreshBlocked;
    m_refreshBlocked = iBlocked;
    return previous;
}

bool SKGObjectModelBase::isRefreshBlocked()
{
    return m_refreshBlocked;
}

void SKGObjectModelBase::refresh()
{
    if (!m_isResetRealyNeeded || isRefreshBlocked()) return;
    SKGTRACEIN(1, "SKGObjectModelBase::refresh");
    SKGTRACEL(1) << "Table:" << getTable() << endl;
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
    Q_EMIT beforeReset();
    {
        _SKGTRACEIN(10, "QAbstractItemModel::refresh-remove");
        clear();
        m_listAttibutes.clear();
        m_listAttributeTypes.clear();
        /*beginRemoveRows(QModelIndex(), 0, rowCount(QModelIndex())-1);
        endRemoveRows();*/
    }

    {
        _SKGTRACEIN(10, "QAbstractItemModel::refresh-fill");
        QStringList listAttibutesTmp;
        if (m_document && m_document->getAttributesList(m_table, listAttibutesTmp).isSucceeded()) {
            m_isResetRealyNeeded = false;
            if (listAttibutesTmp.count()) {
                //Filter attributes
                int nb = m_listSupported.count();
                if (nb == 0) {
                    setSupportedAttributes(QStringList());
                    nb = m_listSupported.count();
                }
                for (int i = 0 ; i < nb ; ++i) {
                    QString att = m_listSupported[i];
                    if (listAttibutesTmp.contains(att) || att.startsWith(QLatin1String("p_"))) {
                        m_listAttibutes.push_back(att);
                        if (att.startsWith(QLatin1String("t_")) || att.startsWith(QLatin1String("p_"))) m_listAttributeTypes.push_back(SKGServices::TEXT);
                        else if (att.startsWith(QLatin1String("f_"))) m_listAttributeTypes.push_back(SKGServices::FLOAT);
                        else if (att.startsWith(QLatin1String("i_"))) m_listAttributeTypes.push_back(SKGServices::INTEGER);
                        else if (att.startsWith(QLatin1String("d_"))) m_listAttributeTypes.push_back(SKGServices::DATE);
                        else m_listAttributeTypes.push_back(SKGServices::OTHER);
                    }
                }

                //Remove double
                nb = m_listAttibutes.count();
                for (int i = nb - 1 ; i >= 0 ; --i) {
                    QString att = m_listAttibutes[i];
                    if (att.contains("_REAL")) {
                        att.replace("_REAL", "_");
                        int p = m_listAttibutes.indexOf(att);
                        if (p == -1) {
                            att = att.toLower();
                            p = m_listAttibutes.indexOf(att);
                        }
                        if (p != -1) {
                            ;
                            m_listAttibutes.removeAt(p);
                            m_listAttributeTypes.removeAt(p);
                            if (p < i) --i;
                        }
                    }
                }
            }

            //Get objects
            QString wc = m_whereClause;
            if (!m_groupby.isEmpty()) {
                if (wc.isEmpty()) wc = "1=1";
                if (m_groupby.startsWith(QLatin1String("p_")))
                    wc += " ORDER BY (SELECT t_value FROM parameters WHERE t_uuid_parent=" % getTable() % ".id||'-" % getRealTable() % "' AND t_name='" % m_groupby % "')";
                else wc += " ORDER BY " % m_groupby;
            }
            if (m_document) m_document->getObjects(m_table, wc , m_listObjects);

            //Initialize object to treat
            QString currentGroup;
            int currentGoupId = 0;
            bool groupByDate = (m_groupby.startsWith(QLatin1String("d_")));
            bool groupByNum = (m_groupby.startsWith(QLatin1String("f_")) || m_groupby.startsWith(QLatin1String("i_")));
            int nb = m_listObjects.count();
            SKGTRACEL(1) << nb << " objects found" << endl;
            for (int t = 0; t < nb; ++t) {
                SKGObjectBase* c = new SKGObjectBase(m_listObjects.at(t));
                int id = getID(c, t);

                int idparent = 0;
                if (m_groupby.isEmpty()) {
                    //Grouping by tree
                    if (!m_parentAttribute.isEmpty()) {
                        int idp = SKGServices::stringToInt(c->getAttribute(m_parentAttribute));
                        if (idp > 0) idparent = idp;
                    }
                } else {
                    //Grouping on specified attribute
                    QString att = getAttributeForGrouping(*c, m_groupby);
                    if (groupByDate) {
                        QDate date = SKGServices::stringToTime(att).date();
                        if (date == QDate::currentDate()) att = i18nc("A group name for grouping by date attibute", "Today");
                        else if (date > QDate::currentDate()) {
                            //In future
                            if (date < QDate::currentDate().addDays(7)) att = i18nc("A group name for grouping by date attibute", "Next 7 days");
                            else if (date < QDate::currentDate().addDays(15)) att = i18nc("A group name for grouping by date attibute", "Next 15 days");
                            else if (date < QDate::currentDate().addMonths(1)) att = i18nc("A group name for grouping by date attibute", "Next month");
                            else if (date < QDate::currentDate().addMonths(3)) att = i18nc("A group name for grouping by date attibute", "Next 3 months");
                            else if (date < QDate::currentDate().addMonths(6)) att = i18nc("A group name for grouping by date attibute", "Next 6 months");
                            else if (date < QDate::currentDate().addYears(1)) att = i18nc("A group name for grouping by date attibute", "Next year");
                            else if (date < QDate::currentDate().addYears(3)) att = i18nc("A group name for grouping by date attibute", "Next 3 years");
                            else att = i18nc("A group name for grouping by date attibute", "Far away in the future");
                        } else {
                            //In the past
                            if (date > QDate::currentDate().addDays(-7)) att = i18nc("A group name for grouping by date attibute", "Last 7 days");
                            else if (date > QDate::currentDate().addDays(-15)) att = i18nc("A group name for grouping by date attibute", "Last 15 days");
                            else if (date > QDate::currentDate().addMonths(-1)) att = i18nc("A group name for grouping by date attibute", "Last month");
                            else if (date > QDate::currentDate().addMonths(-3)) att = i18nc("A group name for grouping by date attibute", "Last 3 months");
                            else if (date > QDate::currentDate().addMonths(-6)) att = i18nc("A group name for grouping by date attibute", "Last 6 months");
                            else if (date > QDate::currentDate().addYears(-1)) att = i18nc("A group name for grouping by date attibute", "Last year");
                            else if (date > QDate::currentDate().addYears(-3)) att = i18nc("A group name for grouping by date attibute", "Last 3 years");
                            else att = i18nc("A group name for grouping by date attibute", "Far away in the past");
                        }
                    } else if (groupByNum) {
                        if (att != QChar(8734)) {
                            double d = SKGServices::stringToDouble(att);
                            if (d > 10000.0) att = i18nc("A group name for grouping by numerical attibute", "> 10000");
                            else if (d > 1000.0) att = i18nc("A group name for grouping by numerical attibute", "> 1000");
                            else if (d > 100.0) att = i18nc("A group name for grouping by numerical attibute", "> 100");
                            else if (d > 10.0) att = i18nc("A group name for grouping by numerical attibute", "> 10");
                            else if (d > 0.0) att = i18nc("A group name for grouping by numerical attibute", "> 0");
                            else if (d < -10000.0) att = i18nc("A group name for grouping by numerical attibute", "< -10000");
                            else if (d < -1000.0) att = i18nc("A group name for grouping by numerical attibute", "< -1000");
                            else if (d < -100.0) att = i18nc("A group name for grouping by numerical attibute", "< -100");
                            else if (d < -10.0) att = i18nc("A group name for grouping by numerical attibute", "< -10");
                            else if (d < 0.0) att = i18nc("A group name for grouping by numerical attibute", "< 0");
                            else att = i18nc("A group name for grouping by numerical attibute", "= 0");
                        }
                    }
                    if (att != currentGroup || currentGoupId == 0) {
                        //Addition of a new group
                        currentGroup = att;
                        currentGoupId--;
                        SKGObjectBase* group = new SKGObjectBase(m_document, "", currentGoupId);
                        group->setAttribute("t_title", currentGroup);

                        m_childParentRelations.insert(currentGoupId, 0);

                        QList<int> childrensids = m_parentChildRelations.value(0);
                        childrensids.push_back(currentGoupId);

                        m_parentChildRelations.insert(0, childrensids);
                        m_objectsHashTableRows.insert(currentGoupId, childrensids.count() - 1);
                        m_objectsHashTable.insert(currentGoupId, group);
                    }

                    idparent = currentGoupId;
                }

                m_childParentRelations.insert(id, idparent);

                QList<int> childrensids = m_parentChildRelations.value(idparent);
                childrensids.push_back(id);

                m_parentChildRelations.insert(idparent, childrensids);
                m_objectsHashTableRows.insert(id, childrensids.count() - 1);
                m_objectsHashTable.insert(id, c);
            }
        }

        //Build cache
        buidCache();

        {
            SKGTRACEIN(1, "SKGObjectModelBase::refresh-reset");
            QAbstractItemModel::reset();
        }
    }
    Q_EMIT afterReset();
    QApplication::restoreOverrideCursor();
}

bool SKGObjectModelBase::hasChildren(const QModelIndex& parent) const
{
    if (parent.column() > 0) return false;
    _SKGTRACEIN(10, "SKGObjectModelBase::hasChildren");

    if (parent.isValid() && m_parentAttribute.isEmpty() && m_groupby.isEmpty()) return false;
    return QAbstractItemModel::hasChildren(parent);
}

int SKGObjectModelBase::rowCount(const QModelIndex& parent) const
{
    if (parent.column() > 0) return 0;
    _SKGTRACEIN(10, "SKGObjectModelBase::rowCount");

    int idParent = 0;
    if (parent.isValid()) idParent = parent.internalId();

    QList<int> children = m_parentChildRelations.value(idParent);
    //SKGTRACE << table << "-rowCount(" << idParent << " )=" << children.count() << endl;
    return children.count();

}

int SKGObjectModelBase::columnCount(const QModelIndex& parent) const
{
    Q_UNUSED(parent);
    return m_listAttibutes.count();
}

QModelIndex SKGObjectModelBase::index(int row, int column, const QModelIndex& parent) const
{
    if (!hasIndex(row, column, parent)) return QModelIndex();
    _SKGTRACEIN(10, "SKGObjectModelBase::index");

    int idParent = 0;
    if (parent.isValid())  idParent = parent.internalId();

    int idChild = m_parentChildRelations.value(idParent).at(row);

    //SKGTRACE << table << "-" << idParent << "(" << row << ") ==> " << idChild << endl;
    return (idChild ? createIndex(row, column, idChild) : QModelIndex());
}

QModelIndex SKGObjectModelBase::parent(const QModelIndex& index) const
{
    if (!index.isValid()) return QModelIndex();
    _SKGTRACEIN(10, "SKGObjectModelBase::parent");

    int idChild = 0;
    if (index.isValid()) idChild = index.internalId();

    int idParent = m_childParentRelations.value(idChild);
    int row = m_objectsHashTableRows.value(idParent);
    //SKGTRACE << table << "-" << idChild << "(" << row << ") <== " << idParent << endl;
    return idParent ? createIndex(row, 0, idParent) : QModelIndex();
}

int SKGObjectModelBase::getIndexAttribute(const QString& iAttributeName) const
{
    int output = m_listAttibutes.indexOf(iAttributeName);
    if (output == -1) {
        SKGTRACE << "[" << iAttributeName << "] not found in [" << getRealTable() << "]" << endl;
    }
    return output;
}

QString SKGObjectModelBase::getAttribute(int iIndex) const
{
    return m_listAttibutes.value(iIndex);
}

SKGServices::AttributeType SKGObjectModelBase::getAttributeType(int iIndex) const
{
    return m_listAttributeTypes.value(iIndex);
}

QVariant SKGObjectModelBase::headerData(int section, Qt::Orientation orientation, int role) const
{
    _SKGTRACEIN(10, "SKGObjectModelBase::headerData");

    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);

            return getDocument()->getDisplay(getTable() % '.' % att);
        } else if (role == Qt::UserRole) {
            QString att;
            if (section >= 0 && section < m_listAttibutes.count()) att = m_listAttibutes.at(section);
            else att = SKGServices::intToString(section);

            int indexAtt = m_listSupported.indexOf(att);

            att = getDocument()->getDisplay(getTable() % '.' % att);


            if (indexAtt >= 0 && indexAtt < m_listVisibility.count()) {
                bool visible = m_listVisibility.at(indexAtt);
                att += QString("|") % (visible ? "Y" : "N");
                if (indexAtt >= 0 && indexAtt < m_listSize.count()) att += '|' % SKGServices::intToString(m_listSize.at(indexAtt));
            }
            return att;
        } else if (role == Qt::DecorationRole) {
            QString att;
            if (section >= 0 && section < m_listAttibutes.count()) att = m_listAttibutes[section];
            else att = SKGServices::intToString(section);

            return getDocument()->getIcon(getTable() % '.' % att);
        }
    }
    return QVariant();
}

SKGObjectBase SKGObjectModelBase::getObject(const QModelIndex& index) const
{
    SKGObjectBase* obj = getObjectPointer(index);
    SKGObjectBase output;
    if (obj != NULL) output = *obj;
    return output;
}

SKGObjectBase* SKGObjectModelBase::getObjectPointer(const QModelIndex& index) const
{
    _SKGTRACEIN(10, "SKGObjectModelBase::getObject");
    //SKGTRACE << "getObject:" << index.internalId() << endl;
    return m_objectsHashTable.value(index.internalId());
}

QVariant SKGObjectModelBase::data(const QModelIndex& index, int role) const
{
    if (!index.isValid()) return QVariant();
    _SKGTRACEIN(10, "SKGObjectModelBase::data");

    switch (role) {
    case Qt::BackgroundRole: {
        SKGObjectBase* obj = getObjectPointer(index);
        if (obj->getTable().isEmpty()) {
            //This is a group
            return QApplication::palette().brush(QPalette::Button);
        }
        break;
    }
    case Qt::DisplayRole:
    case Qt::EditRole:
    case Qt::UserRole: {
        SKGObjectBase* obj = getObjectPointer(index);
        QString att = m_listAttibutes[index.column()];
        if (obj->getTable().isEmpty()) {
            //This is a group
            if (index.column() == 0) {
                if (role == Qt::UserRole) return -obj->getID(); //For sorting
                else return i18nc("How to display a grouping title. Here \"title (count)\"", "%1: %2 (%3)",
                                      getDocument()->getDisplay(m_groupby),
                                      obj->getAttribute("t_title"),
                                      rowCount(SKGObjectModelBase::index(index.row(), 0)));
            }
            return "";
        } else {
            QString val;
            if (att.startsWith(QLatin1String("p_"))) {
                //This is a property
                val = obj->getProperty(att.right(att.count() - 2));
            } else {
                //This is a real attribute
                val = obj->getAttribute(att);
            }

            switch (getAttributeType(index.column())) {
            case SKGServices::FLOAT: {
                double dval = SKGServices::stringToDouble(val);
                return dval;
            }
            case SKGServices::INTEGER: {
                return SKGServices::stringToInt(val);
            }
            case SKGServices::DATE: {
                QDate dval = SKGServices::stringToTime(val).date();
                if (role == Qt::DisplayRole) {
                    return KGlobal::locale()->formatDate(dval, (KLocale::DateFormat) skgbasegui_settings::date_format());
                } else {
                    return dval;
                }
            }
            default:
            {}
            }

            if (m_doctransactionTable && att == "t_savestep") return "";

            //return val+"("+SKGServices::intToString(rowCount(index))+")";
            return val;
        }
    }

    case Qt::FontRole: {
        SKGObjectBase* obj = getObjectPointer(index);
        //Text color
        if (obj->getTable().isEmpty()) {
            //This is a group
            if (index.column() == 0) {
                QFont f;
                f.setBold(true);

                return qVariantFromValue(f);
            }
            return "";
        }
        break;
    }
    case Qt::TextColorRole: {
        //Text color
        if (getAttributeType(index.column()) == SKGServices::FLOAT) {
            QVariant value_displayed = SKGObjectModelBase::data(index, Qt::UserRole);
            bool ok = false;
            double value_double = value_displayed.toDouble(&ok);
            if (ok && value_double < 0) return m_fontNegativeColor;
        }
        break;
    }
    case Qt::TextAlignmentRole: {
        //Text alignment
        SKGServices::AttributeType attType = getAttributeType(index.column());
        return (int)(Qt::AlignVCenter | (attType == SKGServices::FLOAT || attType == SKGServices::INTEGER ? Qt::AlignRight : Qt::AlignLeft));
    }
    case Qt::DecorationRole: {
        //Decoration
        SKGObjectBase* obj = getObjectPointer(index);
        QString att = m_listAttibutes[index.column()];
        if (obj->getTable().isEmpty()) {
            //This is a group
            if (index.column() == 0) return qVariantFromValue((QIcon) KIcon("arrow-right"));;
            return "";
        } else {
            if (index.column() == 0 && m_nodeTable) {
                SKGNodeObject node(*obj);
                return qVariantFromValue(node.getIcon());
            } else if (index.column() == 0 && m_doctransactionTable) {
                return qVariantFromValue((QIcon) KIcon(obj->getAttribute("t_mode") == "U" ? "edit-undo" : "edit-redo"));
            } else if (m_doctransactionTable && att == "t_savestep") {
                if (obj->getAttribute("t_savestep") == "Y") return qVariantFromValue((QIcon) KIcon("document-save"));
            }
        }
        break;
    }
    case Qt::ToolTipRole: {
        //Tooltip
        QString toolTipString;
        SKGObjectBase* obj = getObjectPointer(index);

        if (obj && m_document) {
            if (m_doctransactionTable) {
                QStringList msg;
                QList<SKGDocument::MessageType> type;
                m_document->getMessages(obj->getID(), msg, type);
                int nbMessages = msg.count();
                if (nbMessages) {
                    for (int i = 0; i < nbMessages; ++i) {
                        if (i != 0) toolTipString += '\n';
                        toolTipString += msg.at(i);
                    }
                }
            } else if (getAttributeType(index.column()) == SKGServices::DATE) {
                QString att = m_listAttibutes[index.column()];
                QString val = obj->getAttribute(att);

                QDate dval = SKGServices::stringToTime(val).date();
                QString fancyDate = KGlobal::locale()->formatDate(dval, KLocale::FancyShortDate);
                QString shortDate = KGlobal::locale()->formatDate(dval, KLocale::ShortDate);


                if (shortDate != fancyDate) toolTipString = shortDate;
            }

            //Add properties
            QStringList props = obj->getProperties();
            if (props.count() > 0 && !toolTipString.isEmpty()) toolTipString += '\n';
            foreach(const QString & prop, props) {
                if (!toolTipString.isEmpty()) toolTipString += '\n';
                toolTipString += i18nc("To display a property and its value", "%1=%2", prop, obj->getProperty(prop));
            }

            //Add UUID
            IFSKGTRACEL(1) {
                toolTipString += '\n' % obj->getUniqueID();
            }
        }
        return toolTipString;
        break;
    }
    case 99: {
        SKGObjectBase* obj = getObjectPointer(index);
        if (obj) return SKGServices::stringToDouble(obj->getAttribute("f_sortorder"));

        break;
    }
    default :
    {}
    }

    return QVariant();
}

Qt::ItemFlags SKGObjectModelBase::flags(const QModelIndex& index) const
{
    _SKGTRACEIN(10, "SKGObjectModelBase::flags");

    Qt::ItemFlags flags = QAbstractItemModel::flags(index) | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;

    if (m_nodeTable) {
        flags |= Qt::ItemIsEditable;
        if (index.isValid()) flags |= Qt::ItemIsDragEnabled;
    }
    if (index.isValid()) {
        QString att = m_listAttibutes[index.column()];
        if (att.toLower() == att || !getDocument()->getRealAttribute(att).isEmpty()) flags |= Qt::ItemIsEditable;
        SKGObjectBase* obj = getObjectPointer(index);
        if (obj->getTable().isEmpty()) flags = Qt::ItemIsEnabled;
    }

    return flags;

}

bool SKGObjectModelBase::setData(const QModelIndex& index, const QVariant& value, int role)
{
    if (!index.isValid()) return false;

    if (role == Qt::EditRole) {
        SKGError err;
        if (m_nodeTable) {
            SKGNodeObject obj(getObject(index));
            QString name = value.toString();
            SKGBEGINTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Bookmark update '%1'", name), err);
            err = obj.setName(name);
            if (!err) err = obj.save();
        } else {
            SKGObjectBase obj(getObject(index));

            SKGBEGINTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Update object"), err);
            SKGObjectBase obj2(obj.getDocument(), obj.getRealTable(), obj.getID()); //To be sure this is not a complex object
            QString att = m_listAttibutes[index.column()];
            err = obj2.setAttribute(att, att.startsWith(QLatin1String("d_")) && value.canConvert<QDateTime>() ? SKGServices::dateToSqlString(value.toDateTime()) : value.toString());
            if (!err) err = obj2.save();
        }

        SKGMainPanel::displayErrorMessage(err);
        return !err;
    }
    return QAbstractItemModel::setData(index, value, role);
}

Qt::DropActions SKGObjectModelBase::supportedDragActions() const
{
    return (m_nodeTable ? Qt::MoveAction : Qt::IgnoreAction);
}

Qt::DropActions SKGObjectModelBase::supportedDropActions() const
{
    return Qt::MoveAction | Qt::LinkAction;
}

QStringList SKGObjectModelBase::mimeTypes() const
{
    QStringList types;
    types << "application/skg." % getRealTable() % ".ids";
    types << "application/data";
    types << "text/uri-list";
    return types;
}

QMimeData* SKGObjectModelBase::mimeData(const QModelIndexList& indexes) const
{
    QMimeData* mimeData = new QMimeData();
    QByteArray encodedData;

    QDataStream stream(&encodedData, QIODevice::WriteOnly);

    QString t = getTable();
    foreach(const QModelIndex & index, indexes) {
        if (index.isValid() && index.column() == 0) {
            SKGObjectBase obj = getObject(index);
            t = obj.getRealTable();
            stream << t;
            stream << obj.getID();
        }
    }

    mimeData->setData("application/skg." % t % ".ids", encodedData);
    return mimeData;
}

bool SKGObjectModelBase::dropMimeData(const QMimeData* data,
                                      Qt::DropAction action,
                                      int row, int column,
                                      const QModelIndex& parent)
{
    Q_UNUSED(row);
    if (action == Qt::IgnoreAction) return true;
    if (!data || !(data->hasFormat("application/skg.node.ids") || data->hasUrls())) return false; //TODO: accept all
    if (column > 0) return false;

    SKGError err;
    //Drop files
    if (data->hasUrls() && parent.isValid() && getRealTable() != "node") {
        QList<QUrl> urls = data->urls();
        int nb = urls.count();
        {
            SKGObjectBase obj(getObject(parent));

            SKGBEGINPROGRESSTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Property creation"), err, nb);
            for (int i = 0; !err && i < nb; ++i) {
                QString name = i18n("File");
                int index = 1;
                while (!err && !obj.getProperty(name).isEmpty()) {
                    index++;
                    name = i18n("File") % " (" % SKGServices::intToString(index) % ')';
                }
                err = obj.setProperty(name, urls.at(i).toLocalFile(), action == Qt::LinkAction ? "" : urls.at(i).toLocalFile());
                if (!err) err = getDocument()->stepForward(i + 1);
            }
        }
    }
    //Drop nodes
    else if (data->hasFormat("application/skg.node.ids")) {
        QByteArray encodedData = data->data("application/skg.node.ids");
        QDataStream stream(&encodedData, QIODevice::ReadOnly);
        QStringList newItems;

        QModelIndex parentIndex = parent;
        SKGNodeObject parentNode;
        if (parentIndex.isValid()) {
            parentNode = getObject(parentIndex);
            if (!parentNode.isFolder()) {
                //The parent is not a directory
                parentNode.getParentNode(parentNode);
                parentIndex = parentIndex.parent();
            }
        }
        {
            SKGBEGINTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Move bookmark"), err);

            double min = 0;
            double max = 0;
            if (row >= 1) {
                QModelIndex previousIndex = SKGObjectModelBase::index(row - 1, 0, parentIndex);
                SKGNodeObject previousObject(getObject(previousIndex));
                min = previousObject.getOrder();
            }

            if (row >= rowCount(parentIndex)) max = min + 1;
            else {
                QModelIndex nextIndex = SKGObjectModelBase::index(row, 0, parentIndex);
                SKGNodeObject nextObject(getObject(nextIndex));
                max = nextObject.getOrder();
            }
            if (max <= min) max = min + 1;

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

                //Set parent
                SKGNodeObject child(getDocument(), o_id);
                err = child.load();
                if (!err) {
                    if (parentIndex.isValid()) err = child.setParentNode(parentNode);
                    else err = child.removeParentNode();
                }

                //Set order
                if (!err)  err = child.setOrder((min + max) / 2.0);

                //Save
                if (!err) err = child.save();
            }
        }
    }
    SKGMainPanel::displayErrorMessage(err);
    return !err;
}

void SKGObjectModelBase::pageChanged()
{
    if (m_isResetRealyNeeded) dataModified("", 0);
}

void SKGObjectModelBase::dataModified(const QString& iTableName, int iIdTransaction)
{
    if (getTable() == iTableName || iTableName.isEmpty()) {
        SKGTRACEIN(1, "SKGObjectModelBase::dataModified");
        SKGTRACEL(1) << "getTable=" << getRealTable() << endl;
        SKGTRACEL(1) << "Parameters=" << iTableName << " , " << iIdTransaction << endl;
        SKGTabPage* page = SKGTabPage::parentTabPage(qobject_cast< QWidget* >(this->QObject::parent()));
        SKGTabPage* cpage = SKGMainPanel::getMainPanel() ? SKGMainPanel::getMainPanel()->currentPage() : NULL;
        if (page != NULL && page != cpage) {
            m_isResetRealyNeeded = true;
            return;
        }

        //Full refresh
        m_isResetRealyNeeded = true;

        //Refresh model
        refresh();
    }
}

QString SKGObjectModelBase::getAttributeForGrouping(const SKGObjectBase& iObject, const QString& iAttribute) const
{
    if (iAttribute.startsWith(QLatin1String("p_"))) {
        //This is a property
        return iObject.getProperty(iAttribute.right(iAttribute.count() - 2));
    } else {
        //This is a real attribute
        return iObject.getAttribute(iAttribute);
    }
}

#include "skgobjectmodelbase.moc"
