//
// Created on 12/16/17.
//

#include <cassert>
#include <vector>
#include "plog/Log.h"
#include "IGroup.h"

using namespace std::placeholders;

IGroup::IGroup(const std::string &groupId, IConn *btm)
        : IConn(groupId) {
    mBtm = btm;
}

int IGroup::Init() {
    int nret = IConn::Init();
    if (nret) {
        LOGE << "IConn::Init failed: " << nret;
        return nret;
    }

    if (mBtm) {
        /*
        nret = mBtm->Init();
        if (nret) {
            LOGE << "btm conn init failed: " << nret;
            return nret;
        }
        */

        auto out = std::bind(&IConn::Send, mBtm, std::placeholders::_1, std::placeholders::_2);
        SetOutputCb(out);

        auto in = std::bind(&IConn::Input, this, std::placeholders::_1, std::placeholders::_2);
        mBtm->SetOnRecvCb(in);
    }
    return 0;
}

int IGroup::Close() {
    IConn::Close();
    if (mBtm) {
        mBtm->Close();
        delete mBtm;
        mBtm = nullptr;
    }
    if (!mConns.empty()) {
        decltype(mConns) copys(mConns);
        for (auto &e: copys) {
            CloseConn(e.second);
        }
        mConns.clear();
    }
    return 0;
}

bool IGroup::RemoveConn(IConn *conn) {
    auto n = mConns.erase(conn->Key());
    conn->SetOnRecvCb(nullptr);
    conn->SetOutputCb(nullptr);
    return n != 0;
}

IConn *IGroup::ConnOfKey(const std::string &key) {
    auto it = mConns.find(key);
    return (it != mConns.end()) ? it->second : nullptr;
}

void IGroup::AddConn(IConn *conn, const IConn::IConnCb &outCb, const IConn::IConnCb &recvCb) {
    mConns.insert({conn->Key(), conn});
    if (outCb) {
        conn->SetOutputCb(outCb);
    }
    if (recvCb) {
        conn->SetOnRecvCb(recvCb);
    }
}

void IGroup::Flush(uint64_t now) {
    std::vector<std::pair<std::string, IConn *>> fails;

    for (auto &e: mConns) {
        if (!e.second->Alive()) {
            fails.emplace_back(e);
        }
    }

    // remove dead conns first
    for (auto &e: fails) {
        LOGV << "conn " << e.second->ToStr() << " is dead";
        OnConnDead(e.second);
//        if (!OnConnDead(e.second)) {
//            CloseConn(e.second);
//        }
    }

    if (mConns.empty()) {
        LOGD << "group: " << ToStr() << ", all conns are dead";
    }

    // then flush
    for (auto &e: mConns) {
        e.second->Flush(now);
    }
}

void IGroup::OnConnDead(IConn *conn) {
    CloseConn(conn);
}

bool IGroup::Alive() {
    for (auto &e: mConns) {
        if (e.second->Alive()) {
            return true;
        }
    }
    return false;
}

std::map<std::string, IConn *> &IGroup::GetAllConns() {
    return mConns;
}

bool IGroup::CloseConn(IConn *conn) {
    if (conn) {
        LOGD << "closing conn " << conn->ToStr();
        assert(conn->Key() != Key());
        assert(conn == ConnOfKey(conn->Key()));
        RemoveConn(conn);
        conn->Close();
        delete conn;
        return true;
    }
    return false;
}

int IGroup::Size() const {
    return mConns.size();
}
