/*
 * Squawk messenger.
 * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
 *
 * 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 3 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/>.
 */

#include "conference.h"

#include <QDebug>

Core::Conference::Conference(const QString& p_jid, const QString& p_account, bool p_autoJoin, const QString& p_name, const QString& p_nick, QXmppMucRoom* p_room):
    RosterItem(p_jid, p_account),
    nick(p_nick),
    room(p_room),
    joined(false),
    autoJoin(p_autoJoin),
    exParticipants()
{
    muc = true;
    name = p_name;
    
    connect(room, &QXmppMucRoom::joined, this, &Conference::onRoomJoined);
    connect(room, &QXmppMucRoom::left, this, &Conference::onRoomLeft);
    connect(room, &QXmppMucRoom::nameChanged, this, &Conference::onRoomNameChanged);
    connect(room, &QXmppMucRoom::subjectChanged, this, &Conference::onRoomSubjectChanged);
    connect(room, &QXmppMucRoom::participantAdded, this, &Conference::onRoomParticipantAdded);
    connect(room, &QXmppMucRoom::participantChanged, this, &Conference::onRoomParticipantChanged);
    connect(room, &QXmppMucRoom::participantRemoved, this, &Conference::onRoomParticipantRemoved);
    connect(room, &QXmppMucRoom::nickNameChanged, this, &Conference::onRoomNickNameChanged);
    connect(room, &QXmppMucRoom::error, this, &Conference::onRoomError);
    
    room->setNickName(nick);
    if (autoJoin)
        room->join();
    
    archive->readAllResourcesAvatars(exParticipants);
}

Core::Conference::~Conference(){
    if (joined)
        room->leave();

    room->deleteLater();
}

QString Core::Conference::getNick() const {
    return nick;
}

bool Core::Conference::getAutoJoin() const {
    return autoJoin;
}

bool Core::Conference::getJoined() const {
    return joined;
}

void Core::Conference::setJoined(bool p_joined) {
    if (joined != p_joined) {
        if (p_joined)
            room->join();
        else
            room->leave();
    }
}

void Core::Conference::setAutoJoin(bool p_autoJoin) {
    if (autoJoin != p_autoJoin) {
        autoJoin = p_autoJoin;
        emit autoJoinChanged(autoJoin);
    }
}

void Core::Conference::setNick(const QString& p_nick) {
    if (nick != p_nick) {
        if (joined) {
            room->setNickName(p_nick);
        } else {
            nick = p_nick;
            emit nickChanged(nick);
        }
    }
}

void Core::Conference::onRoomJoined() {
    joined = true;
    emit joinedChanged(joined);
}

void Core::Conference::onRoomLeft() {
    joined = false;
    emit joinedChanged(joined);
}

void Core::Conference::onRoomNameChanged(const QString& p_name) {
    setName(p_name);
}

void Core::Conference::onRoomNickNameChanged(const QString& p_nick) {
    if (p_nick != nick) {
        nick = p_nick;
        emit nickChanged(nick);
    }
}

void Core::Conference::onRoomError(const QXmppStanza::Error& err) {
    qDebug() << "MUC" << jid << "error:" << err.text();
}

void Core::Conference::onRoomParticipantAdded(const QString& p_name) {
    QStringList comps = p_name.split("/");
    QString resource = comps.back();
    QXmppPresence pres = room->participantPresence(p_name);
    QXmppMucItem mi = pres.mucItem();
    if (resource == jid)
        resource = "";
    
    std::map<QString, Archive::AvatarInfo>::const_iterator itr = exParticipants.find(resource);
    bool hasAvatar = itr != exParticipants.end();
    
    if (resource.size() > 0) {
        QDateTime lastInteraction = pres.lastUserInteraction();
        if (!lastInteraction.isValid()) {
            lastInteraction = QDateTime::currentDateTimeUtc();
        }
        
        QMap<QString, QVariant> cData = {
            {"lastActivity", lastInteraction},
            {"availability", pres.availableStatusType()},
            {"status", pres.statusText()},
            {"affiliation", mi.affiliation()},
            {"role", mi.role()},
            {"client", QVariant::fromValue(
                Shared::ClientId(
                    pres.capabilityNode(),
                    pres.capabilityVer().toBase64(),
                    pres.capabilityHash())
                )
            }
        };
        careAboutAvatar(hasAvatar, itr->second, cData, resource, p_name);
        
        emit addParticipant(resource, cData);
    }
    
    switch (pres.vCardUpdateType()) {
        case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
            break;
        case QXmppPresence::VCardUpdateNotReady:        //let's say the photo didn't change here
            break;
        case QXmppPresence::VCardUpdateNoPhoto: {       //there is no photo, need to drop if any
            if (!hasAvatar || !itr->second.autogenerated) {
                setAutoGeneratedAvatar(resource);
            }
        }         
        break;
        case QXmppPresence::VCardUpdateValidPhoto:{     //there is a photo, need to load
            if (hasAvatar) {
                if (itr->second.autogenerated || itr->second.hash != pres.photoHash())
                    emit requestVCard(p_name);

            } else {
                emit requestVCard(p_name);
            }
            break;
        }      
    }
}

void Core::Conference::onRoomParticipantChanged(const QString& p_name) {
    QStringList comps = p_name.split("/");
    QString resource = comps.back();
    QXmppPresence pres = room->participantPresence(p_name);
    QXmppMucItem mi = pres.mucItem();
    handlePresence(pres);
    if (resource != jid) {
        QDateTime lastInteraction = pres.lastUserInteraction();
        if (!lastInteraction.isValid())
            lastInteraction = QDateTime::currentDateTimeUtc();
        
        emit changeParticipant(resource, {
            {"lastActivity", lastInteraction},
            {"availability", pres.availableStatusType()},
            {"status", pres.statusText()},
            {"affiliation", mi.affiliation()},
            {"role", mi.role()},
            {"client", QVariant::fromValue(
                Shared::ClientId(
                    pres.capabilityNode(),
                    pres.capabilityVer().toBase64(),
                    pres.capabilityHash())
                )
            }
        });
    }
}

void Core::Conference::onRoomParticipantRemoved(const QString& p_name) {
    QStringList comps = p_name.split("/");
    QString resource = comps.back();
    if (resource == jid) {
        qDebug() << "Room" << jid << "is reporting of removing his own presence from the list of participants. Not sure what to do with that yet, skipping";
    } else {
        emit removeParticipant(resource);
    }
}

QString Core::Conference::getSubject() const {
    if (joined)
        return room->subject();
    else
        return "";
}

void Core::Conference::onRoomSubjectChanged(const QString& p_name) {
    emit subjectChanged(p_name);
}

void Core::Conference::handlePresence(const QXmppPresence& pres) {
    QString id = pres.from();
    QStringList comps = id.split("/");
    QString jid = comps.front();
    QString resource("");
    if (comps.size() > 1)
        resource = comps.back();
    
    switch (pres.vCardUpdateType()) {
        case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
            break;
        case QXmppPresence::VCardUpdateNotReady:        //let's say the photo didn't change here
            break;
        case QXmppPresence::VCardUpdateNoPhoto: {       //there is no photo, need to drop if any
            Archive::AvatarInfo info;
            bool hasAvatar = readAvatarInfo(info, resource);
            if (!hasAvatar || !info.autogenerated) {
                setAutoGeneratedAvatar(resource);
            }
        }         
            break;
        case QXmppPresence::VCardUpdateValidPhoto:{     //there is a photo, need to load
            Archive::AvatarInfo info;
            bool hasAvatar = readAvatarInfo(info, resource);
            if (hasAvatar) {
                if (info.autogenerated || info.hash != pres.photoHash())
                    emit requestVCard(id);
            } else {
                emit requestVCard(id);
            }
            break;
        }      
    }
}

bool Core::Conference::setAutoGeneratedAvatar(const QString& resource) {
    Archive::AvatarInfo newInfo;
    bool result = RosterItem::setAutoGeneratedAvatar(newInfo, resource);
    if (result && resource.size() != 0) {
        std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
        if (itr == exParticipants.end())
            exParticipants.insert(std::make_pair(resource, newInfo));
        else
            itr->second = newInfo;
        emit changeParticipant(resource, {
            {"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
            {"avatarPath", avatarPath(resource) + "." + newInfo.type}
        });
    }
    
    return result;
}

bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource) {
    bool result = RosterItem::setAvatar(data, info, resource);
    if (result && resource.size() != 0) {
        if (data.size() > 0) {
            std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
            if (itr == exParticipants.end())
                exParticipants.insert(std::make_pair(resource, info));
            else
                itr->second = info;
            
            emit changeParticipant(resource, {
                {"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
                {"avatarPath", avatarPath(resource) + "." + info.type}
            });
        } else {
            std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
            if (itr != exParticipants.end())
                exParticipants.erase(itr);
            
            emit changeParticipant(resource, {
                {"avatarState", static_cast<uint>(Shared::Avatar::empty)},
                {"avatarPath", ""}
            });
        }
        
    }
    
    return result;
}

void Core::Conference::handleResponseVCard(const QXmppVCardIq& card, const QString &resource, Shared::VCard& out) {
    RosterItem::handleResponseVCard(card, resource, out);
    if (resource.size() > 0) {
        emit changeParticipant(resource, {
            {"avatarState", static_cast<uint>(out.getAvatarType())},
            {"avatarPath", out.getAvatarPath()}
        });
    }
}

QMap<QString, QVariant> Core::Conference::getAllAvatars() const {
    QMap<QString, QVariant> result;
    for (const std::pair<const QString, Archive::AvatarInfo>& pair : exParticipants)
        result.insert(pair.first, avatarPath(pair.first) + "." + pair.second.type);
    return result;
}

QMap<QString, QVariant> Core::Conference::getInfo() const {
    QMap<QString, QVariant> data = RosterItem::getInfo();

    data.insert("autoJoin", getAutoJoin());
    data.insert("joined", getJoined());
    data.insert("nick", getNick());
    data.insert("avatars", getAllAvatars());

    return data;
}