/***************************************************************************
              userlistmodel.cpp  -  User List Model implementation
                             -------------------
    begin                : Sun Nov 18 2007
    copyright            : (C) 2007 by Edward Sheldrake
    email                : ejs1920@yahoo.co.uk
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "userlistmodel.h"

#include <QtAlgorithms>
#include <QtGlobal>

/* need to convert shared bytes to size string */
#include "dcguiutils.h"

/** */
UserListModel::UserListModel(QObject * parent) : QAbstractItemModel(parent) {
    sortColumn = COLUMN_NICK;
    sortOrder = Qt::AscendingOrder;
    stripper.setPattern("\\[.*\\]");
    stripper.setMinimal(true);
}

/** delete all items in model */
UserListModel::~UserListModel() {
    clear();
}

/** */
int UserListModel::rowCount(const QModelIndex & /* index */) const {
    return itemList.size();
}

/** */
int UserListModel::columnCount(const QModelIndex & /* index */) const {
    return 9;
}

/** */
QVariant UserListModel::data(const QModelIndex & index, int role) const {
    if (!index.isValid()) {
        return QVariant();
    }

    UserListItem * item = itemList.value(index.row());

    if (item == 0) {
        return QVariant();
    }

    if (role == Qt::DisplayRole) // text
    {
        switch (index.column()) {
            case COLUMN_NICK: return item->nick;
            case COLUMN_COMMENT: return item->comment;
            case COLUMN_TAG: return item->tag;
            case COLUMN_SPEED: return item->speed;
            case COLUMN_EMAIL: return item->email;
            case COLUMN_SHARE: return DCGuiUtils::GetSizeString(item->shared);
            case COLUMN_IP: return item->ip;
            case COLUMN_LOCKPK: return item->lockpk;
            case COLUMN_SUPPORTS: return item->supports;
        }
    } else if (role == Qt::DecorationRole) // icon
    {
        if (index.column() == COLUMN_NICK) {
            // to check icons are being cached properly
            // printf("row=%d column=%d cacheKey=%llu\n",index.row(),index.column(),item->pixmap.cacheKey());
            return item->pixmap;
        }
    } else if (role == Qt::ToolTipRole) // tool tip
    {
        if (index.column() == COLUMN_SHARE) {
            return QString::number(item->shared);
        }
    } else if (role == Qt::TextAlignmentRole) // text alignment
    {
        if (index.column() == COLUMN_SHARE) {
            return Qt::AlignRight;
        }
    } else if (role == Qt::ForegroundRole) // text colour
    {
        if ((index.column() == COLUMN_NICK) && (item->highlight)) {
            return QColor(255, 0, 0);
        }
    }

    return QVariant();
}

/** */
QVariant UserListModel::headerData(int section, Qt::Orientation orientation, int role) const {
    if ((orientation == Qt::Horizontal) && (role == Qt::DisplayRole)) {
        switch (section) {
            case COLUMN_NICK: return tr("Nick");
            case COLUMN_COMMENT: return tr("Comment");
            case COLUMN_TAG: return tr("Tag");
            case COLUMN_SPEED: return tr("Speed");
            case COLUMN_EMAIL: return tr("Email");
            case COLUMN_SHARE: return tr("Share");
            case COLUMN_IP: return tr("IP");
            case COLUMN_LOCKPK: return tr("Lock/PK");
            case COLUMN_SUPPORTS: return tr("Supports");
        }
    }

    return QVariant();
}

namespace {

template <Qt::SortOrder order>
struct Compare {
    void static sort(int col, QList<UserListItem*>& items) {
        qStableSort(items.begin(), items.end(), getAttrComp(col));
    }

    QList<UserListItem*>::iterator static insertSorted(int col, QList<UserListItem*>& items, UserListItem* item) {
        return qLowerBound(items.begin(), items.end(), item, getAttrComp(col));
    }

    private:
        typedef bool (*AttrComp)(const UserListItem* l, const UserListItem* r);
        AttrComp static getAttrComp(int column) {
            switch (column) {
                case COLUMN_NICK:
                    return AttrCmp<QString, &UserListItem::nick_lc>;
                    break;
                case COLUMN_COMMENT:
                    return AttrCmp<QString, &UserListItem::comment_lc>;
                    break;
                case COLUMN_TAG:
                    return AttrCmp<QString, &UserListItem::tag_lc>;
                    break;
                case COLUMN_SPEED:
                    return AttrCmp<QString, &UserListItem::speed_lc>;
                    break;
                case COLUMN_EMAIL:
                    return AttrCmp<QString, &UserListItem::email_lc>;
                    break;
                case COLUMN_SHARE:
                    return AttrCmp<ulonglong, &UserListItem::shared>;
                    break;
                case COLUMN_IP:
                    return AttrCmp<QString, &UserListItem::ip>;
                    break;
                case COLUMN_LOCKPK:
                    return AttrCmp<QString, &UserListItem::lockpk>;
                    break;
                case COLUMN_SUPPORTS:
                    return AttrCmp<QString, &UserListItem::supports>;
                    break;
            }
            Q_ASSERT_X(false, "getAttrComp", QString("Unknown column %1").arg(column).toLocal8Bit().constData());
            return 0;
        }
        template <typename T, T (UserListItem::*attr)>
        bool static AttrCmp(const UserListItem * l, const UserListItem * r) {
            if (l->sorttop == r->sorttop)
                return Cmp(l->*attr, r->*attr);
            else
                return l->sorttop;
       }
        template <typename T>
        bool static Cmp(const T& l, const T& r);
};

template <> template <typename T>
bool inline Compare<Qt::AscendingOrder>::Cmp(const T& l, const T& r) {
    return l < r;
}

template <> template <typename T>
bool inline Compare<Qt::DescendingOrder>::Cmp(const T& l, const T& r) {
    return l > r;
}

template <> template <>
bool inline Compare<Qt::AscendingOrder>::Cmp(const QString& l, const QString& r) {
    return Cmp(QString::localeAwareCompare(l, r), 0);
}

template <> template <>
bool inline Compare<Qt::DescendingOrder>::Cmp(const QString& l, const QString& r) {
    return Cmp(QString::localeAwareCompare(l, r), 0);
}

} //namespace

/** */
void UserListModel::sort(int column, Qt::SortOrder order) {
    sortColumn = column;
    sortOrder = order;

    if (column == -1) // sorting disabled
    {
        return;
    }

    emit layoutAboutToBeChanged();
    if (order == Qt::AscendingOrder)
        Compare<Qt::AscendingOrder>().sort(column, itemList);
    else if (order == Qt::DescendingOrder)
        Compare<Qt::DescendingOrder>().sort(column, itemList);

    for (int i = 0; i < itemList.size(); ++i) {
        itemList.at(i)->index = i;
    }

    emit layoutChanged();
}

/** */
QModelIndex UserListModel::index(int row, int column, const QModelIndex & parent) const {
    if (!hasIndex(row, column, parent))
        return QModelIndex();

    return createIndex(row, column);
}

/** */
QModelIndex UserListModel::parent(const QModelIndex & /* parent */) const {
    return QModelIndex();
}

/** */
void UserListModel::clear() {
    emit layoutAboutToBeChanged();

    itemList.clear();
    itemHash.clear();

    emit layoutChanged();
}

/** */
bool UserListModel::hasNick(const QString & nick) const {
    return itemHash.contains(nick);
}

/** */
void UserListModel::removeUser(const QString & nick) {
    itemHashType::iterator iter = itemHash.find(nick);
    if (iter == itemHash.end())
        return;

    const int index = iter.value().index;
    beginRemoveRows(QModelIndex(), index, index);

    itemList.removeAt(index);

    // destroy item
    itemHash.erase(iter);

    // fix indexes
    for (int i = index; i < itemList.size(); ++i)
        itemList.at(i)->index = i;

    endRemoveRows();
}

/** */
void UserListModel::addUser(const QString nick,
        const QPixmap icon,
        const QString comment,
        const QString tag,
        const QString speed,
        const QString email,
        const ulonglong share,
        const QString ip,
        const QString lockpk,
        const QString supports) {
    itemHashType::iterator iter = itemHash.find(nick);
    if (iter != itemHash.end()) {
        UserListItem & item = iter.value();
        item.pixmap = icon;
        item.comment = comment;
        item.tag = tag;
        item.speed = speed;
        item.email = email;
        item.shared = share;
        item.ip = ip;
        item.lockpk = lockpk;
        item.supports = supports;

        item.nick_lc = nick.toLower();
        item.nick_stripped = nick.toLower().remove(stripper);
        item.comment_lc = comment.toLower();
        item.tag_lc = tag.toLower();
        item.speed_lc = speed.toLower();
        item.email_lc = email.toLower();

        QModelIndex topLeft = createIndex(item.index, COLUMN_NICK);
        QModelIndex bottomRight = createIndex(item.index, COLUMN_SUPPORTS);

        emit dataChanged(topLeft, bottomRight);

        if (sortColumn != COLUMN_NICK)
            sort(sortColumn, sortOrder);

        return;
    }

    UserListItem new_item;

    new_item.nick = nick;
    new_item.pixmap = icon;
    new_item.comment = comment;
    new_item.tag = tag;
    new_item.speed = speed;
    new_item.email = email;
    new_item.shared = share;
    new_item.ip = ip;
    new_item.lockpk = lockpk;
    new_item.supports = supports;

    new_item.nick_lc = nick.toLower();
    new_item.nick_stripped = nick.toLower().remove(stripper);
    new_item.comment_lc = comment.toLower();
    new_item.tag_lc = tag.toLower();
    new_item.speed_lc = speed.toLower();
    new_item.email_lc = email.toLower();

    UserListItem &item = itemHash[ nick ] = new_item;

    if (sortColumn == -1) // if sorting disabled
    {
        const int i = item.index = itemList.size();
        beginInsertRows(QModelIndex(), i, i);
        itemList.append(&item);//item);
        endInsertRows();

        return;
    }

#ifdef _DEBUG_MODEL_
    qDebug() << "Inserting new user item";
#endif

    QList<UserListItem*>::iterator it = itemList.end();
    if (sortOrder == Qt::AscendingOrder)
        it = Compare<Qt::AscendingOrder>().insertSorted(sortColumn, itemList, &item);
    else if (sortOrder == Qt::DescendingOrder)
        it = Compare<Qt::DescendingOrder>().insertSorted(sortColumn, itemList, &item);

    const int pos = it - itemList.begin();
    beginInsertRows(QModelIndex(), pos, pos);
    itemList.insert(it, &item);

    // fix remaining indexes
    for (int i = pos; i < itemList.size(); i++)
        itemList.at(i)->index = i;

    endInsertRows();
}

/** */
void UserListModel::updateIcon(const QString & nick, const QPixmap icon) {
    itemHashType::iterator iter = itemHash.find(nick);
    if (iter == itemHash.end()) {
        return;
    }

    UserListItem & item = iter.value();
    item.pixmap = icon;

    QModelIndex position = createIndex(item.index, COLUMN_NICK);

    emit dataChanged(position, position);
}

void UserListModel::emitUpdaedItem(const UserListItem& item, int col) {
    QModelIndex position = createIndex(item.index, col);

    emit dataChanged(position, position);

    if (sortColumn == col) {
        sort(sortColumn, sortOrder);
    }
}

/** */
void UserListModel::updateComment(const QString & nick, const QString comment) {
    itemHashType::iterator iter = itemHash.find(nick);
    if (iter == itemHash.end()) {
        return;
    }

    UserListItem & item = iter.value();
    item.comment = comment;
    item.comment_lc = comment.toLower();

    emitUpdaedItem(item, COLUMN_COMMENT);
}

/** */
void UserListModel::updateTag(const QString & nick, const QString tag) {
    itemHashType::iterator iter = itemHash.find(nick);
    if (iter == itemHash.end()) {
        return;
    }

    UserListItem & item = iter.value();
    item.tag = tag;
    item.tag_lc = tag.toLower();

    emitUpdaedItem(item, COLUMN_TAG);
}

/** */
void UserListModel::updateSpeed(const QString & nick, const QString speed) {
    itemHashType::iterator iter = itemHash.find(nick);
    if (iter == itemHash.end()) {
        return;
    }

    UserListItem & item = iter.value();
    item.speed = speed;
    item.speed_lc = speed.toLower();

    emitUpdaedItem(item, COLUMN_SPEED);
}

/** */
void UserListModel::updateEmail(const QString & nick, const QString email) {
    itemHashType::iterator iter = itemHash.find(nick);
    if (iter == itemHash.end()) {
        return;
    }

    UserListItem & item = iter.value();
    item.email = email;
    item.email_lc = email.toLower();

    emitUpdaedItem(item, COLUMN_EMAIL);
}

/** */
void UserListModel::updateShare(const QString & nick, const ulonglong share) {
    itemHashType::iterator iter = itemHash.find(nick);
    if (iter == itemHash.end()) {
        return;
    }

    UserListItem & item = iter.value();
    item.shared = share;

    emitUpdaedItem(item, COLUMN_SHARE);
}

/** */
void UserListModel::updateIP(const QString & nick, const QString ip) {
    itemHashType::iterator iter = itemHash.find(nick);
    if (iter == itemHash.end()) {
        return;
    }

    UserListItem & item = iter.value();
    item.ip = ip;

    emitUpdaedItem(item, COLUMN_IP);
}

/** */
void UserListModel::updateLockPK(const QString & nick, const QString lockpk) {
    itemHashType::iterator iter = itemHash.find(nick);
    if (iter == itemHash.end()) {
        return;
    }

    UserListItem & item = iter.value();
    item.lockpk = lockpk;

    emitUpdaedItem(item, COLUMN_LOCKPK);
}

/** */
void UserListModel::updateSupports(const QString & nick, const QString supports) {
    itemHashType::iterator iter = itemHash.find(nick);
    if (iter == itemHash.end()) {
        return;
    }

    UserListItem & item = iter.value();
    item.supports = supports;

    emitUpdaedItem(item, COLUMN_SUPPORTS);
}

/** */
void UserListModel::setSortTop(const QString & nick, const bool top) {
    itemHashType::iterator iter = itemHash.find(nick);
    if (iter == itemHash.end()) {
        return;
    }

    UserListItem & item = iter.value();
    if (item.sorttop == top) {
        return;
    }

    item.sorttop = top;

    sort(sortColumn, sortOrder);
}

/** */
void UserListModel::setHighlight(const QString & nick, const bool highlight) {
    itemHashType::iterator iter = itemHash.find(nick);
    if (iter == itemHash.end()) {
        return;
    }

    UserListItem & item = iter.value();
    item.highlight = highlight;

    QModelIndex position = createIndex(item.index, COLUMN_NICK);

    emit dataChanged(position, position);
}

/** */
QModelIndex UserListModel::indexForNick(const QString & nick) const {
    itemHashType::const_iterator iter = itemHash.find(nick);
    if (iter == itemHash.end()) {
        return QModelIndex();
    }

    return createIndex(iter.value().index, COLUMN_NICK);
}

/** */
QStringList UserListModel::matchNicksContaining(const QString & part, bool stripTags) const {
    QStringList matches;

    if (part.isEmpty()) {
        return matches;
    }

    if (stripTags) {
        for (QList<UserListItem*>::const_iterator it = itemList.constBegin(); it != itemList.constEnd(); ++it) {
            if ((*it)->nick_stripped.contains(part)) {
                matches << (*it)->nick;
            }
        }
    } else {
        for (QList<UserListItem*>::const_iterator it = itemList.constBegin(); it != itemList.constEnd(); ++it) {
            if ((*it)->nick_lc.contains(part)) {
                matches << (*it)->nick;
            }
        }
    }

    return matches;
}

/** */
QStringList UserListModel::matchNicksStartingWith(const QString & part, bool stripTags) const {
    QStringList matches;

    if (part.isEmpty()) {
        return matches;
    }

    if (stripTags) {
        for (QList<UserListItem*>::const_iterator it = itemList.constBegin(); it != itemList.constEnd(); ++it) {
            if ((*it)->nick_stripped.startsWith(part)) {
                matches << (*it)->nick;
            }
        }
    } else {
        for (QList<UserListItem*>::const_iterator it = itemList.constBegin(); it != itemList.constEnd(); ++it) {
            if ((*it)->nick_lc.startsWith(part)) {
                matches << (*it)->nick;
            }
        }
    }

    return matches;
}
