#pragma once

#include <type_traits>
#include <new>
#include <utility>  // [[since C++14]]: std::exchange
#include <algorithm>
#include <atomic>
#include <tuple>
#include <thread>
#include <chrono>
#include <string>
#include <cassert>  // assert

#include "libipc/def.h"
#include "libipc/shm.h"
#include "libipc/rw_lock.h"

#include "libipc/utility/log.h"
#include "libipc/platform/detail.h"
#include "libipc/circ/elem_def.h"

namespace ipc {
namespace detail {

class queue_conn {
protected:
    circ::cc_t connected_ = 0;
    shm::handle elems_h_;

    template <typename Elems>
    Elems* open(char const * name) {
        if (name == nullptr || name[0] == '\0') {
            ipc::error("fail open waiter: name is empty!\n");
            return nullptr;
        }
        if (!elems_h_.acquire(name, sizeof(Elems))) {
            return nullptr;
        }
        auto elems = static_cast<Elems*>(elems_h_.get());
        if (elems == nullptr) {
            ipc::error("fail acquire elems: %s\n", name);
            return nullptr;
        }
        elems->init();
        return elems;
    }

    void close() {
        elems_h_.release();
    }

public:
    queue_conn() = default;
    queue_conn(const queue_conn&) = delete;
    queue_conn& operator=(const queue_conn&) = delete;

    bool connected() const noexcept {
        return connected_ != 0;
    }

    circ::cc_t connected_id() const noexcept {
        return connected_;
    }

    template <typename Elems>
    auto connect(Elems* elems) noexcept
                         /*needs 'optional' here*/
     -> std::tuple<bool, bool, decltype(std::declval<Elems>().cursor())> {
        if (elems == nullptr) return {};
        // if it's already connected, just return
        if (connected()) return {connected(), false, 0};
        connected_ = elems->connect_receiver();
        return {connected(), true, elems->cursor()};
    }

    template <typename Elems>
    bool disconnect(Elems* elems) noexcept {
        if (elems == nullptr) return false;
        // if it's already disconnected, just return false
        if (!connected()) return false;
        elems->disconnect_receiver(std::exchange(connected_, 0));
        return true;
    }
};

template <typename Elems>
class queue_base : public queue_conn {
    using base_t = queue_conn;

public:
    using elems_t  = Elems;
    using policy_t = typename elems_t::policy_t;

protected:
    elems_t * elems_ = nullptr;
    decltype(std::declval<elems_t>().cursor()) cursor_ = 0;
    bool sender_flag_ = false;

public:
    using base_t::base_t;

    queue_base() = default;

    explicit queue_base(char const * name)
        : queue_base{} {
        elems_ = open<elems_t>(name);
    }

    explicit queue_base(elems_t * elems) noexcept
        : queue_base{} {
        assert(elems != nullptr);
        elems_ = elems;
    }

    /* not virtual */ ~queue_base() {
        base_t::close();
    }

    elems_t       * elems()       noexcept { return elems_; }
    elems_t const * elems() const noexcept { return elems_; }

    bool ready_sending() noexcept {
        if (elems_ == nullptr) return false;
        return sender_flag_ || (sender_flag_ = elems_->connect_sender());
    }

    void shut_sending() noexcept {
        if (elems_ == nullptr) return;
        if (!sender_flag_) return;
        elems_->disconnect_sender();
    }

    bool connect() noexcept {
        auto tp = base_t::connect(elems_);
        if (std::get<0>(tp) && std::get<1>(tp)) {
            cursor_ = std::get<2>(tp);
            return true;
        }
        return std::get<0>(tp);
    }

    bool disconnect() noexcept {
        return base_t::disconnect(elems_);
    }

    std::size_t conn_count() const noexcept {
        return (elems_ == nullptr) ? static_cast<std::size_t>(invalid_value) : elems_->conn_count();
    }

    bool valid() const noexcept {
        return elems_ != nullptr;
    }

    bool empty() const noexcept {
        return !valid() || (cursor_ == elems_->cursor());
    }

    template <typename T, typename F, typename... P>
    bool push(F&& prep, P&&... params) {
        if (elems_ == nullptr) return false;
        return elems_->push(this, [&](void* p) {
            if (prep(p)) ::new (p) T(std::forward<P>(params)...);
        });
    }

    template <typename T, typename F, typename... P>
    bool force_push(F&& prep, P&&... params) {
        if (elems_ == nullptr) return false;
        return elems_->force_push(this, [&](void* p) {
            if (prep(p)) ::new (p) T(std::forward<P>(params)...);
        });
    }

    template <typename T, typename F>
    bool pop(T& item, F&& out) {
        if (elems_ == nullptr) {
            return false;
        }
        return elems_->pop(this, &(this->cursor_), [&item](void* p) {
            ::new (&item) T(std::move(*static_cast<T*>(p)));
        }, std::forward<F>(out));
    }
};

} // namespace detail

template <typename T, typename Policy>
class queue final : public detail::queue_base<typename Policy::template elems_t<sizeof(T), alignof(T)>> {
    using base_t = detail::queue_base<typename Policy::template elems_t<sizeof(T), alignof(T)>>;

public:
    using value_t = T;

    using base_t::base_t;

    template <typename... P>
    bool push(P&&... params) {
        return base_t::template push<T>(std::forward<P>(params)...);
    }

    template <typename... P>
    bool force_push(P&&... params) {
        return base_t::template force_push<T>(std::forward<P>(params)...);
    }

    bool pop(T& item) {
        return base_t::pop(item, [](bool) {});
    }

    template <typename F>
    bool pop(T& item, F&& out) {
        return base_t::pop(item, std::forward<F>(out));
    }
};

} // namespace ipc
