/*
 *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
 *
 *  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 <https://www.gnu.org/licenses/>.
 */

#include "conference_protocol.h"

#include "logger.h"
#include "string_utils.h"

namespace jami {

namespace ProtocolKeys {

constexpr static const char* PROTOVERSION = "version";
constexpr static const char* LAYOUT = "layout";
// V0
constexpr static const char* HANDRAISED = "handRaised";
constexpr static const char* HANDSTATE = "handState";
constexpr static const char* ACTIVEPART = "activeParticipant";
constexpr static const char* MUTEPART = "muteParticipant";
constexpr static const char* MUTESTATE = "muteState";
constexpr static const char* HANGUPPART = "hangupParticipant";
// V1
constexpr static const char* DEVICES = "devices";
constexpr static const char* MEDIAS = "medias";
constexpr static const char* RAISEHAND = "raiseHand";
constexpr static const char* HANGUP = "hangup";
constexpr static const char* ACTIVE = "active";
constexpr static const char* MUTEAUDIO = "muteAudio";
// Future
constexpr static const char* MUTEVIDEO = "muteVideo";
constexpr static const char* VOICEACTIVITY = "voiceActivity";

} // namespace ProtocolKeys

void
ConfProtocolParser::parse()
{
    if (data_.isMember(ProtocolKeys::PROTOVERSION)) {
        uint32_t version = data_[ProtocolKeys::PROTOVERSION].asUInt();
        if (version_)
            version_(version);
        if (version == 1) {
            parseV1();
        } else {
            JAMI_WARNING("Unsupported protocol version {}", version);
        }
    } else {
        parseV0();
    }
}

void
ConfProtocolParser::parseV0()
{
    if (!checkAuthorization_ || !raiseHandUri_ || !setLayout_ || !setActiveParticipant_ || !muteParticipant_
        || !kickParticipant_) {
        JAMI_ERROR("Missing methods for ConfProtocolParser");
        return;
    }
    // Check if all lambdas set
    auto isPeerModerator = checkAuthorization_(peerId_);
    if (data_.isMember(ProtocolKeys::HANDRAISED)) {
        auto state = data_[ProtocolKeys::HANDSTATE].asString() == TRUE_STR;
        auto uri = data_[ProtocolKeys::HANDRAISED].asString();
        if (peerId_ == uri) {
            // In this case, the user want to change their state
            raiseHandUri_(uri, state);
        } else if (!state && isPeerModerator) {
            // In this case a moderator can lower the hand
            raiseHandUri_(uri, state);
        }
    }
    if (!isPeerModerator) {
        JAMI_WARNING("Received conference order from a non-moderator ({})", peerId_);
        return;
    }
    if (data_.isMember(ProtocolKeys::LAYOUT)) {
        setLayout_(data_[ProtocolKeys::LAYOUT].asInt());
    }
    if (data_.isMember(ProtocolKeys::ACTIVEPART)) {
        setActiveParticipant_(data_[ProtocolKeys::ACTIVEPART].asString());
    }
    if (data_.isMember(ProtocolKeys::MUTEPART) && data_.isMember(ProtocolKeys::MUTESTATE)) {
        muteParticipant_(data_[ProtocolKeys::MUTEPART].asString(),
                         data_[ProtocolKeys::MUTESTATE].asString() == TRUE_STR);
    }
    if (data_.isMember(ProtocolKeys::HANGUPPART)) {
        kickParticipant_(data_[ProtocolKeys::HANGUPPART].asString());
    }
}

void
ConfProtocolParser::parseV1()
{
    if (!checkAuthorization_ || !setLayout_ || !raiseHand_ || !hangupParticipant_ || !muteStreamAudio_
        || !setActiveStream_) {
        JAMI_ERROR("Missing methods for ConfProtocolParser");
        return;
    }

    auto isPeerModerator = checkAuthorization_(peerId_);
    for (Json::Value::const_iterator itr = data_.begin(); itr != data_.end(); itr++) {
        auto key = itr.key();
        if (key == ProtocolKeys::LAYOUT) {
            // Note: can be removed soon
            if (isPeerModerator)
                setLayout_(itr->asInt());
        } else if (key == ProtocolKeys::PROTOVERSION) {
            continue;
        } else {
            auto accValue = *itr;
            if (accValue.isMember(ProtocolKeys::DEVICES)) {
                auto accountUri = key.asString();
                for (Json::Value::const_iterator itrd = accValue[ProtocolKeys::DEVICES].begin();
                     itrd != accValue[ProtocolKeys::DEVICES].end();
                     itrd++) {
                    auto deviceId = itrd.key().asString();
                    const auto& deviceValue = *itrd;
                    if (deviceValue.isMember(ProtocolKeys::RAISEHAND)) {
                        auto newState = deviceValue[ProtocolKeys::RAISEHAND].asBool();
                        if (peerId_ == accountUri || (!newState && isPeerModerator))
                            raiseHand_(deviceId, newState);
                    }
                    if (isPeerModerator && deviceValue.isMember(ProtocolKeys::HANGUP)) {
                        hangupParticipant_(accountUri, deviceId);
                    }
                    if (deviceValue.isMember(ProtocolKeys::MEDIAS)) {
                        for (Json::Value::const_iterator itrm = accValue[ProtocolKeys::MEDIAS].begin();
                             itrm != accValue[ProtocolKeys::MEDIAS].end();
                             itrm++) {
                            auto streamId = itrm.key().asString();
                            const auto& mediaVal = *itrm;
                            if (mediaVal.isMember(ProtocolKeys::VOICEACTIVITY)) {
                                voiceActivity_(streamId, mediaVal[ProtocolKeys::VOICEACTIVITY].asBool());
                            }
                            if (isPeerModerator) {
                                if (mediaVal.isMember(ProtocolKeys::MUTEVIDEO) && !muteStreamVideo_) {
                                    // Note: For now, it's not implemented so not set
                                    muteStreamVideo_(accountUri,
                                                     deviceId,
                                                     streamId,
                                                     mediaVal[ProtocolKeys::MUTEVIDEO].asBool());
                                }
                                if (mediaVal.isMember(ProtocolKeys::MUTEAUDIO)) {
                                    muteStreamAudio_(accountUri,
                                                     deviceId,
                                                     streamId,
                                                     mediaVal[ProtocolKeys::MUTEAUDIO].asBool());
                                }
                                if (mediaVal.isMember(ProtocolKeys::ACTIVE)) {
                                    setActiveStream_(streamId, mediaVal[ProtocolKeys::ACTIVE].asBool());
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

} // namespace jami
