/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
#include "ExecutorService.h"

#include <algorithm>

#include "LogUtils.h"
#include "TimeUtils.h"
DECLARE_LOG_OBJECT()

namespace pulsar {

ExecutorService::ExecutorService() {}

ExecutorService::~ExecutorService() { close(0); }

void ExecutorService::start() {
    auto self = shared_from_this();
    std::thread t{[this, self] {
        LOG_DEBUG("Run io_context in a single thread");
        while (!closed_) {
            io_context_.restart();
            auto work_guard = ASIO::make_work_guard(io_context_);
            try {
                io_context_.run();
                LOG_DEBUG("Event loop of ExecutorService exits successfully");
            } catch (const ASIO_SYSTEM_ERROR &e) {
                LOG_ERROR("Failed to run io_context: " << e.what());
            }
        }
        {
            std::lock_guard<std::mutex> lock{mutex_};
            ioServiceDone_ = true;
        }
        cond_.notify_all();
    }};
    t.detach();
}

ExecutorServicePtr ExecutorService::create() {
    // make_shared cannot access the private constructor, so we need to expose the private constructor via a
    // derived class.
    struct ExecutorServiceImpl : public ExecutorService {};

    auto executor = std::make_shared<ExecutorServiceImpl>();
    executor->start();
    return std::static_pointer_cast<ExecutorService>(executor);
}

/*
 *  factory method of ASIO::ip::tcp::socket associated with io_context_ instance
 *  @ returns shared_ptr to this socket
 */
SocketPtr ExecutorService::createSocket() {
    try {
        return SocketPtr(new ASIO::ip::tcp::socket(io_context_));
    } catch (const ASIO_SYSTEM_ERROR &e) {
        restart();
        auto error = std::string("Failed to create socket: ") + e.what();
        throw std::runtime_error(error);
    }
}

TlsSocketPtr ExecutorService::createTlsSocket(SocketPtr &socket, ASIO::ssl::context &ctx) {
    return std::shared_ptr<ASIO::ssl::stream<ASIO::ip::tcp::socket &>>(
        new ASIO::ssl::stream<ASIO::ip::tcp::socket &>(*socket, ctx));
}

/*
 *  factory method of Resolver object associated with io_context_ instance
 *  @returns shraed_ptr to resolver object
 */
TcpResolverPtr ExecutorService::createTcpResolver() {
    try {
        return TcpResolverPtr(new ASIO::ip::tcp::resolver(io_context_));
    } catch (const ASIO_SYSTEM_ERROR &e) {
        restart();
        auto error = std::string("Failed to create resolver: ") + e.what();
        throw std::runtime_error(error);
    }
}

DeadlineTimerPtr ExecutorService::createDeadlineTimer() {
    try {
        return DeadlineTimerPtr(new ASIO::steady_timer(io_context_));
    } catch (const ASIO_SYSTEM_ERROR &e) {
        restart();
        auto error = std::string("Failed to create steady_timer: ") + e.what();
        throw std::runtime_error(error);
    }
}

void ExecutorService::restart() { io_context_.stop(); }

void ExecutorService::close(long timeoutMs) {
    bool expectedState = false;
    if (!closed_.compare_exchange_strong(expectedState, true)) {
        return;
    }
    if (timeoutMs == 0) {  // non-blocking
        io_context_.stop();
        return;
    }

    std::unique_lock<std::mutex> lock{mutex_};
    io_context_.stop();
    if (timeoutMs > 0) {
        cond_.wait_for(lock, std::chrono::milliseconds(timeoutMs), [this] { return ioServiceDone_; });
    } else {  // < 0
        cond_.wait(lock, [this] { return ioServiceDone_; });
    }
}

/////////////////////

ExecutorServiceProvider::ExecutorServiceProvider(int nthreads)
    : executors_(std::max(1, nthreads)), executorIdx_(0), mutex_() {}

ExecutorServicePtr ExecutorServiceProvider::get(size_t idx) {
    if (executors_.empty()) {
        return nullptr;
    }
    idx %= executors_.size();
    Lock lock(mutex_);

    if (!executors_[idx]) {
        executors_[idx] = ExecutorService::create();
    }

    return executors_[idx];
}

void ExecutorServiceProvider::close(long timeoutMs) {
    Lock lock(mutex_);

    TimeoutProcessor<std::chrono::milliseconds> timeoutProcessor{timeoutMs};
    for (auto &&executor : executors_) {
        timeoutProcessor.tik();
        if (executor) {
            executor->close(timeoutProcessor.getLeftTimeout());
        }
        timeoutProcessor.tok();
        executor.reset();
    }
}
}  // namespace pulsar
