/*
 * 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()
{
    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()}
        };
        
        if (hasAvatar) {
            if (itr->second.autogenerated) {
                cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid));
            } else {
                cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
            }
            cData.insert("avatarPath", avatarPath(resource) + "." + itr->second.type);
        } else {
            cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
            cData.insert("avatarPath", "");
            emit requestVCard(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()}
        });
    }
}

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