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

#include "pch.h"
#include "Terminal.hpp"

using namespace Microsoft::Terminal::Core;
using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::Render;

Viewport Terminal::GetViewport() noexcept
{
    return _GetVisibleViewport();
}

til::point Terminal::GetTextBufferEndPosition() const noexcept
{
    // We use the end line of mutableViewport as the end
    // of the text buffer, it always moves with the written
    // text
    return { _GetMutableViewport().Width() - 1, ViewEndIndex() };
}

TextBuffer& Terminal::GetTextBuffer() const noexcept
{
    return _activeBuffer();
}

const FontInfo& Terminal::GetFontInfo() const noexcept
{
    _assertLocked();
    return _fontInfo;
}

void Terminal::SetFontInfo(const FontInfo& fontInfo)
{
    _assertLocked();
    _fontInfo = fontInfo;
}

TimerDuration Terminal::GetBlinkInterval() noexcept
{
    if (!_cursorBlinkInterval)
    {
        const auto enabled = GetSystemMetrics(SM_CARETBLINKINGENABLED);
        const auto interval = GetCaretBlinkTime();
        // >10s --> no blinking. The limit is arbitrary, because technically the valid range
        // on Windows is 200-1200ms. GetCaretBlinkTime() returns INFINITE for no blinking, 0 for errors.
        _cursorBlinkInterval = enabled && interval <= 10000 ? std ::chrono::milliseconds(interval) : TimerDuration::max();
    }
    return *_cursorBlinkInterval;
}

ULONG Terminal::GetCursorPixelWidth() const noexcept
{
    return 1;
}

bool Terminal::IsGridLineDrawingAllowed() noexcept
{
    return true;
}

std::wstring Microsoft::Terminal::Core::Terminal::GetHyperlinkUri(uint16_t id) const
{
    return _activeBuffer().GetHyperlinkUriFromId(id);
}

std::wstring Microsoft::Terminal::Core::Terminal::GetHyperlinkCustomId(uint16_t id) const
{
    return _activeBuffer().GetCustomIdFromId(id);
}

// Method Description:
// - Gets the regex pattern ids of a location
// Arguments:
// - The location
// Return value:
// - The pattern IDs of the location
std::vector<size_t> Terminal::GetPatternId(const til::point location) const
{
    _assertLocked();

    // Look through our interval tree for this location
    const auto intervals = _patternIntervalTree.findOverlapping({ location.x + 1, location.y }, location);
    if (intervals.size() == 0)
    {
        return {};
    }
    else
    {
        std::vector<size_t> result{};
        for (const auto& interval : intervals)
        {
            result.emplace_back(interval.value);
        }
        return result;
    }
    return {};
}

std::pair<COLORREF, COLORREF> Terminal::GetAttributeColors(const TextAttribute& attr) const noexcept
{
    return GetRenderSettings().GetAttributeColors(attr);
}

std::span<const til::point_span> Terminal::GetSelectionSpans() const noexcept
try
{
    if (_selection.generation() != _lastSelectionGeneration)
    {
        _lastSelectionSpans = _GetSelectionSpans();
        _lastSelectionGeneration = _selection.generation();
    }

    return _lastSelectionSpans;
}
catch (...)
{
    LOG_CAUGHT_EXCEPTION();
    return {};
}

// Method Description:
// - Helper to determine the search highlights in the buffer. Used for rendering.
// Return Value:
// - A vector of rectangles representing the regions to select, line by line. They are absolute coordinates relative to the buffer origin.
std::span<const til::point_span> Terminal::GetSearchHighlights() const noexcept
{
    _assertLocked();
    return _searchHighlights;
}

const til::point_span* Terminal::GetSearchHighlightFocused() const noexcept
{
    _assertLocked();
    if (_searchHighlightFocused < _searchHighlights.size())
    {
        return &til::at(_searchHighlights, _searchHighlightFocused);
    }
    return nullptr;
}

// Method Description:
// - If necessary, scrolls the viewport such that the start point is in the
//   viewport, and if that's already the case, also brings the end point inside
//   the viewport
// Arguments:
// - coordStart - The start point
// - coordEnd - The end point
// Return Value:
// - The updated scroll offset
til::CoordType Terminal::_ScrollToPoints(const til::point coordStart, const til::point coordEnd)
{
    if (coordStart.y < _VisibleStartIndex())
    {
        _ScrollToPoint(coordStart);
    }
    else if (coordEnd.y > _VisibleEndIndex())
    {
        _ScrollToPoint(coordEnd);
    }
    return _VisibleStartIndex();
}

// Method Description:
// - selects the region from coordStart to coordEnd
// Arguments:
// - coordStart - The start point (inclusive)
// - coordEnd - The end point (inclusive)
void Terminal::SelectNewRegion(const til::point coordStart, const til::point coordEnd)
{
    const auto newScrollOffset = _ScrollToPoints(coordStart, coordEnd);

    // update the selection coordinates so they're relative to the new scroll-offset
    const auto newCoordStart = til::point{ coordStart.x, coordStart.y - newScrollOffset };
    const auto newCoordEnd = til::point{ coordEnd.x, coordEnd.y - newScrollOffset };
    SetSelectionAnchor(newCoordStart);
    SetSelectionEnd(newCoordEnd, SelectionExpansion::Char);
    _activeBuffer().TriggerSelection();
}

std::wstring_view Terminal::GetConsoleTitle() const noexcept
{
    _assertLocked();
    if (_title.has_value())
    {
        return *_title;
    }
    return _startingTitle;
}

// Method Description:
// - Lock the terminal for reading the contents of the buffer. Ensures that the
//      contents of the terminal won't be changed in the middle of a paint
//      operation.
//   Callers should make sure to also call Terminal::UnlockConsole once
//      they're done with any querying they need to do.
void Terminal::LockConsole() noexcept
{
    _readWriteLock.lock();
}

// Method Description:
// - Unlocks the terminal after a call to Terminal::LockConsole.
void Terminal::UnlockConsole() noexcept
{
    _readWriteLock.unlock();
}

bool Terminal::IsUiaDataInitialized() const noexcept
{
    // GH#11135: Windows Terminal needs to create and return an automation peer
    // when a screen reader requests it. However, the terminal might not be fully
    // initialized yet. So we use this to check if any crucial components of
    // UiaData are not yet initialized.
    _assertLocked();
    return !!_mainBuffer;
}
