// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#include "precomp.h"

#include "WaitQueue.h"
#include "WaitBlock.h"

#include "../host/globals.h"
#include "../host/utils.hpp"

// Routine Description:
// - Instantiates a new ConsoleWaitQueue
ConsoleWaitQueue::ConsoleWaitQueue() :
    _blocks()
{
}

// Routine Description:
// - Destructs a ConsoleWaitQueue
// - This will notify any remaining waiting items that the associated process/object is dying.
ConsoleWaitQueue::~ConsoleWaitQueue()
{
    // Notify all blocks that the thread or object is dying when destroyed.
    NotifyWaiters(TRUE, WaitTerminationReason::ThreadDying);
}

// Routine Description:
// - Establishes a wait (call me back later) for a particular message with a given callback routine and its parameter
// Arguments:
// - pWaitReplyMessage - The API message that we're deferring until data is available later.
// - pWaiter - The context/callback information to restore and dispatch the call later.
// Return Value:
// - S_OK if enqueued appropriately and everything is alright. Or suitable HRESULT failure otherwise.
[[nodiscard]] HRESULT ConsoleWaitQueue::s_CreateWait(_Inout_ CONSOLE_API_MSG* const pWaitReplyMessage,
                                                     _In_ IWaitRoutine* const pWaiter)
{
    // Normally we'd have the Wait Queue handle the insertion of the block into the queue, but
    // the console does queues in a somewhat special way.
    //
    // Each block belongs in two queues:
    // 1. The process queue of the client that dispatched the request
    // 2. The object queue that the request will be serviced by
    // As such, when a wait occurs, it gets added to both queues.
    //
    // It will end up being serviced by one or the other queue, but when it is serviced, it must be
    // removed from both so it is not double processed.
    //
    // Therefore, I've inverted the queue management responsibility into the WaitBlock itself
    // and made it a friend to this WaitQueue class.

    return ConsoleWaitBlock::s_CreateWait(pWaitReplyMessage,
                                          pWaiter);
}

// Routine Description:
// - Instructs this queue to attempt to callback waiting requests
// Arguments:
// - fNotifyAll - If true, we will notify all items in the queue. If false, we will only notify the first item.
// Return Value:
// - True if any block was successfully notified. False if no blocks were successful.
bool ConsoleWaitQueue::NotifyWaiters(const bool fNotifyAll)
{
    return NotifyWaiters(fNotifyAll, WaitTerminationReason::NoReason);
}

// Routine Description:
// - Instructs this queue to attempt to callback waiting requests and request termination with the given reason
// Arguments:
// - fNotifyAll - If true, we will notify all items in the queue. If false, we will only notify the first item.
// - TerminationReason - A reason/message to pass to each waiter signaling it should terminate appropriately.
// Return Value:
// - True if any block was successfully notified. False if no blocks were successful.
bool ConsoleWaitQueue::NotifyWaiters(const bool fNotifyAll,
                                     const WaitTerminationReason TerminationReason)
{
    auto fResult = false;

    auto it = _blocks.cbegin();
    while (!_blocks.empty() && it != _blocks.cend())
    {
        const auto WaitBlock = (*it);
        if (nullptr == WaitBlock)
        {
            break;
        }

        const auto nextIt = std::next(it); // we have to capture next before it is potentially erased

        if (_NotifyBlock(WaitBlock, TerminationReason))
        {
            fResult = true;
        }

        if (!fNotifyAll)
        {
            break;
        }

        it = nextIt;
    }

    return fResult;
}

void ConsoleWaitQueue::CancelWaitersForScreenBuffer(const SCREEN_INFORMATION* pScreenInfo)
{
    for (auto it = _blocks.begin(); it != _blocks.end();)
    {
        const auto nextIt = std::next(it); // we have to capture next before it is potentially erased
        auto* pWaitBlock = *it;

        if (pWaitBlock->GetScreenBuffer() == pScreenInfo)
        {
            _NotifyBlock(pWaitBlock, WaitTerminationReason::HandleClosing);
        }

        it = nextIt;
    }
}

// Routine Description:
// - A helper to delete successfully notified callbacks
// Arguments:
// - pWaitBlock - A block containing callback data
// - TerminationReason - Optional reason to tell the callback to terminate. If 0, we're not requesting termination.
// Return Value:
// - True if callback successfully delivered data. False if callback still needs to wait longer.
bool ConsoleWaitQueue::_NotifyBlock(_In_ ConsoleWaitBlock* pWaitBlock,
                                    _In_ WaitTerminationReason TerminationReason)
{
    // Attempt to notify block with the given reason.
    const auto fResult = pWaitBlock->Notify(TerminationReason);

    if (fResult)
    {
        // If it was successful, delete it. (It will remove itself from appropriate queues.)
        delete pWaitBlock;
    }

    return fResult;
}
