/*
 * contactlistitem.cpp - base class for contact list items
 * Copyright (C) 2008-2010  Yandex LLC (Michail Pishchagin)
 *
 * 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 <https://www.gnu.org/licenses/>.
 *
 */

#include "contactlistitem.h"

#include "avatars.h"
#include "contactlistaccountmenu.h"
#include "contactlistgroupmenu.h"
#include "contactlistmodel.h"
#include "psiaccount.h"
#include "psicontact.h"
#include "userlist.h"

#include <QCoreApplication>
#include <QTextDocument>

ContactListItem::ContactListItem(ContactListModel *model, Type type, SpecialGroupType specialGropType) :
    AbstractTreeItem(), _model(model), _type(type), _specialGroupType(specialGropType), _editing(false),
    _selfValid(true), _contact(nullptr), _account(nullptr), _expanded(true), _internalName(), _displayName(),
    _totalContacts(0), _onlineContacts(0), _shouldBeVisible(type != Type::GroupType), _hidden(false)
{
    switch (_specialGroupType) {
    case SpecialGroupType::GeneralSpecialGroupType:
        _internalName = QString::fromUtf8("☣special_group_general");
        _displayName  = PsiContact::generalGroupName();
        break;

    case SpecialGroupType::NotInListSpecialGroupType:
        _internalName = QString::fromUtf8("☣special_group_notinlist");
        _displayName  = PsiContact::notInListGroupName();
        break;

    case SpecialGroupType::TransportsSpecialGroupType:
        _internalName = QString::fromUtf8("☣special_group_transports");
        _displayName  = QCoreApplication::translate("ContactListItem", "Agents/Transports");
        break;

    case SpecialGroupType::MucPrivateChatsSpecialGroupType:
        _internalName = QString::fromUtf8("☣special_group_mucprivatechats");
        _displayName  = QCoreApplication::translate("ContactListItem", "Private messages");
        break;

    case SpecialGroupType::ConferenceSpecialGroupType:
        _internalName = QString::fromUtf8("☣special_group_conferences");
        _displayName  = QCoreApplication::translate("ContactListItem", "Conferences");
        break;

    default:
        break;
    }
}

ContactListItem::~ContactListItem()
{
    _selfValid
        = false; // just for a check for already freed but still mapped memory. though does not give any guarantee
}

ContactListModel *ContactListItem::model() const { return _model; }

ContactListItem::Type ContactListItem::type() const { return _type; }

ContactListItem::SpecialGroupType ContactListItem::specialGroupType() const { return _specialGroupType; }

bool ContactListItem::isRoot() const { return _type == Type::RootType; }

bool ContactListItem::isAccount() const { return _type == Type::AccountType; }

bool ContactListItem::isGroup() const { return _type == Type::GroupType; }

bool ContactListItem::isContact() const { return _type == Type::ContactType; }

bool ContactListItem::isEditable() const
{
    if (_type == Type::AccountType) {
        return true;
    } else if (_type == Type::ContactType) {
        return _contact->account() && _contact->inList();
    } else if (_type == Type::GroupType && _specialGroupType != SpecialGroupType::NoneSpecialGroupType) {
        return false;
    } else {
        AbstractTreeItemList children = AbstractTreeItem::children();
        for (const auto &child : children) {
            ContactListItem *item = static_cast<ContactListItem *>(child);
            if (item->isEditable()) {
                return true;
            }
        }
        return false;
    }
}

bool ContactListItem::isDragEnabled() const { return _type == Type::ContactType && !_contact->isSelf(); }

bool ContactListItem::isRemovable() const
{
    bool res = false;
    if (_type == Type::GroupType) {
        switch (_specialGroupType) {
        case SpecialGroupType::GeneralSpecialGroupType:
        case SpecialGroupType::NotInListSpecialGroupType:
        case SpecialGroupType::MucPrivateChatsSpecialGroupType:
        case SpecialGroupType::NoneSpecialGroupType:
            res = true;
            break;

        default:
            break;
        }
    } else if (_type == Type::ContactType) {
        Q_ASSERT(_contact);
        res = _contact->isRemovable();
    }
    return res;
}

bool ContactListItem::isExpandable() const { return _type != Type::ContactType; }

bool ContactListItem::expanded() const { return _expanded; }

void ContactListItem::setExpanded(bool expanded) { _expanded = expanded; }

ContactListItemMenu *ContactListItem::contextMenu()
{
    ContactListItemMenu *menu = nullptr;
    switch (_type) {
    case Type::ContactType:
        menu = _contact->contextMenu(_model);
        break;
    case Type::AccountType:
        menu = new ContactListAccountMenu(_account, _model);
        break;
    case Type::GroupType:
        menu = new ContactListGroupMenu(this, _model);
        break;
    default:
        break;
    }

    return menu;
}

bool ContactListItem::isFixedSize() const { return true; }

bool ContactListItem::lessThan(const ContactListItem *other) const
{
    if (_type == Type::GroupType && other->_type == Type::GroupType) {
        if (_specialGroupType != other->_specialGroupType) {
            return _specialGroupType < other->_specialGroupType;
        } else {
            return QString::localeAwareCompare(_displayName.toLower(), other->_displayName.toLower()) < 0;
        }
    } else if (_type == Type::ContactType && other->_type == Type::ContactType) {
        int rank = rankStatus(_contact->status().type()) - rankStatus(other->_contact->status().type());
        if (rank == 0)
            rank = QString::localeAwareCompare(_contact->name().toLower(), other->_contact->name().toLower());
        return rank < 0;
    } else if (_type == Type::AccountType && other->_type == Type::AccountType) {
        return QString::localeAwareCompare(_account->name().toLower(), other->_account->name().toLower()) < 0;
    } else if (_type == Type::ContactType && other->_type == Type::GroupType) {
        return _contact->isSelf();
    } else if (_type == Type::GroupType && other->_type == Type::ContactType) {
        return !other->_contact->isSelf();
    }

    return false;
}

QString ContactListItem::name() const
{
    QString name;
    switch (_type) {
    case Type::AccountType:
        name = _account->name();
        break;
    case Type::ContactType:
        name = _contact->name();
        break;
    case Type::GroupType:
        name = _displayName;
        break;
    default:
        break;
    }

    return name;
}

void ContactListItem::setName(const QString &name)
{
    switch (_type) {
    case Type::ContactType:
        Q_ASSERT(_contact);
        if (_contact) {
            _contact->setName(name);
        }
        break;

    case Type::GroupType:
        _displayName = name;
        break;

    default:
        break;
    }
}

QString ContactListItem::internalName() const
{
    QString res;

    switch (_type) {
    case Type::AccountType:
        res = _account->id();
        break;
    case Type::GroupType:
        res = account()->id() + "::" + name();
        break;
    default:
        break;
    }

    return res;
}

bool ContactListItem::editing() const { return _editing; }

void ContactListItem::setEditing(bool editing) { _editing = editing; }

void ContactListItem::setContact(PsiContact *contact) { _contact = contact; }

PsiContact *ContactListItem::contact() const { return _contact; }

void ContactListItem::setAccount(PsiAccount *account) { _account = account; }

PsiAccount *ContactListItem::account() const
{
    const ContactListItem *item = this;
    while (item->_type != Type::AccountType) {
        if (item->parent())
            item = item->parent();
        else
            break;
    }
    return item->_account;
}

bool ContactListItem::shouldBeVisible() const { return _shouldBeVisible; }

void ContactListItem::setHidden(bool hidden, bool notifyModel)
{
    _hidden = hidden;
    _model->updateItem(this, notifyModel);
}

bool ContactListItem::isHidden() const { return _hidden; }

QList<PsiContact *> ContactListItem::contacts()
{
    AbstractTreeItemList children = AbstractTreeItem::children();
    QList<PsiContact *>  res;
    for (const auto &child : children) {
        ContactListItem *item = static_cast<ContactListItem *>(child);
        if (item->isContact()) {
            res << item->_contact;
        } else {
            res += item->contacts();
        }
    }

    return res;
}

ContactListItem *ContactListItem::findAccount(PsiAccount *account)
{
    AbstractTreeItemList children = AbstractTreeItem::children();
    ContactListItem     *res      = nullptr;
    for (const auto &child : children) {
        ContactListItem *item = static_cast<ContactListItem *>(child);
        if (item->isAccount() && item->account() == account) {
            res = item;
            break;
        }
    }

    return res;
}

ContactListItem *ContactListItem::findGroup(const QString &groupName)
{
    QString id;
    QString realGroupName;

    if (_type == Type::RootType) {
        id            = groupName.section("::", 0, 0);
        realGroupName = groupName.section("::", 1, 1);
    }

    AbstractTreeItemList children = AbstractTreeItem::children();
    ContactListItem     *res      = nullptr;

    for (const auto &child : children) {
        ContactListItem *item = static_cast<ContactListItem *>(child);

        if (_type == Type::AccountType) {
            if (item->isGroup() && item->name() == groupName) {
                res = item;
                break;
            }
        } else if (_type == Type::RootType) {
            if (item->isAccount() && item->account()->id() == id) {
                res = item->findGroup(realGroupName);
            }
        }
    }

    return res;
}

ContactListItem *ContactListItem::findGroup(ContactListItem::SpecialGroupType specialGroupType)
{
    AbstractTreeItemList children = AbstractTreeItem::children();
    ContactListItem     *res      = nullptr;
    for (const auto &child : children) {
        ContactListItem *item = static_cast<ContactListItem *>(child);
        if (item->isGroup() && item->specialGroupType() == specialGroupType) {
            res = item;
            break;
        }
    }

    return res;
}

ContactListItem *ContactListItem::findContact(PsiContact *contact)
{
    AbstractTreeItemList children = AbstractTreeItem::children();
    ContactListItem     *res      = nullptr;
    for (const auto &child : children) {
        ContactListItem *item = static_cast<ContactListItem *>(child);
        if (item->contact() == contact) {
            res = item;
            break;
        }
    }

    return res;
}

void ContactListItem::setValue(int role, const QVariant &value)
{
    Q_UNUSED(role);
    Q_UNUSED(value);
}

QVariant ContactListItem::value(int role) const
{
    QVariant res;
    Q_ASSERT(_selfValid);
    // Common roles first
    switch (role) {
    case ContactListModel::TypeRole:
        res = QVariant::fromValue(_type);
        break;
    case ContactListModel::ExpandedRole:
        res = _expanded;
        break;

    default:

        // Other roles. Behaviour relies from type.
        switch (_type) {
        case Type::ContactType:
            Q_ASSERT(_contact);
            switch (role) {
            case Qt::EditRole:
                res = _contact->name();
                break;
            case Qt::DisplayRole:
                res = _contact->name();
                break;
            case Qt::ToolTipRole:
                res = _contact->userListItem().makeTip(true, false);
                break;
            case ContactListModel::JidRole:
                res = _contact->jid().full();
                break;
            case ContactListModel::PictureRole:
                res = _contact->picture();
                break;
            case ContactListModel::StatusTextRole:
                res = _contact->statusText().simplified();
                break;
            case ContactListModel::StatusTypeRole:
                res = _contact->status().type();
                break;
            case ContactListModel::PresenceErrorRole:
                res = _contact->userListItem().presenceError();
                break;
            case ContactListModel::IsAgentRole:
                res = _contact->isAgent();
                break;
            case ContactListModel::AuthorizesToSeeStatusRole:
                res = _contact->authorizesToSeeStatus();
                break;
            case ContactListModel::AskingForAuthRole:
                res = _contact->askingForAuth();
                break;
            case ContactListModel::IsAlertingRole:
                res = _contact->alerting();
                break;
            case ContactListModel::AlertPictureRole:
                res = _contact->alertPicture();
                break;
            case ContactListModel::IsAnimRole:
                res = _contact->isAnimated();
                break;
            case ContactListModel::PhaseRole:
                res = false;
                break;
            case ContactListModel::GeolocationRole:
                res = !_contact->userListItem().geoLocation().isNull();
                break;
            case ContactListModel::TuneRole:
                res = !_contact->userListItem().tune().isEmpty();
                break;
            case ContactListModel::ClientRole:
                res = _contact->userListItem().clients();
                break;
            case ContactListModel::IsMucRole:
                res = _contact->userListItem().isConference();
                break;
            case ContactListModel::MucMessagesRole:
                res = _contact->userListItem().pending();
                break;
            case ContactListModel::BlockRole:
                res = _contact->isBlocked();
                break;
            case ContactListModel::IsSecureRole:
                res = _contact->userListItem().isSecure();
                break;

            case ContactListModel::AvatarRole:
                if (_contact->isPrivate())
                    res = _contact->account()->avatarFactory()->getMucAvatar(_contact->jid());
                else
                    res = _contact->account()->avatarFactory()->getAvatar(_contact->jid());

                break;

            case ContactListModel::MoodRole:
                return QVariant::fromValue(_contact->userListItem().mood());

            case ContactListModel::ActivityRole:
                return QVariant::fromValue(_contact->userListItem().activity());

            default:
                break;
            }

            break;

        case Type::AccountType:
            Q_ASSERT(_account);
            switch (role) {
            case Qt::EditRole:
                res = _account->name();
                break;
            case Qt::DisplayRole:
                res = _account->name();
                break;
            case Qt::ToolTipRole:
                res = _account->selfContact()->userListItem().makeTip(true, false);
                break;
            case ContactListModel::JidRole:
                res = _account->jid().full();
                break;
            case ContactListModel::StatusTextRole:
                res = _account->status().status().simplified();
                break;
            case ContactListModel::StatusTypeRole:
                res = _account->status().type();
                break;
            case ContactListModel::OnlineContactsRole:
                res = _account->onlineContactsCount();
                break;
            case ContactListModel::TotalContactsRole:
                res = _account->contactList().count();
                break;
            case ContactListModel::UsingSSLRole:
                res = _account->usingSSL();
                break;
            case ContactListModel::IsAlertingRole:
                res = _account->alerting();
                break;
            case ContactListModel::AlertPictureRole:
                res = _account->alertPicture();
                break;
            default:
                break;
            }

            break;

        case Type::GroupType:
            switch (role) {
            case Qt::EditRole:
                res = name();
                break;
            case Qt::DisplayRole:
                res = name();
                break;
            case Qt::ToolTipRole: {
                QString text = name();
                text         = text.toHtmlEscaped();
                res          = text + QString(" (%1/%2)").arg(_onlineContacts).arg(_totalContacts);
            } break;

            case ContactListModel::DisplayGroupRole:
                updateContactsCount();
                res = name() + QString(" (%1/%2)").arg(_onlineContacts).arg(_totalContacts);
                break;

            case ContactListModel::OnlineContactsRole:
                updateContactsCount();
                res = _onlineContacts;
                break;

            case ContactListModel::TotalContactsRole:
                updateContactsCount();
                res = _totalContacts;
                break;

            default:
                break;
            }
            break;

        default:
            break;
        }

        break;
    }

    return res;
}

void ContactListItem::updateContactsCount() const
{
    if (_type != Type::GroupType)
        return;

    _totalContacts   = 0;
    _onlineContacts  = 0;
    _shouldBeVisible = false;

    AbstractTreeItemList children = AbstractTreeItem::children();
    for (const auto &child : children) {
        ContactListItem *item = static_cast<ContactListItem *>(child);
        if (item->_type == Type::ContactType) {
            _totalContacts++;
            if (item->_contact->alerting() || item->_contact->isAlwaysVisible()) {

                _shouldBeVisible = true;
            }

            if (item->_contact->isOnline()) {
                _onlineContacts++;
            }
        } else if (item->_type == Type::GroupType) {
            item->updateContactsCount();
            if (item->shouldBeVisible()) {
                _shouldBeVisible = true;
            }
            _totalContacts += item->_totalContacts;
            _onlineContacts += item->_onlineContacts;
        }
    }
}

QList<ContactListItem *> ContactListItem::allChildren() const
{
    AbstractTreeItemList     children = AbstractTreeItem::children();
    QList<ContactListItem *> res;
    for (const auto &child : children) {
        ContactListItem *item = static_cast<ContactListItem *>(child);
        res << item;
        res += item->allChildren();
    }

    return res;
}
