/*
 * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
 *
 * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
 *
 * Use of this source code is governed by MIT-like license that can be found in the
 * LICENSE file in the root of the source tree. All contributing project authors
 * may be found in the AUTHORS file in the root of the source tree.
 */

#include "Util/util.h"
#include "Common/config.h"
#include "WebRtcTransport.h"
#include "WebRtcSignalingMsg.h"
#include "WebRtcSignalingSession.h"

using namespace std;
using namespace toolkit;
using namespace mediakit::Rtc;

namespace mediakit {

// 注册上来的peer列表
static std::atomic<uint32_t> s_room_idx_generate { 1 };
static ServiceController<WebRtcSignalingSession> s_rooms;

void listWebrtcRooms(const std::function<void(const std::string &key, const WebRtcSignalingSession::Ptr &p)> &cb) {
    s_rooms.for_each(cb);
}

Json::Value ToJson(const WebRtcSignalingSession::Ptr &p) {
    return p->makeInfoJson();
}

WebRtcSignalingSession::Ptr getWebrtcRoomKeeper(const string &room_id) {
    return s_rooms.find(room_id);
}

////////////  WebRtcSignalingSession //////////////////////////

WebRtcSignalingSession::WebRtcSignalingSession(const Socket::Ptr &sock) : Session(sock) {
    DebugL;
}

WebRtcSignalingSession::~WebRtcSignalingSession() {
    DebugL << "room_id: " << _room_id;
}

void WebRtcSignalingSession::onRecv(const Buffer::Ptr &buffer) {
    DebugL << "recv msg:\r\n" << buffer->data();

    Json::Value args;
    Json::Reader reader;
    reader.parse(buffer->data(), args);
    Parser parser;
    HttpAllArgs<decltype(args)> allArgs(parser, args);

    using MsgHandler = void (WebRtcSignalingSession::*)(SIGNALING_MSG_ARGS);
    static std::unordered_map<std::pair<std::string /*class*/, std::string /*method*/>, MsgHandler, ClassMethodHash> s_msg_handlers;

    static onceToken token([]() {
        s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REQUEST, METHOD_VALUE_REGISTER), &WebRtcSignalingSession::handleRegisterRequest);
        s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REQUEST, METHOD_VALUE_UNREGISTER), &WebRtcSignalingSession::handleUnregisterRequest);
        s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REQUEST, METHOD_VALUE_CALL), &WebRtcSignalingSession::handleCallRequest);
        s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_ACCEPT, METHOD_VALUE_CALL), &WebRtcSignalingSession::handleCallAccept);
        s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REJECT, METHOD_VALUE_CALL), &WebRtcSignalingSession::handleCallReject);
        s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_INDICATION, METHOD_VALUE_BYE), &WebRtcSignalingSession::handleByeIndication);
        s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_INDICATION, METHOD_VALUE_CANDIDATE), &WebRtcSignalingSession::handleCandidateIndication);
    });

    try {
        CHECK_ARGS(CLASS_KEY, METHOD_KEY, TRANSACTION_ID_KEY);
        auto it = s_msg_handlers.find(std::make_pair(allArgs[CLASS_KEY], allArgs[METHOD_KEY]));
        if (it == s_msg_handlers.end()) {
            WarnL << " not support class: " << allArgs[CLASS_KEY] << ", method: " << allArgs[METHOD_KEY] << ", ignore";
            return;
        }

        (this->*(it->second))(allArgs);
    } catch (std::exception &ex) {
        WarnL << "process msg fail: " << ex.what();
    }
}

void WebRtcSignalingSession::onError(const SockException &err) {
    WarnL << "room_id: " << _room_id;
    notifyByeIndication();
    s_rooms.erase(_room_id);
}

void WebRtcSignalingSession::onManager() {
    // Websocket会话会自行定时发送PING/PONG 消息，并进行超时自己管理，该对象暂时不需要心跳超时处理
}

void WebRtcSignalingSession::handleRegisterRequest(SIGNALING_MSG_ARGS) {
    DebugL;

    std::string room_id;
    Json::Value body;
    body[METHOD_KEY] = METHOD_VALUE_REGISTER;

    // 如果客户端没有提供 room_id，服务端自动分配一个
    if (allArgs[ROOM_ID_KEY].empty()) {
        auto idx = s_room_idx_generate.fetch_add(1);
        room_id = std::to_string(idx) + "_" + makeRandStr(16);
        DebugL << "auto generated room_id: " << room_id;
    } else {
        room_id = allArgs[ROOM_ID_KEY];
        if (s_rooms.find(room_id)) {
            // 已经注册了
            body[ROOM_ID_KEY] = room_id;
            return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "room id conflict");
        }
    }

    body[ROOM_ID_KEY] = room_id;

    _room_id = room_id;
    s_rooms.emplace(_room_id, shared_from_this());
    sendRegisterAccept(body, allArgs[TRANSACTION_ID_KEY]);
}

void WebRtcSignalingSession::handleUnregisterRequest(SIGNALING_MSG_ARGS) {
    DebugL;
    CHECK_ARGS(ROOM_ID_KEY);

    Json::Value body;
    body[METHOD_KEY] = METHOD_VALUE_UNREGISTER;
    body[ROOM_ID_KEY] = allArgs[ROOM_ID_KEY];

    if (_room_id.empty()) {
        return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "unregistered");
    }

    if (allArgs[ROOM_ID_KEY] != getRoomId()) {
        return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], StrPrinter << "room_id: \"" << allArgs[ROOM_ID_KEY] << "\" not match room_id:" << getRoomId());
    }

    sendAcceptResponse(body, allArgs[TRANSACTION_ID_KEY]);

    // 同时主动向所有连接的对端会话发送bye
    notifyByeIndication();

    if (s_rooms.find(_room_id)) {
        s_rooms.erase(_room_id);
    }
}

void WebRtcSignalingSession::handleCallRequest(SIGNALING_MSG_ARGS) {
    DebugL;
    CHECK_ARGS(TRANSACTION_ID_KEY, GUEST_ID_KEY, ROOM_ID_KEY, CALL_VHOST_KEY, CALL_APP_KEY, CALL_STREAM_KEY, TYPE_KEY, SDP_KEY);

    Json::Value body;
    body[METHOD_KEY] = METHOD_VALUE_CALL;
    body[ROOM_ID_KEY] = allArgs[ROOM_ID_KEY];
    body[GUEST_ID_KEY] = allArgs[GUEST_ID_KEY];
    body[CALL_VHOST_KEY] = allArgs[CALL_VHOST_KEY];
    body[CALL_APP_KEY] = allArgs[CALL_APP_KEY];
    body[CALL_STREAM_KEY] = allArgs[CALL_STREAM_KEY];
    body[TYPE_KEY] = allArgs[TYPE_KEY];
    if (_room_id.empty()) {
        return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "should register first");
    }
    auto peer_id = allArgs[ROOM_ID_KEY];
    auto session = getWebrtcRoomKeeper(peer_id);
    if (!session) {
        return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], StrPrinter << "room_id: \"" << peer_id << "\" unregistered");
    }

    _tours.emplace(allArgs[GUEST_ID_KEY], peer_id);
    // forwardOffer
    weak_ptr<WebRtcSignalingSession> sender_ptr = static_pointer_cast<WebRtcSignalingSession>(shared_from_this());
    session->forwardCallRequest(sender_ptr, allArgs);
}

void WebRtcSignalingSession::handleCallAccept(SIGNALING_MSG_ARGS) {
    DebugL;
    CHECK_ARGS(GUEST_ID_KEY, ROOM_ID_KEY, CALL_VHOST_KEY, CALL_APP_KEY, CALL_STREAM_KEY);

    Json::Value body;
    body[ROOM_ID_KEY] = allArgs[ROOM_ID_KEY];

    if (_room_id.empty()) {
        return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "should register first");
    }

    auto it = _guests.find(allArgs[GUEST_ID_KEY]);
    if (it == _guests.end()) {
        WarnL << "guest_id: \"" << allArgs[GUEST_ID_KEY] << "\" not register";
        return;
    }
    auto session = it->second.lock();
    if (!session) {
        WarnL << "guest_id: \"" << allArgs[GUEST_ID_KEY] << "\" leave alreadly";
        return;
    }

    session->forwardCallAccept(allArgs);
}

void WebRtcSignalingSession::handleByeIndication(SIGNALING_MSG_ARGS) {
    DebugL;
    CHECK_ARGS(GUEST_ID_KEY, ROOM_ID_KEY);
    auto guest_id = allArgs[GUEST_ID_KEY];

    Json::Value body;
    body[METHOD_KEY] = METHOD_VALUE_BYE;
    body[ROOM_ID_KEY] = allArgs[ROOM_ID_KEY];
    body[GUEST_ID_KEY] = guest_id;
    if (_room_id.empty()) {
        return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "should register first");
    }
    if (allArgs[ROOM_ID_KEY] == getRoomId()) {
        // 作为被叫方,接收bye
        auto it = _guests.find(guest_id);
        if (it == _guests.end()) {
            WarnL << "guest_id: \"" << guest_id << "\" not register";
            return;
        }
        auto session = it->second.lock();
        if (!session) {
            WarnL << "guest_id: \"" << guest_id << "\" leave alreadly";
            return;
        }
        _guests.erase(guest_id);
        session->forwardBye(allArgs);
    } else {
        // 作为主叫方，接受bye
        auto session = getWebrtcRoomKeeper(allArgs[ROOM_ID_KEY]);
        if (!session) {
            WarnL << "room_id: \"" << allArgs[ROOM_ID_KEY] << "\" not register";
            return;
        }
        _tours.erase(guest_id);
        session->forwardBye(allArgs);
    }
}

void WebRtcSignalingSession::handleCandidateIndication(SIGNALING_MSG_ARGS) {
    DebugL;
    CHECK_ARGS(TRANSACTION_ID_KEY, GUEST_ID_KEY, ROOM_ID_KEY, CANDIDATE_KEY, UFRAG_KEY, PWD_KEY);

    Json::Value body;
    body[METHOD_KEY] = METHOD_VALUE_CANDIDATE;
    body[ROOM_ID_KEY] = allArgs[ROOM_ID_KEY];

    if (_room_id.empty()) {
        sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "should register first");
    } else {
        handleOtherMsg(allArgs);
    }
}

void WebRtcSignalingSession::handleOtherMsg(SIGNALING_MSG_ARGS) {
    DebugL;
    if (allArgs[ROOM_ID_KEY] == getRoomId()) {
        // 作为被叫方,接收bye
        auto guest_id = allArgs[GUEST_ID_KEY];
        auto it = _guests.find(guest_id);
        if (it == _guests.end()) {
            WarnL << "guest_id: \"" << guest_id << "\" not register";
            return;
        }
        auto session = it->second.lock();
        if (!session) {
            WarnL << "guest_id: \"" << guest_id << "\" leave alreadly";
            return;
        }

        session->forwardPacket(allArgs);
    } else {
        // 作为主叫方，接受bye
        auto session = getWebrtcRoomKeeper(allArgs[ROOM_ID_KEY]);
        if (!session) {
            WarnL << "room_id: \"" << allArgs[ROOM_ID_KEY] << "\" not register";
            return;
        }
        session->forwardPacket(allArgs);
    }
}

void WebRtcSignalingSession::notifyByeIndication() {
    DebugL;

    Json::Value allArgs;
    allArgs[CLASS_KEY] = CLASS_VALUE_INDICATION;
    allArgs[METHOD_KEY] = METHOD_VALUE_BYE;
    allArgs[REASON_KEY] = "peer unregister";
    // 作为被叫方
    for (auto it : _guests) {
        auto session = it.second.lock();
        if (session) {
            allArgs[TRANSACTION_ID_KEY] = makeRandStr(32);
            allArgs[GUEST_ID_KEY] = it.first;
            allArgs[ROOM_ID_KEY] = getRoomId();
            session->forwardBye(allArgs);
        }
    }

    // 作为主叫方
    for (auto it : _tours) {
        auto guest_id = it.first;
        auto peer_room_id = it.second;
        auto session = getWebrtcRoomKeeper(peer_room_id);
        if (session) {
            allArgs[TRANSACTION_ID_KEY] = makeRandStr(32);
            allArgs[GUEST_ID_KEY] = guest_id;
            allArgs[ROOM_ID_KEY] = peer_room_id;
            session->forwardBye(allArgs);
        }
    }
}

void WebRtcSignalingSession::forwardCallRequest(WebRtcSignalingSession::WeakPtr sender, SIGNALING_MSG_ARGS) {
    DebugL;
    WeakPtr weak_self = std::static_pointer_cast<WebRtcSignalingSession>(shared_from_this());
    getPoller()->async([weak_self, sender, allArgs]() {
        if (auto strong_self = weak_self.lock()) {
            strong_self->_guests.emplace(allArgs[GUEST_ID_KEY], sender);
            strong_self->sendPacket(allArgs.getArgs());
        }
    });
}

void WebRtcSignalingSession::forwardCallAccept(SIGNALING_MSG_ARGS) {
    DebugL;
    WeakPtr weak_self = std::static_pointer_cast<WebRtcSignalingSession>(shared_from_this());
    getPoller()->async([weak_self, allArgs]() {
        if (auto strong_self = weak_self.lock()) {
            strong_self->sendPacket(allArgs.getArgs());
        }
    });
}

void WebRtcSignalingSession::forwardBye(SIGNALING_MSG_ARGS) {
    DebugL;
    WeakPtr weak_self = std::static_pointer_cast<WebRtcSignalingSession>(shared_from_this());
    getPoller()->async([weak_self, allArgs]() {
        if (auto strong_self = weak_self.lock()) {
            if (allArgs[ROOM_ID_KEY] == strong_self->getRoomId()) {
                // 作为被叫
                strong_self->_guests.erase(allArgs[GUEST_ID_KEY]);
            } else {
                // 作为主叫
                strong_self->_tours.erase(allArgs[GUEST_ID_KEY]);
            }
            strong_self->sendPacket(allArgs.getArgs());
        }
    });
}

void WebRtcSignalingSession::forwardBye(Json::Value allArgs) {
    DebugL;
    WeakPtr weak_self = std::static_pointer_cast<WebRtcSignalingSession>(shared_from_this());
    getPoller()->async([weak_self, allArgs]() {
        if (auto strong_self = weak_self.lock()) {
            if (allArgs[ROOM_ID_KEY] == strong_self->getRoomId()) {
                // 作为被叫
                strong_self->_guests.erase(allArgs[GUEST_ID_KEY].asString());
            } else {
                // 作为主叫
                strong_self->_tours.erase(allArgs[GUEST_ID_KEY].asString());
            }
            strong_self->sendPacket(allArgs);
        }
    });
}

void WebRtcSignalingSession::forwardPacket(SIGNALING_MSG_ARGS) {
    WeakPtr weak_self = std::static_pointer_cast<WebRtcSignalingSession>(shared_from_this());
    getPoller()->async([weak_self, allArgs]() {
        if (auto strong_self = weak_self.lock()) {
            strong_self->sendPacket(allArgs.getArgs());
        }
    });
}

void WebRtcSignalingSession::sendRegisterAccept(Json::Value& body, const std::string& transaction_id) {
    DebugL;
    body[CLASS_KEY] = CLASS_VALUE_ACCEPT;

    Json::Value ice_server;
    GET_CONFIG(uint16_t, icePort, Rtc::kIcePort);
    GET_CONFIG(bool, enable_turn, Rtc::kEnableTurn);
    GET_CONFIG(string, iceUfrag, Rtc::kIceUfrag);
    GET_CONFIG(string, icePwd, Rtc::kIcePwd);
    GET_CONFIG_FUNC(std::vector<std::string>, extern_ips, Rtc::kExternIP, [](string str) {
        std::vector<std::string> ret;
        if (str.length()) {
            ret = split(str, ",");
        }
        translateIPFromEnv(ret);
        return ret;
    });

    // 如果配置了extern_ips, 则选择第一个作为turn服务器的ip
    // 如果没配置获取网卡接口
    std::string extern_ip;
    if (!extern_ips.empty()) {
        extern_ip = extern_ips.front();
    } else {
        extern_ip = SockUtil::get_local_ip();
    }

    // TODO: support multi extern ip
    // TODO: support third stun/turn server

    std::string url;
    // SUPPORT:
    // stun:host:port?transport=udp
    // turn:host:port?transport=udp

    // NOT SUPPORT NOW TODO:
    // turns:host:port?transport=udp
    // turn:host:port?transport=tcp
    // turns:host:port?transport=tcp
    // stuns:host:port?transport=udp
    // stuns:host:port?transport=udp
    // stun:host:port?transport=tcp
    if (enable_turn) {
        url = "turn:" + extern_ip + ":" + std::to_string(icePort) + "?transport=udp";
    } else {
        url = "stun:" + extern_ip + ":" + std::to_string(icePort) + "?transport=udp";
    }

    ice_server[URL_KEY] = url;
    ice_server[UFRAG_KEY] = iceUfrag;
    ice_server[PWD_KEY] = icePwd;

    Json::Value ice_servers;
    ice_servers.append(ice_server);

    body[ICE_SERVERS_KEY] = ice_servers;

    sendAcceptResponse(body, transaction_id);
}

void WebRtcSignalingSession::sendAcceptResponse(Json::Value &body, const std::string &transaction_id) {
    TraceL;
    body[CLASS_KEY] = CLASS_VALUE_ACCEPT;
    return sendResponse(body, transaction_id);
}

void WebRtcSignalingSession::sendRejectResponse(Json::Value &body, const std::string &transaction_id, const std::string &reason) {
    DebugL;
    body[CLASS_KEY] = CLASS_VALUE_REJECT;
    body[REASON_KEY] = reason;
    return sendResponse(body, transaction_id);
}

void WebRtcSignalingSession::sendResponse(Json::Value &body, const std::string &transaction_id) {
    DebugL;
    body[TRANSACTION_ID_KEY] = transaction_id;
    return sendPacket(body);
}

void WebRtcSignalingSession::sendPacket(const Json::Value &body) {
    auto msg = body.toStyledString();
    TraceL << "send msg: " << msg;
    SockSender::send(msg);
}

Json::Value WebRtcSignalingSession::makeInfoJson() {
    Json::Value item;
    item["room_id"] = getRoomId();

    Json::Value tours_obj(Json::arrayValue);
    auto tours = _tours;
    for (auto &tour : tours) {
        Json::Value obj;
        obj["guest_id"] = tour.first;
        obj["room_id"] = tour.second;
        tours_obj.append(std::move(obj));
    }
    item["tours"] = std::move(tours_obj);

    Json::Value guests_obj(Json::arrayValue);
    auto guests = _guests;
    for (auto &guest : guests) {
        Json::Value obj;
        obj["guest_id"] = guest.first;
        guests_obj.append(std::move(obj));
    }
    item["guests"] = std::move(guests_obj);
    return item;
}

} // namespace mediakit
