#include "multiserver.h"

#include <library/cpp/deprecated/atomic/atomic.h>
#include <util/system/mutex.h>

#include <library/cpp/logger/global/global.h>

class THttpMultiServer::TImpl: public THttpServer::ICallBack {
public:
    TImpl(ICallBack* cb, const TOptions& options, size_t servers);
    ~TImpl();

    void Start();
    void Stop();
    void ShutdownAndWait();

    bool IsRunning() const;

protected: // THttpServer::ICallBack
    void OnFailRequest(int failstate) override;
    void OnFailRequestEx(const TFailLogData& d) override;
    void OnException() override;
    void OnMaxConn() override;
    TClientRequest* CreateClient() override;
    void OnListenStart() override;
    void OnListenStop() override;
    void OnWait() override;
    void* CreateThreadSpecificResource() override;
    void DestroyThreadSpecificResource(void* p) override;

private:
    bool IsRunningUnsafe(const TGuard<TMutex>&) const; // fake parameter to ensure it's called under the lock

private:
    ICallBack* const DownstreamCallback;

    TMutex StateLock;
    TAtomic RunningCounter;
    TVector<THolder<THttpServer>> HttpServers;
};

THttpMultiServer::TImpl::TImpl(ICallBack* cb, const TOptions& options, size_t servers)
    : DownstreamCallback(cb)
    , RunningCounter(0)
{
    VERIFY_WITH_LOG(cb != nullptr, "Invalid THttpServer::ICallBack");
    VERIFY_WITH_LOG(servers != 0, "Incorrect count of HTTP servers: %lu", servers);
    VERIFY_WITH_LOG(servers == 1 || options.ReusePort, "SO_REUSEPORT is required to run multiple http servers");

    HttpServers.reserve(servers);
    for (ui32 i = 0; i < servers; i++) {
        HttpServers.push_back(MakeHolder<THttpServer>(this, options));
    }

    VERIFY_WITH_LOG(!IsRunning(), "Unexpected HTTP server state in the constructor");
}

THttpMultiServer::TImpl::~TImpl() {
    Stop();
}

void THttpMultiServer::TImpl::Start()
{
    TGuard<TMutex> g(StateLock);
    if (IsRunningUnsafe(g)) {
        return ;
    }
    NOTICE_LOG << "Starting " << HttpServers.size() << " HTTP servers ..." << Endl;
    for (auto &server : HttpServers) {
        bool status = server->Start();
        VERIFY_WITH_LOG(status, "Error running server on port %d : %s", server->Options().Port, server->GetError());
    }
    // HttpServers::Start() completes only after OnListenStart cb is called, all threads should be running now
    VERIFY_WITH_LOG(IsRunningUnsafe(g), "Cannot start HTTP servers");
    DownstreamCallback->OnListenStart();
    NOTICE_LOG << "Starting " << HttpServers.size() << " HTTP servers ... OK" << Endl;
}

void THttpMultiServer::TImpl::Stop()
{
    TGuard<TMutex> g(StateLock);
    if (!IsRunningUnsafe(g)) {
        return ;
    }
    NOTICE_LOG << "Stopping " << HttpServers.size() << " HTTP servers ..." << Endl;
    for (auto &server : HttpServers) {
        server->Stop();
    }
    // THttpServer::Stop() is a sync call, should have 0 running threads by now.
    // If some listener didn't stop then it's better to crash, otherwise server restart may result in a mess.
    VERIFY_WITH_LOG(!IsRunningUnsafe(g), "Cannot stop HTTP servers");
    DownstreamCallback->OnListenStop();
    NOTICE_LOG << "Stopping " << HttpServers.size() << " HTTP servers ... OK" << Endl;
}

void THttpMultiServer::TImpl::ShutdownAndWait()
{
    TGuard<TMutex> g(StateLock);
    if (!IsRunningUnsafe(g)) {
        return ;
    }
    NOTICE_LOG << "Shutdown " << HttpServers.size() << " HTTP servers ..." << Endl;
    for (auto &server : HttpServers) {
        server->Shutdown();
    }
    NOTICE_LOG << "Shutdown " << HttpServers.size() << " HTTP servers ... OK" << Endl;

    // State may be inconsistent at this point, Shutdown() is an async call. Need to Wait() before checking the state.
    NOTICE_LOG << "Waiting for " << HttpServers.size() << " HTTP servers to stop ..." << Endl;
    DownstreamCallback->OnWait();
    for (auto &server : HttpServers) {
        server->Wait();
    }
    NOTICE_LOG << "Waiting for " << HttpServers.size() << " HTTP servers to stop ... OK" << Endl;

    // Now everything should be stopped, can check the state.
    NOTICE_LOG << "Verifying stopped state of " << HttpServers.size() << " HTTP servers ..." << Endl;
    VERIFY_WITH_LOG(!IsRunningUnsafe(g), "HTTP servers are still running");
    DownstreamCallback->OnListenStop();
    NOTICE_LOG << "Verifying stopped state of " << HttpServers.size() << " HTTP servers ... OK" << Endl;
}

bool THttpMultiServer::TImpl::IsRunning() const {
    TGuard<TMutex> g(StateLock);
    return IsRunningUnsafe(g);
}

bool THttpMultiServer::TImpl::IsRunningUnsafe(const TGuard<TMutex>&) const {
    size_t running = (size_t)AtomicGet(RunningCounter);
    if (running != 0 && running != HttpServers.size()) {
        FAIL_LOG("Inconsistent state of THttpMultiServer::TImpl: (%lu != 0) && (%lu != %lu)", running, running, HttpServers.size());
    }
    return (running != 0);
}

void THttpMultiServer::TImpl::OnFailRequest(int failstate) {
    DownstreamCallback->OnFailRequest(failstate);
}

void THttpMultiServer::TImpl::OnFailRequestEx(const TFailLogData& d) {
    DownstreamCallback->OnFailRequestEx(d);
}

void THttpMultiServer::TImpl::OnException() {
    DownstreamCallback->OnException();
}

void THttpMultiServer::TImpl::OnMaxConn() {
    DownstreamCallback->OnMaxConn();
}

TClientRequest* THttpMultiServer::TImpl::CreateClient() {
    return DownstreamCallback->CreateClient();
}

void THttpMultiServer::TImpl::OnListenStart() {
    auto newValue = (size_t)AtomicIncrement(RunningCounter);
    VERIFY_WITH_LOG(newValue <= HttpServers.size(), "Unexpected RunningCounter value: %lu", newValue);
    // DownstreamCallback->OnListenStart() is called once in TImpl::Start()
}

void THttpMultiServer::TImpl::OnListenStop() {
    auto newValue = (size_t)AtomicDecrement(RunningCounter);
    VERIFY_WITH_LOG(newValue >= 0, "Unexpected RunningCounter value: %lu", newValue);
    // DownstreamCallback->OnListenStop() is called once in TImpl::Stop() and TImpl::ShutdownAndWait()
}

void THttpMultiServer::TImpl::OnWait() {
    // DownstreamCallback->OnWait() is called once in TImpl::ShutdownAndWait()
}

void* THttpMultiServer::TImpl::CreateThreadSpecificResource() {
    return DownstreamCallback->CreateThreadSpecificResource();
}

void THttpMultiServer::TImpl::DestroyThreadSpecificResource(void* p) {
    DownstreamCallback->DestroyThreadSpecificResource(p);
}

THttpMultiServer::THttpMultiServer(ICallBack* cb, const TOptions& options, size_t servers)
    : Impl(MakeHolder<TImpl>(cb, options, servers))
{
}

THttpMultiServer::~THttpMultiServer() = default;

void THttpMultiServer::Start() {
    Impl->Start();
}

void THttpMultiServer::Stop() {
    Impl->Stop();
}

void THttpMultiServer::ShutdownAndWait() {
    Impl->ShutdownAndWait();
}

bool THttpMultiServer::IsRunning() const {
    return Impl->IsRunning();
}

