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

#include "precomp.h"
#include "CommandNumberPopup.hpp"

#include "stream.h"
#include "_stream.h"
#include "cmdline.h"
#include "resource.h"

#include "../interactivity/inc/ServiceLocator.hpp"

// 5 digit number for command history
static constexpr size_t COMMAND_NUMBER_LENGTH = 5;

static constexpr size_t COMMAND_NUMBER_PROMPT_LENGTH = 22;

CommandNumberPopup::CommandNumberPopup(SCREEN_INFORMATION& screenInfo) :
    Popup(screenInfo, { COMMAND_NUMBER_PROMPT_LENGTH + COMMAND_NUMBER_LENGTH, 1 })
{
    _userInput.reserve(COMMAND_NUMBER_LENGTH);
}

// Routine Description:
// - handles numerical user input
// Arguments:
// - cookedReadData - read data to operate on
// - wch - digit to handle
void CommandNumberPopup::_handleNumber(COOKED_READ_DATA& cookedReadData, const wchar_t wch) noexcept
{
    if (_userInput.size() < COMMAND_NUMBER_LENGTH)
    {
        auto CharsToWrite = sizeof(wchar_t);
        const auto realAttributes = cookedReadData.ScreenInfo().GetAttributes();
        cookedReadData.ScreenInfo().SetAttributes(_attributes);
        size_t NumSpaces;
        FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(),
                                                      _userInput.data(),
                                                      _userInput.data() + _userInput.size(),
                                                      &wch,
                                                      &CharsToWrite,
                                                      &NumSpaces,
                                                      cookedReadData.OriginalCursorPosition().x,
                                                      WC_DESTRUCTIVE_BACKSPACE | WC_KEEP_CURSOR_VISIBLE | WC_PRINTABLE_CONTROL_CHARS,
                                                      nullptr));
        cookedReadData.ScreenInfo().SetAttributes(realAttributes);
        try
        {
            _push(wch);
        }
        CATCH_LOG();
    }
}

// Routine Description:
// - handles backspace user input. removes a digit from the user input
// Arguments:
// - cookedReadData - read data to operate on
void CommandNumberPopup::_handleBackspace(COOKED_READ_DATA& cookedReadData) noexcept
{
    if (_userInput.size() > 0)
    {
        auto CharsToWrite = sizeof(WCHAR);
        const auto backspace = UNICODE_BACKSPACE;
        const auto realAttributes = cookedReadData.ScreenInfo().GetAttributes();
        cookedReadData.ScreenInfo().SetAttributes(_attributes);
        size_t NumSpaces;
        FAIL_FAST_IF_NTSTATUS_FAILED(WriteCharsLegacy(cookedReadData.ScreenInfo(),
                                                      _userInput.data(),
                                                      _userInput.data() + _userInput.size(),
                                                      &backspace,
                                                      &CharsToWrite,
                                                      &NumSpaces,
                                                      cookedReadData.OriginalCursorPosition().x,
                                                      WC_DESTRUCTIVE_BACKSPACE | WC_KEEP_CURSOR_VISIBLE | WC_PRINTABLE_CONTROL_CHARS,
                                                      nullptr));
        cookedReadData.ScreenInfo().SetAttributes(realAttributes);
        _pop();
    }
}

// Routine Description:
// - handles escape user input. cancels the popup
// Arguments:
// - cookedReadData - read data to operate on
void CommandNumberPopup::_handleEscape(COOKED_READ_DATA& cookedReadData) noexcept
{
    CommandLine::Instance().EndAllPopups();

    // Note that cookedReadData's OriginalCursorPosition is the position before ANY text was entered on the edit line.
    // We want to use the position before the cursor was moved for this popup handler specifically, which may
    // be *anywhere* in the edit line and will be synchronized with the pointers in the cookedReadData
    // structure (BufPtr, etc.)
    LOG_IF_FAILED(cookedReadData.ScreenInfo().SetCursorPosition(cookedReadData.BeforeDialogCursorPosition(), TRUE));
}

// Routine Description:
// - handles return user input. sets the prompt to the history item indicated
// Arguments:
// - cookedReadData - read data to operate on
void CommandNumberPopup::_handleReturn(COOKED_READ_DATA& cookedReadData) noexcept
{
    const auto commandNumber = gsl::narrow<short>(std::min(static_cast<size_t>(_parse()),
                                                           cookedReadData.History().GetNumberOfCommands() - 1));

    CommandLine::Instance().EndAllPopups();
    SetCurrentCommandLine(cookedReadData, commandNumber);
}

// Routine Description:
// - This routine handles the command number selection popup.
// Return Value:
// - CONSOLE_STATUS_WAIT - we ran out of input, so a wait block was created
// - CONSOLE_STATUS_READ_COMPLETE - user hit return
[[nodiscard]] NTSTATUS CommandNumberPopup::Process(COOKED_READ_DATA& cookedReadData) noexcept
{
    auto Status = STATUS_SUCCESS;
    auto wch = UNICODE_NULL;
    auto popupKeys = false;
    DWORD modifiers = 0;

    for (;;)
    {
        Status = _getUserInput(cookedReadData, popupKeys, modifiers, wch);
        if (FAILED_NTSTATUS(Status))
        {
            return Status;
        }

        if (std::iswdigit(wch))
        {
            _handleNumber(cookedReadData, wch);
        }
        else if (wch == UNICODE_BACKSPACE)
        {
            _handleBackspace(cookedReadData);
        }
        else if (wch == VK_ESCAPE)
        {
            _handleEscape(cookedReadData);
            break;
        }
        else if (wch == UNICODE_CARRIAGERETURN)
        {
            _handleReturn(cookedReadData);
            break;
        }
    }
    return CONSOLE_STATUS_WAIT_NO_BLOCK;
}

void CommandNumberPopup::_DrawContent()
{
    _DrawPrompt(ID_CONSOLE_MSGCMDLINEF9);
}

// Routine Description:
// - adds single digit number to the popup's number buffer
// Arguments:
// - wch - char of the number to add. must be in the range [L'0', L'9']
// Note: will throw if wch is out of range
void CommandNumberPopup::_push(const wchar_t wch)
{
    THROW_HR_IF(E_INVALIDARG, !std::iswdigit(wch));
    if (_userInput.size() < COMMAND_NUMBER_LENGTH)
    {
        _userInput += wch;
    }
}

// Routine Description:
// - removes the last number added to the number buffer
void CommandNumberPopup::_pop() noexcept
{
    if (!_userInput.empty())
    {
        _userInput.pop_back();
    }
}

// Routine Description:
// - get numerical value for the data stored in the number buffer
// Return Value:
// - parsed integer representing the string value found in the number buffer
int CommandNumberPopup::_parse() const noexcept
{
    try
    {
        return std::stoi(_userInput);
    }
    catch (...)
    {
        return 0;
    }
}
