// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Can't compile this for Zircon userspace yet since libstdc++ isn't available.
#ifndef FIT_NO_STD_FOR_ZIRCON_USERSPACE

#include <lib/fit/scheduler.h>

#include <map>
#include <queue>
#include <utility>

namespace fit {
namespace subtle {

scheduler::scheduler() = default;

scheduler::~scheduler() = default;

void scheduler::schedule_task(pending_task task) {
    assert(task);
    runnable_tasks_.push(std::move(task));
}

suspended_task::ticket scheduler::obtain_ticket(uint32_t initial_refs) {
    suspended_task::ticket ticket = next_ticket_++;
    tickets_.emplace(ticket, ticket_record(initial_refs));
    return ticket;
}

void scheduler::finalize_ticket(suspended_task::ticket ticket,
                                pending_task* task) {
    auto it = tickets_.find(ticket);
    assert(it != tickets_.end());
    assert(!it->second.task);
    assert(it->second.ref_count > 0);
    assert(task);

    it->second.ref_count--;
    if (!*task) {
        // task already finished
    } else if (it->second.was_resumed) {
        // task immediately became runnable
        runnable_tasks_.push(std::move(*task));
    } else if (it->second.ref_count > 0) {
        // task remains suspended
        it->second.task = std::move(*task);
        suspended_task_count_++;
    } // else, task was abandoned and caller retains ownership of it
    if (it->second.ref_count == 0) {
        tickets_.erase(it);
    }
}

void scheduler::duplicate_ticket(suspended_task::ticket ticket) {
    auto it = tickets_.find(ticket);
    assert(it != tickets_.end());
    assert(it->second.ref_count > 0);

    it->second.ref_count++;
    assert(it->second.ref_count != 0); // did we really make 4 billion refs?!
}

pending_task scheduler::release_ticket(suspended_task::ticket ticket) {
    auto it = tickets_.find(ticket);
    assert(it != tickets_.end());
    assert(it->second.ref_count > 0);

    it->second.ref_count--;
    if (it->second.ref_count == 0) {
        pending_task task = std::move(it->second.task);
        if (task) {
            assert(suspended_task_count_ > 0);
            suspended_task_count_--;
        }
        tickets_.erase(it);
        return task;
    }
    return pending_task();
}

bool scheduler::resume_task_with_ticket(suspended_task::ticket ticket) {
    auto it = tickets_.find(ticket);
    assert(it != tickets_.end());
    assert(it->second.ref_count > 0);

    bool did_resume = false;
    it->second.ref_count--;
    if (!it->second.was_resumed) {
        it->second.was_resumed = true;
        if (it->second.task) {
            did_resume = true;
            assert(suspended_task_count_ > 0);
            suspended_task_count_--;
            runnable_tasks_.push(std::move(it->second.task));
        }
    }
    if (it->second.ref_count == 0) {
        tickets_.erase(it);
    }
    return did_resume;
}

void scheduler::take_runnable_tasks(task_queue* tasks) {
    assert(tasks && tasks->empty());
    runnable_tasks_.swap(*tasks);
}

void scheduler::take_all_tasks(task_queue* tasks) {
    assert(tasks && tasks->empty());

    runnable_tasks_.swap(*tasks);
    if (suspended_task_count_ > 0) {
        for (auto& item : tickets_) {
            if (item.second.task) {
                assert(suspended_task_count_ > 0);
                suspended_task_count_--;
                tasks->push(std::move(item.second.task));
            }
        }
    }
}

} // namespace subtle
} // namespace fit

#endif // FIT_NO_STD_FOR_ZIRCON_USERSPACE