#include "tile/io/descriptor.h"

#include "tile/base/internal/move_on_copy.h"
#include "tile/base/internal/time_keeper.h"
#include "tile/base/logging.h"
#include "tile/base/make_unique.h"
#include "tile/base/string.h"
#include "tile/base/thread/cond_var.h"
#include "tile/base/thread/mutex.h"
#include "tile/base/thread/scoped_lock.h"
#include "tile/io/event_loop.h"

namespace tile {

namespace {
void
CreateOneTimeTimer(std::function<void()> callback, std::chrono::nanoseconds ns)
{
    if (ns.count() != 0) {
        auto moved_callback = MakeMoveOnCopy(callback);
        internal::TimeKeeper::Instance()->AddTimer(
            ReadSteadyClock() + ns, ns, [moved_callback] { moved_callback.Ref()(); }, true);
    } else {
        callback();
    }
}

}// namespace

struct Descriptor::SeldomlyUsed {
    std::string name;
    std::atomic<bool> cleanup_queued{false};
    std::atomic<std::size_t> error_events{};
    std::atomic<bool> error_seen{};
    std::atomic<CleanupReason> cleanup_reason{CleanupReason::None};

    Mutex cleanup_lock;
    CondVar cleanup_cv;
    bool cleanup_completed{false};
};

Descriptor::Descriptor(Handle fd, Event events, const std::string &name)
{
    // static_assert(offsetof(Descriptor, read_mostly_) ==
    //                   hardware_destructive_interference_size,
    //               "");

    // init read_mostly_
    read_mostly_.fd            = std::move(fd);
    read_mostly_.event_mask    = static_cast<int>(events);
    read_mostly_.seldomly_used = make_unique<SeldomlyUsed>();

    restart_read_count_        = HasEventMask(Event::Write) ? 1 : 0;
    restart_read_count_        = HasEventMask(Event::Write) ? 1 : 0;

    if (name.empty()) {
        read_mostly_.seldomly_used->name = Format("{}", fmt::ptr(this));
    } else {
        read_mostly_.seldomly_used->name = Format("{} @ {}", name, fmt::ptr(this));
    }
}

Descriptor::~Descriptor()
{
    TILE_CHECK(!Enabled(),
               "Descriptor {} is still associated with event loop {} when it's "
               "destructed.",
               fmt::ptr(this), fmt::ptr(GetEventLoop()));
}

void
Descriptor::RestartReadIn(std::chrono::nanoseconds after)
{
    auto ref = RefPtr<Descriptor>(ref_ptr, this);
    CreateOneTimeTimer([this, ref] { RestartReadNow(); }, after);
}

void
Descriptor::RestartWriteIn(std::chrono::nanoseconds after)
{
    auto ref = RefPtr<Descriptor>(ref_ptr, this);
    CreateOneTimeTimer([this, ref] { RestartWriteNow(); }, after);
}

void
Descriptor::Kill(CleanupReason reason)
{
    TILE_CHECK(reason != CleanupReason::None);
    auto expected = CleanupReason::None;
    if (read_mostly_.seldomly_used->cleanup_reason.compare_exchange_strong(
            expected, reason, std::memory_order_relaxed)) {
        auto ref = RefPtr<Descriptor>(ref_ptr, this);
        GetEventLoop()->AddTask([this, ref] {
            GetEventLoop()->DisabledDescriptor(this);
            cleanup_pending_.store(true, std::memory_order_relaxed);

            QueueCleanupCallbackCheck();
        });
    }
}

void
Descriptor::WaitForCleanup()
{
    ScopedLock lock(read_mostly_.seldomly_used->cleanup_lock);
    read_mostly_.seldomly_used->cleanup_cv.Wait(lock, [this] { return read_mostly_.seldomly_used->cleanup_completed; });
}

const std::string &
Descriptor::GetName() const
{
    return read_mostly_.seldomly_used->name;
}

void
Descriptor::FireEvents(int mask, std::uint64_t polled_at)
{
    if (TILE_UNLIKELY(mask & static_cast<int>(Event::Read))) {
        FireErrorEvent(polled_at);
        return;
    }

    if (mask & static_cast<int>(Event::Write)) { FireWriteEvent(polled_at); }

    if (mask & static_cast<int>(Event::Read)) { FireReadEvent(polled_at); }
}

void
Descriptor::FireReadEvent(std::uint64_t fired_at)
{
    if (read_events_.fetch_add(1, std::memory_order_acquire) == 0) {
        RefPtr<Descriptor> ref(ref_ptr, this);
        do {
            auto rc = OnReadable();

            if (TILE_LIKELY(rc == EventAction::Ready)) {
                continue;
            } else if (TILE_UNLIKELY(rc == EventAction::Leaving)) {
                TILE_CHECK(
                    read_mostly_.seldomly_used->cleanup_reason.load(std::memory_order_relaxed) != CleanupReason::None,
                    "Did you forget to call `Kill()`");

                GetEventLoop()->AddTask([this, ref] {
                    read_events_.store(0, std::memory_order_relaxed);
                    QueueCleanupCallbackCheck();
                });
                break;
            } else if (rc == EventAction::Suppress) {
                SuppressReadAndClearReadEventCount();
                break;
            }
        } while (read_events_.fetch_sub(1, std::memory_order_release) != 1);

        QueueCleanupCallbackCheck();
    }
}

void
Descriptor::FireWriteEvent(std::uint64_t fired_at)
{
    if (write_events_.fetch_add(1, std::memory_order_acquire) == 0) {
        RefPtr<Descriptor> ref(ref_ptr, this);
        do {
            auto rc = OnWritable();
            if (TILE_LIKELY(rc == EventAction::Ready)) {
                continue;
            } else if (TILE_UNLIKELY(rc == EventAction::Leaving)) {
                TILE_CHECK(
                    read_mostly_.seldomly_used->cleanup_reason.load(std::memory_order_relaxed) != CleanupReason::None,
                    "Did you forget to call `Kill()`");
                GetEventLoop()->AddTask([this, ref] {
                    write_events_.store(0, std::memory_order_relaxed);
                    QueueCleanupCallbackCheck();
                });
                break;
            } else {
                TILE_UNREACHABLE("Unexpected EventAction");
            }
        } while (write_events_.fetch_sub(1, std::memory_order_release) != 1);
        QueueCleanupCallbackCheck();
    }
}

void
Descriptor::FireErrorEvent(std::uint64_t fired_at)
{
    if (read_mostly_.seldomly_used->error_seen.exchange(true, std::memory_order_relaxed)) {
        TILE_VLOG(10, "Unexpected: Multiple `Event::Error` received.");
        return;
    }

    if (read_mostly_.seldomly_used->error_events.fetch_add(1, std::memory_order_relaxed) == 0) {
        TILE_CHECK_EQ(read_mostly_.seldomly_used->error_events.fetch_sub(1, std::memory_order_release), 1);
        QueueCleanupCallbackCheck();
    } else {
        TILE_UNEXPECTED("Don't Allow Multi Error");
    }
}

void
Descriptor::RestartReadNow()
{
    RefPtr<Descriptor> ref(ref_ptr, this);
    GetEventLoop()->AddTask([this, ref] {
        if (Enabled()) {
            auto count = restart_read_count_.fetch_add(1, std::memory_order_relaxed);

            if (count == 0) {
                TILE_CHECK(!HasEventMask(Event::Read));
                SetEventMask(GetEventMask() | static_cast<int>(Event::Read));
                GetEventLoop()->RearmDescriptor(this);
            }
        }
    });
}

void
Descriptor::SuppressReadAndClearReadEventCount()
{
    RefPtr<Descriptor> ref(ref_ptr, this);
    GetEventLoop()->AddTask([this, ref] {
        read_events_.store(0, std::memory_order_relaxed);
        QueueCleanupCallbackCheck();

        if (Enabled()) {
            auto reached = restart_read_count_.fetch_sub(1, std::memory_order_relaxed) - 1;
            TILE_CHECK_NE(reached, -1, "Must call by `RestartReadNow`");
            TILE_CHECK(GetEventMask() & static_cast<int>(Event::Read));

            if (reached == 0) {
                SetEventMask(GetEventMask() & ~static_cast<int>(Event::Read));
                GetEventLoop()->RearmDescriptor(this);
            } else {
                FireEvents(static_cast<int>(Event::Read), 0);
            }
        }
    });
}

void
Descriptor::SuppressWriteAndClearWriteEventCount()
{
    RefPtr<Descriptor> ref(ref_ptr, this);

    GetEventLoop()->AddTask([this, ref] {
        write_events_.store(0, std::memory_order_relaxed);
        QueueCleanupCallbackCheck();

        if (Enabled()) {
            auto reached = restart_write_count_.fetch_sub(1, std::memory_order_relaxed) - 1;
            TILE_CHECK(reached == 0 || reached == 1, "Unexpected restart-write count: {}");
            TILE_CHECK(GetEventMask() & static_cast<int>(Event::Write));

            if (reached == 0) {
                SetEventMask(GetEventMask() & ~static_cast<int>(Event::Write));
                GetEventLoop()->RearmDescriptor(this);
            } else {
                FireEvents(static_cast<int>(Event::Write), 0);
            }
        }
    });
}

void
Descriptor::RestartWriteNow()
{
    RefPtr<Descriptor> ref(ref_ptr, this);
    GetEventLoop()->AddTask([this, ref] {
        if (Enabled()) {
            auto count = restart_write_count_.fetch_add(1, std::memory_order_relaxed);
            if (count == 0) {
                TILE_CHECK(!HasEventMask(Event::Write));
                SetEventMask(GetEventMask() | static_cast<int>(Event::Write));
                GetEventLoop()->RearmDescriptor(this);
            }
        }
    });
}

void
Descriptor::QueueCleanupCallbackCheck()
{
    if (TILE_LIKELY(!cleanup_pending_.load(std::memory_order_relaxed))) { return; }

    bool no_event = read_events_.load(std::memory_order_relaxed) == 0
        && write_events_.load(std::memory_order_relaxed) == 0
        && read_mostly_.seldomly_used->error_events.load(std::memory_order_relaxed) == 0;
    if (no_event) {
        if (!read_mostly_.seldomly_used->cleanup_queued.exchange(true, std::memory_order_relaxed)) {
            RefPtr<Descriptor> ref(ref_ptr, this);
            GetEventLoop()->AddTask([this, ref] {
                TILE_CHECK_EQ(read_events_.load(std::memory_order_relaxed), 0);
                TILE_CHECK_EQ(write_events_.load(std::memory_order_relaxed), 0);
                TILE_CHECK_EQ(read_mostly_.seldomly_used->error_events.load(std::memory_order_relaxed), 0);

                GetEventLoop()->DetachDescriptor(this);
                OnCleanup(read_mostly_.seldomly_used->cleanup_reason.load(std::memory_order_relaxed));

                ScopedLock _(read_mostly_.seldomly_used->cleanup_lock);
                read_mostly_.seldomly_used->cleanup_completed = true;
                read_mostly_.seldomly_used->cleanup_cv.NotifyOne();
            });
        }
    }
}

}// namespace tile
