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

#include "precomp.h"
#include "WexTestClass.h"
#include "..\..\inc\consoletaeftemplates.hpp"

#include "CommonState.hpp"

#include "../../types/inc/Viewport.hpp"

using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;

using Viewport = Microsoft::Console::Types::Viewport;

class ViewportTests
{
    TEST_CLASS(ViewportTests);

    TEST_METHOD(CreateEmpty)
    {
        const auto v = Viewport::Empty();

        VERIFY_ARE_EQUAL(0, v.Left());
        VERIFY_ARE_EQUAL(-1, v.RightInclusive());
        VERIFY_ARE_EQUAL(0, v.RightExclusive());
        VERIFY_ARE_EQUAL(0, v.Top());
        VERIFY_ARE_EQUAL(-1, v.BottomInclusive());
        VERIFY_ARE_EQUAL(0, v.BottomExclusive());
        VERIFY_ARE_EQUAL(0, v.Height());
        VERIFY_ARE_EQUAL(0, v.Width());
        VERIFY_ARE_EQUAL(COORD({ 0 }), v.Origin());
        VERIFY_ARE_EQUAL(COORD({ 0 }), v.Dimensions());
    }

    TEST_METHOD(CreateFromInclusive)
    {
        SMALL_RECT rect;
        rect.Top = 3;
        rect.Bottom = 5;
        rect.Left = 10;
        rect.Right = 20;

        COORD origin;
        origin.X = rect.Left;
        origin.Y = rect.Top;

        COORD dimensions;
        dimensions.X = rect.Right - rect.Left + 1;
        dimensions.Y = rect.Bottom - rect.Top + 1;

        const auto v = Viewport::FromInclusive(rect);

        VERIFY_ARE_EQUAL(rect.Left, v.Left());
        VERIFY_ARE_EQUAL(rect.Right, v.RightInclusive());
        VERIFY_ARE_EQUAL(rect.Right + 1, v.RightExclusive());
        VERIFY_ARE_EQUAL(rect.Top, v.Top());
        VERIFY_ARE_EQUAL(rect.Bottom, v.BottomInclusive());
        VERIFY_ARE_EQUAL(rect.Bottom + 1, v.BottomExclusive());
        VERIFY_ARE_EQUAL(dimensions.Y, v.Height());
        VERIFY_ARE_EQUAL(dimensions.X, v.Width());
        VERIFY_ARE_EQUAL(origin, v.Origin());
        VERIFY_ARE_EQUAL(dimensions, v.Dimensions());
    }

    TEST_METHOD(CreateFromExclusive)
    {
        SMALL_RECT rect;
        rect.Top = 3;
        rect.Bottom = 5;
        rect.Left = 10;
        rect.Right = 20;

        COORD origin;
        origin.X = rect.Left;
        origin.Y = rect.Top;

        COORD dimensions;
        dimensions.X = rect.Right - rect.Left;
        dimensions.Y = rect.Bottom - rect.Top;

        const auto v = Viewport::FromExclusive(rect);

        VERIFY_ARE_EQUAL(rect.Left, v.Left());
        VERIFY_ARE_EQUAL(rect.Right - 1, v.RightInclusive());
        VERIFY_ARE_EQUAL(rect.Right, v.RightExclusive());
        VERIFY_ARE_EQUAL(rect.Top, v.Top());
        VERIFY_ARE_EQUAL(rect.Bottom - 1, v.BottomInclusive());
        VERIFY_ARE_EQUAL(rect.Bottom, v.BottomExclusive());
        VERIFY_ARE_EQUAL(dimensions.Y, v.Height());
        VERIFY_ARE_EQUAL(dimensions.X, v.Width());
        VERIFY_ARE_EQUAL(origin, v.Origin());
        VERIFY_ARE_EQUAL(dimensions, v.Dimensions());
    }

    TEST_METHOD(CreateFromDimensionsWidthHeight)
    {
        SMALL_RECT rect;
        rect.Top = 3;
        rect.Bottom = 5;
        rect.Left = 10;
        rect.Right = 20;

        COORD origin;
        origin.X = rect.Left;
        origin.Y = rect.Top;

        COORD dimensions;
        dimensions.X = rect.Right - rect.Left + 1;
        dimensions.Y = rect.Bottom - rect.Top + 1;

        const auto v = Viewport::FromDimensions(origin, dimensions.X, dimensions.Y);

        VERIFY_ARE_EQUAL(rect.Left, v.Left());
        VERIFY_ARE_EQUAL(rect.Right, v.RightInclusive());
        VERIFY_ARE_EQUAL(rect.Right + 1, v.RightExclusive());
        VERIFY_ARE_EQUAL(rect.Top, v.Top());
        VERIFY_ARE_EQUAL(rect.Bottom, v.BottomInclusive());
        VERIFY_ARE_EQUAL(rect.Bottom + 1, v.BottomExclusive());
        VERIFY_ARE_EQUAL(dimensions.Y, v.Height());
        VERIFY_ARE_EQUAL(dimensions.X, v.Width());
        VERIFY_ARE_EQUAL(origin, v.Origin());
        VERIFY_ARE_EQUAL(dimensions, v.Dimensions());
    }

    TEST_METHOD(CreateFromDimensions)
    {
        SMALL_RECT rect;
        rect.Top = 3;
        rect.Bottom = 5;
        rect.Left = 10;
        rect.Right = 20;

        COORD origin;
        origin.X = rect.Left;
        origin.Y = rect.Top;

        COORD dimensions;
        dimensions.X = rect.Right - rect.Left + 1;
        dimensions.Y = rect.Bottom - rect.Top + 1;

        const auto v = Viewport::FromDimensions(origin, dimensions);

        VERIFY_ARE_EQUAL(rect.Left, v.Left());
        VERIFY_ARE_EQUAL(rect.Right, v.RightInclusive());
        VERIFY_ARE_EQUAL(rect.Right + 1, v.RightExclusive());
        VERIFY_ARE_EQUAL(rect.Top, v.Top());
        VERIFY_ARE_EQUAL(rect.Bottom, v.BottomInclusive());
        VERIFY_ARE_EQUAL(rect.Bottom + 1, v.BottomExclusive());
        VERIFY_ARE_EQUAL(dimensions.Y, v.Height());
        VERIFY_ARE_EQUAL(dimensions.X, v.Width());
        VERIFY_ARE_EQUAL(origin, v.Origin());
        VERIFY_ARE_EQUAL(dimensions, v.Dimensions());
    }

    TEST_METHOD(CreateFromDimensionsNoOrigin)
    {
        SMALL_RECT rect;
        rect.Top = 0;
        rect.Left = 0;
        rect.Bottom = 5;
        rect.Right = 20;

        COORD origin;
        origin.X = rect.Left;
        origin.Y = rect.Top;

        COORD dimensions;
        dimensions.X = rect.Right - rect.Left + 1;
        dimensions.Y = rect.Bottom - rect.Top + 1;

        const auto v = Viewport::FromDimensions(dimensions);

        VERIFY_ARE_EQUAL(rect.Left, v.Left());
        VERIFY_ARE_EQUAL(rect.Right, v.RightInclusive());
        VERIFY_ARE_EQUAL(rect.Right + 1, v.RightExclusive());
        VERIFY_ARE_EQUAL(rect.Top, v.Top());
        VERIFY_ARE_EQUAL(rect.Bottom, v.BottomInclusive());
        VERIFY_ARE_EQUAL(rect.Bottom + 1, v.BottomExclusive());
        VERIFY_ARE_EQUAL(dimensions.Y, v.Height());
        VERIFY_ARE_EQUAL(dimensions.X, v.Width());
        VERIFY_ARE_EQUAL(origin, v.Origin());
        VERIFY_ARE_EQUAL(dimensions, v.Dimensions());
    }

    TEST_METHOD(CreateFromCoord)
    {
        COORD origin;
        origin.X = 12;
        origin.Y = 24;

        const auto v = Viewport::FromCoord(origin);

        VERIFY_ARE_EQUAL(origin.X, v.Left());
        VERIFY_ARE_EQUAL(origin.X, v.RightInclusive());
        VERIFY_ARE_EQUAL(origin.X + 1, v.RightExclusive());
        VERIFY_ARE_EQUAL(origin.Y, v.Top());
        VERIFY_ARE_EQUAL(origin.Y, v.BottomInclusive());
        VERIFY_ARE_EQUAL(origin.Y + 1, v.BottomExclusive());
        VERIFY_ARE_EQUAL(1, v.Height());
        VERIFY_ARE_EQUAL(1, v.Width());
        VERIFY_ARE_EQUAL(origin, v.Origin());
        // clang-format off
        VERIFY_ARE_EQUAL(COORD({ 1, 1, }), v.Dimensions());
        // clang-format on
    }

    TEST_METHOD(ToRect)
    {
        COORD origin;
        origin.X = 2;
        origin.Y = 4;

        COORD dimensions;
        dimensions.X = 10;
        dimensions.Y = 20;

        const auto v = Viewport::FromDimensions(origin, dimensions);

        const RECT rc = v.ToRect();
        const SMALL_RECT exclusive = v.ToExclusive();

        VERIFY_ARE_EQUAL(exclusive.Left, v.Left());
        VERIFY_ARE_EQUAL(rc.left, v.Left());

        VERIFY_ARE_EQUAL(exclusive.Top, v.Top());
        VERIFY_ARE_EQUAL(rc.top, v.Top());

        VERIFY_ARE_EQUAL(exclusive.Right, v.RightExclusive());
        VERIFY_ARE_EQUAL(rc.right, v.RightExclusive());

        VERIFY_ARE_EQUAL(exclusive.Bottom, v.BottomExclusive());
        VERIFY_ARE_EQUAL(rc.bottom, v.BottomExclusive());
    }

    TEST_METHOD(IsInBoundsCoord)
    {
        SMALL_RECT r;
        r.Top = 3;
        r.Bottom = 5;
        r.Left = 10;
        r.Right = 20;

        const auto v = Viewport::FromInclusive(r);

        COORD c;
        c.X = r.Left;
        c.Y = r.Top;
        VERIFY_IS_TRUE(v.IsInBounds(c), L"Top left corner in bounds.");

        c.Y = r.Bottom;
        VERIFY_IS_TRUE(v.IsInBounds(c), L"Bottom left corner in bounds.");

        c.X = r.Right;
        VERIFY_IS_TRUE(v.IsInBounds(c), L"Bottom right corner in bounds.");

        c.Y = r.Top;
        VERIFY_IS_TRUE(v.IsInBounds(c), L"Top right corner in bounds.");

        c.X++;
        VERIFY_IS_FALSE(v.IsInBounds(c), L"One right out the top right is out of bounds.");

        c.X--;
        c.Y--;
        VERIFY_IS_FALSE(v.IsInBounds(c), L"One up out the top right is out of bounds.");

        c.X = r.Left;
        c.Y = r.Top;
        c.X--;
        VERIFY_IS_FALSE(v.IsInBounds(c), L"One left out the top left is out of bounds.");

        c.X++;
        c.Y--;
        VERIFY_IS_FALSE(v.IsInBounds(c), L"One up out the top left is out of bounds.");

        c.X = r.Left;
        c.Y = r.Bottom;
        c.X--;
        VERIFY_IS_FALSE(v.IsInBounds(c), L"One left out the bottom left is out of bounds.");

        c.X++;
        c.Y++;
        VERIFY_IS_FALSE(v.IsInBounds(c), L"One down out the bottom left is out of bounds.");

        c.X = r.Right;
        c.Y = r.Bottom;
        c.X++;
        VERIFY_IS_FALSE(v.IsInBounds(c), L"One right out the bottom right is out of bounds.");

        c.X--;
        c.Y++;
        VERIFY_IS_FALSE(v.IsInBounds(c), L"One down out the bottom right is out of bounds.");
    }

    TEST_METHOD(IsInBoundsViewport)
    {
        SMALL_RECT rect;
        rect.Top = 3;
        rect.Bottom = 5;
        rect.Left = 10;
        rect.Right = 20;

        const auto original = rect;

        const auto view = Viewport::FromInclusive(rect);

        auto test = Viewport::FromInclusive(rect);
        VERIFY_IS_TRUE(view.IsInBounds(test), L"Same size/position viewport is in bounds.");

        rect.Top++;
        rect.Bottom--;
        rect.Left++;
        rect.Right--;
        test = Viewport::FromInclusive(rect);
        VERIFY_IS_TRUE(view.IsInBounds(test), L"Viewport inscribed inside viewport is in bounds.");

        rect = original;
        rect.Top--;
        test = Viewport::FromInclusive(rect);
        VERIFY_IS_FALSE(view.IsInBounds(test), L"Viewport that is one taller upwards is out of bounds.");

        rect = original;
        rect.Bottom++;
        test = Viewport::FromInclusive(rect);
        VERIFY_IS_FALSE(view.IsInBounds(test), L"Viewport that is one taller downwards is out of bounds.");

        rect = original;
        rect.Left--;
        test = Viewport::FromInclusive(rect);
        VERIFY_IS_FALSE(view.IsInBounds(test), L"Viewport that is one wider leftwards is out of bounds.");

        rect = original;
        rect.Right++;
        test = Viewport::FromInclusive(rect);
        VERIFY_IS_FALSE(view.IsInBounds(test), L"Viewport that is one wider rightwards is out of bounds.");

        rect = original;
        rect.Left++;
        rect.Right++;
        rect.Top++;
        rect.Bottom++;
        test = Viewport::FromInclusive(rect);
        VERIFY_IS_FALSE(view.IsInBounds(test), L"Viewport offset at the origin but same size is out of bounds.");
    }

    TEST_METHOD(ClampCoord)
    {
        SMALL_RECT rect;
        rect.Top = 3;
        rect.Bottom = 5;
        rect.Left = 10;
        rect.Right = 20;

        const auto view = Viewport::FromInclusive(rect);

        COORD pos;
        pos.X = rect.Left;
        pos.Y = rect.Top;

        auto before = pos;
        view.Clamp(pos);
        VERIFY_ARE_EQUAL(before, pos, L"Verify clamp did nothing for position in top left corner.");

        pos.X = rect.Left;
        pos.Y = rect.Bottom;
        before = pos;
        view.Clamp(pos);
        VERIFY_ARE_EQUAL(before, pos, L"Verify clamp did nothing for position in bottom left corner.");

        pos.X = rect.Right;
        pos.Y = rect.Bottom;
        before = pos;
        view.Clamp(pos);
        VERIFY_ARE_EQUAL(before, pos, L"Verify clamp did nothing for position in bottom right corner.");

        pos.X = rect.Right;
        pos.Y = rect.Top;
        before = pos;
        view.Clamp(pos);
        VERIFY_ARE_EQUAL(before, pos, L"Verify clamp did nothing for position in top right corner.");

        COORD expected;
        expected.X = rect.Right;
        expected.Y = rect.Top;

        pos = expected;
        pos.X++;
        pos.Y--;
        before = pos;

        view.Clamp(pos);
        VERIFY_ARE_NOT_EQUAL(before, pos, L"Verify clamp modified position out the top right corner back.");
        VERIFY_ARE_EQUAL(expected, pos, L"Verify position was clamped into the top right corner.");

        expected.X = rect.Left;
        expected.Y = rect.Top;

        pos = expected;
        pos.X--;
        pos.Y--;
        before = pos;

        view.Clamp(pos);
        VERIFY_ARE_NOT_EQUAL(before, pos, L"Verify clamp modified position out the top left corner back.");
        VERIFY_ARE_EQUAL(expected, pos, L"Verify position was clamped into the top left corner.");

        expected.X = rect.Left;
        expected.Y = rect.Bottom;

        pos = expected;
        pos.X--;
        pos.Y++;
        before = pos;

        view.Clamp(pos);
        VERIFY_ARE_NOT_EQUAL(before, pos, L"Verify clamp modified position out the bottom left corner back.");
        VERIFY_ARE_EQUAL(expected, pos, L"Verify position was clamped into the bottom left corner.");

        expected.X = rect.Right;
        expected.Y = rect.Bottom;

        pos = expected;
        pos.X++;
        pos.Y++;
        before = pos;

        view.Clamp(pos);
        VERIFY_ARE_NOT_EQUAL(before, pos, L"Verify clamp modified position out the bottom right corner back.");
        VERIFY_ARE_EQUAL(expected, pos, L"Verify position was clamped into the bottom right corner.");

        Viewport invalidView = Viewport::Empty();
        VERIFY_THROWS_SPECIFIC(invalidView.Clamp(pos),
                               wil::ResultException,
                               [](wil::ResultException& e) { return e.GetErrorCode() == E_NOT_VALID_STATE; });
    }

    TEST_METHOD(ClampViewport)
    {
        // Create the rectangle/view we will clamp to.
        SMALL_RECT rect;
        rect.Top = 3;
        rect.Bottom = 5;
        rect.Left = 10;
        rect.Right = 20;

        const auto view = Viewport::FromInclusive(rect);

        Log::Comment(L"Make a rectangle that is larger than and fully encompasses our clamping rectangle.");
        SMALL_RECT testRect;
        testRect.Top = rect.Top - 3;
        testRect.Bottom = rect.Bottom + 3;
        testRect.Left = rect.Left - 3;
        testRect.Right = rect.Right + 3;

        auto testView = Viewport::FromInclusive(testRect);

        auto actual = view.Clamp(testView);
        VERIFY_ARE_EQUAL(view, actual, L"All sides should get reduced down to the size of the given rect.");

        Log::Comment(L"Make a rectangle that is fully inscribed inside our clamping rectangle.");
        testRect.Top = rect.Top + 1;
        testRect.Bottom = rect.Bottom - 1;
        testRect.Left = rect.Left + 1;
        testRect.Right = rect.Right - 1;
        testView = Viewport::FromInclusive(testRect);

        actual = view.Clamp(testView);
        VERIFY_ARE_EQUAL(testView, actual, L"Verify that nothing changed because this rectangle already sat fully inside the clamping rectangle.");

        Log::Comment(L"Craft a rectangle where the left is outside the right, right is outside the left, top is outside the bottom, and bottom is outside the top.");
        testRect.Top = rect.Bottom + 10;
        testRect.Bottom = rect.Top - 10;
        testRect.Left = rect.Right + 10;
        testRect.Right = rect.Left - 10;
        testView = Viewport::FromInclusive(testRect);

        Log::Comment(L"We expect it to be pulled back so each coordinate is in bounds, but the rectangle is still invalid (since left will be > right).");
        SMALL_RECT expected;
        expected.Top = rect.Bottom;
        expected.Bottom = rect.Top;
        expected.Left = rect.Right;
        expected.Right = rect.Left;
        const auto expectedView = Viewport::FromInclusive(expected);

        actual = view.Clamp(testView);
        VERIFY_ARE_EQUAL(expectedView, actual, L"Every dimension should be pulled just inside the clamping rectangle.");
    }

    TEST_METHOD(IncrementInBounds)
    {
        bool success = false;

        SMALL_RECT edges;
        edges.Left = 10;
        edges.Right = 19;
        edges.Top = 20;
        edges.Bottom = 29;

        const auto v = Viewport::FromInclusive(edges);
        COORD original;
        COORD screen;

        // #1 coord inside region
        original.X = screen.X = 15;
        original.Y = screen.Y = 25;

        success = v.IncrementInBounds(screen);

        VERIFY_IS_TRUE(success);
        VERIFY_ARE_EQUAL(screen.X, original.X + 1);
        VERIFY_ARE_EQUAL(screen.Y, original.Y);

        // #2 coord right edge, not bottom
        original.X = screen.X = edges.Right;
        original.Y = screen.Y = 25;

        success = v.IncrementInBounds(screen);

        VERIFY_IS_TRUE(success);
        VERIFY_ARE_EQUAL(screen.X, edges.Left);
        VERIFY_ARE_EQUAL(screen.Y, original.Y + 1);

        // #3 coord right edge, bottom
        original.X = screen.X = edges.Right;
        original.Y = screen.Y = edges.Bottom;

        success = v.IncrementInBounds(screen);

        VERIFY_IS_FALSE(success);
        VERIFY_ARE_EQUAL(screen.X, edges.Right);
        VERIFY_ARE_EQUAL(screen.Y, edges.Bottom);
    }

    TEST_METHOD(IncrementInBoundsCircular)
    {
        bool success = false;

        SMALL_RECT edges;
        edges.Left = 10;
        edges.Right = 19;
        edges.Top = 20;
        edges.Bottom = 29;

        const auto v = Viewport::FromInclusive(edges);
        COORD original;
        COORD screen;

        // #1 coord inside region
        original.X = screen.X = 15;
        original.Y = screen.Y = 25;

        success = v.IncrementInBoundsCircular(screen);

        VERIFY_IS_TRUE(success);
        VERIFY_ARE_EQUAL(screen.X, original.X + 1);
        VERIFY_ARE_EQUAL(screen.Y, original.Y);

        // #2 coord right edge, not bottom
        original.X = screen.X = edges.Right;
        original.Y = screen.Y = 25;

        success = v.IncrementInBoundsCircular(screen);

        VERIFY_IS_TRUE(success);
        VERIFY_ARE_EQUAL(screen.X, edges.Left);
        VERIFY_ARE_EQUAL(screen.Y, original.Y + 1);

        // #3 coord right edge, bottom
        original.X = screen.X = edges.Right;
        original.Y = screen.Y = edges.Bottom;

        success = v.IncrementInBoundsCircular(screen);

        VERIFY_IS_FALSE(success);
        VERIFY_ARE_EQUAL(screen.X, edges.Left);
        VERIFY_ARE_EQUAL(screen.Y, edges.Top);
    }

    TEST_METHOD(DecrementInBounds)
    {
        bool success = false;

        SMALL_RECT edges;
        edges.Left = 10;
        edges.Right = 19;
        edges.Top = 20;
        edges.Bottom = 29;

        const auto v = Viewport::FromInclusive(edges);
        COORD original;
        COORD screen;

        // #1 coord inside region
        original.X = screen.X = 15;
        original.Y = screen.Y = 25;

        success = v.DecrementInBounds(screen);

        VERIFY_IS_TRUE(success);
        VERIFY_ARE_EQUAL(screen.X, original.X - 1);
        VERIFY_ARE_EQUAL(screen.Y, original.Y);

        // #2 coord left edge, not top
        original.X = screen.X = edges.Left;
        original.Y = screen.Y = 25;

        success = v.DecrementInBounds(screen);

        VERIFY_IS_TRUE(success);
        VERIFY_ARE_EQUAL(screen.X, edges.Right);
        VERIFY_ARE_EQUAL(screen.Y, original.Y - 1);

        // #3 coord left edge, top
        original.X = screen.X = edges.Left;
        original.Y = screen.Y = edges.Top;

        success = v.DecrementInBounds(screen);

        VERIFY_IS_FALSE(success);
        VERIFY_ARE_EQUAL(screen.X, edges.Left);
        VERIFY_ARE_EQUAL(screen.Y, edges.Top);
    }

    TEST_METHOD(DecrementInBoundsCircular)
    {
        bool success = false;

        SMALL_RECT edges;
        edges.Left = 10;
        edges.Right = 19;
        edges.Top = 20;
        edges.Bottom = 29;

        const auto v = Viewport::FromInclusive(edges);
        COORD original;
        COORD screen;

        // #1 coord inside region
        original.X = screen.X = 15;
        original.Y = screen.Y = 25;

        success = v.DecrementInBoundsCircular(screen);

        VERIFY_IS_TRUE(success);
        VERIFY_ARE_EQUAL(screen.X, original.X - 1);
        VERIFY_ARE_EQUAL(screen.Y, original.Y);

        // #2 coord left edge, not top
        original.X = screen.X = edges.Left;
        original.Y = screen.Y = 25;

        success = v.DecrementInBoundsCircular(screen);

        VERIFY_IS_TRUE(success);
        VERIFY_ARE_EQUAL(screen.X, edges.Right);
        VERIFY_ARE_EQUAL(screen.Y, original.Y - 1);

        // #3 coord left edge, top
        original.X = screen.X = edges.Left;
        original.Y = screen.Y = edges.Top;

        success = v.DecrementInBoundsCircular(screen);

        VERIFY_IS_FALSE(success);
        VERIFY_ARE_EQUAL(screen.X, edges.Right);
        VERIFY_ARE_EQUAL(screen.Y, edges.Bottom);
    }

    SHORT RandomShort()
    {
        SHORT s;

        do
        {
            s = (SHORT)rand() % SHORT_MAX;
        } while (s == 0i16);

        return s;
    }

    TEST_METHOD(MoveInBounds)
    {
        const UINT cTestLoopInstances = 100;

        const SHORT sRowWidth = 20;
        VERIFY_IS_TRUE(sRowWidth > 0);

        // 20x20 box
        SMALL_RECT srectEdges;
        srectEdges.Top = srectEdges.Left = 0;
        srectEdges.Bottom = srectEdges.Right = sRowWidth - 1;

        const auto v = Viewport::FromInclusive(srectEdges);

        // repeat test
        for (UINT i = 0; i < cTestLoopInstances; i++)
        {
            COORD coordPos;
            coordPos.X = RandomShort() % 20;
            coordPos.Y = RandomShort() % 20;

            SHORT sAddAmount = RandomShort() % (sRowWidth * sRowWidth);

            COORD coordFinal;
            coordFinal.X = (coordPos.X + sAddAmount) % sRowWidth;
            coordFinal.Y = coordPos.Y + ((coordPos.X + sAddAmount) / sRowWidth);

            Log::Comment(String().Format(L"Add To Position: (%d, %d)  Amount to add: %d", coordPos.Y, coordPos.X, sAddAmount));

            // Movement result is expected to be true, unless there's an error.
            bool fExpectedResult = true;

            // if we've calculated past the final row, then the function will reset to the original position and the output will be false.
            if (coordFinal.Y >= sRowWidth)
            {
                coordFinal = coordPos;
                fExpectedResult = false;
            }

            bool const fActualResult = v.MoveInBounds(sAddAmount, coordPos);

            VERIFY_ARE_EQUAL(fExpectedResult, fActualResult);
            VERIFY_ARE_EQUAL(coordPos.X, coordFinal.X);
            VERIFY_ARE_EQUAL(coordPos.Y, coordFinal.Y);

            Log::Comment(String().Format(L"Actual: (%d, %d) Expected: (%d, %d)", coordPos.Y, coordPos.X, coordFinal.Y, coordFinal.X));
        }
    }

    TEST_METHOD(CompareInBounds)
    {
        SMALL_RECT edges;
        edges.Left = 10;
        edges.Right = 19;
        edges.Top = 20;
        edges.Bottom = 29;

        const auto v = Viewport::FromInclusive(edges);

        COORD first, second;
        first.X = 12;
        first.Y = 24;
        second = first;
        second.X += 2;

        VERIFY_ARE_EQUAL(-2, v.CompareInBounds(first, second), L"Second and first on same row. Second is right of first.");
        VERIFY_ARE_EQUAL(2, v.CompareInBounds(second, first), L"Reverse params, should get opposite direction, same magnitude.");

        first.X = edges.Left;
        first.Y = 24;

        second.X = edges.Right;
        second.Y = first.Y - 1;

        VERIFY_ARE_EQUAL(1, v.CompareInBounds(first, second), L"Second is up a line at the right edge from first at the line below on the left edge.");
        VERIFY_ARE_EQUAL(-1, v.CompareInBounds(second, first), L"Reverse params, should get opposite direction, same magnitude.");
    }

    TEST_METHOD(Offset)
    {
        SMALL_RECT edges;
        edges.Top = 0;
        edges.Left = 0;
        edges.Right = 10;
        edges.Bottom = 10;

        const auto original = Viewport::FromInclusive(edges);

        Log::Comment(L"Move down and to the right first.");
        COORD adjust = { 7, 2 };
        SMALL_RECT expectedEdges;
        expectedEdges.Top = edges.Top + adjust.Y;
        expectedEdges.Bottom = edges.Bottom + adjust.Y;
        expectedEdges.Left = edges.Left + adjust.X;
        expectedEdges.Right = edges.Right + adjust.X;

        Viewport expected = Viewport::FromInclusive(expectedEdges);

        Viewport actual = Viewport::Offset(original, adjust);
        VERIFY_ARE_EQUAL(expected, actual);

        Log::Comment(L"Now try moving up and to the left.");
        adjust = { -3, -5 };

        expectedEdges.Top = edges.Top + adjust.Y;
        expectedEdges.Bottom = edges.Bottom + adjust.Y;
        expectedEdges.Left = edges.Left + adjust.X;
        expectedEdges.Right = edges.Right + adjust.X;

        expected = Viewport::FromInclusive(expectedEdges);
        actual = Viewport::Offset(original, adjust);
        VERIFY_ARE_EQUAL(expected, actual);

        Log::Comment(L"Now try adding way too much to cause an overflow.");
        adjust = { SHORT_MAX, SHORT_MAX };

        VERIFY_THROWS_SPECIFIC(const auto vp = Viewport::Offset(original, adjust),
                               wil::ResultException,
                               [](wil::ResultException& e) { return e.GetErrorCode() == HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW); });
    }

    TEST_METHOD(Union)
    {
        SMALL_RECT srOne;
        srOne.Left = 4;
        srOne.Right = 10;
        srOne.Top = 6;
        srOne.Bottom = 14;
        const auto one = Viewport::FromInclusive(srOne);

        SMALL_RECT srTwo;
        srTwo.Left = 5;
        srTwo.Right = 13;
        srTwo.Top = 2;
        srTwo.Bottom = 10;
        const auto two = Viewport::FromInclusive(srTwo);

        SMALL_RECT srExpected;
        srExpected.Left = srOne.Left < srTwo.Left ? srOne.Left : srTwo.Left;
        srExpected.Right = srOne.Right > srTwo.Right ? srOne.Right : srTwo.Right;
        srExpected.Top = srOne.Top < srTwo.Top ? srOne.Top : srTwo.Top;
        srExpected.Bottom = srOne.Bottom > srTwo.Bottom ? srOne.Bottom : srTwo.Bottom;

        const auto expected = Viewport::FromInclusive(srExpected);

        const auto actual = Viewport::Union(one, two);
        VERIFY_ARE_EQUAL(expected, actual);
    }

    TEST_METHOD(Intersect)
    {
        SMALL_RECT srOne;
        srOne.Left = 4;
        srOne.Right = 10;
        srOne.Top = 6;
        srOne.Bottom = 14;
        const auto one = Viewport::FromInclusive(srOne);

        SMALL_RECT srTwo;
        srTwo.Left = 5;
        srTwo.Right = 13;
        srTwo.Top = 2;
        srTwo.Bottom = 10;
        const auto two = Viewport::FromInclusive(srTwo);

        SMALL_RECT srExpected;
        srExpected.Left = srOne.Left > srTwo.Left ? srOne.Left : srTwo.Left;
        srExpected.Right = srOne.Right < srTwo.Right ? srOne.Right : srTwo.Right;
        srExpected.Top = srOne.Top > srTwo.Top ? srOne.Top : srTwo.Top;
        srExpected.Bottom = srOne.Bottom < srTwo.Bottom ? srOne.Bottom : srTwo.Bottom;

        const auto expected = Viewport::FromInclusive(srExpected);

        const auto actual = Viewport::Intersect(one, two);
        VERIFY_ARE_EQUAL(expected, actual);
    }

    TEST_METHOD(SubtractFour)
    {
        SMALL_RECT srOriginal;
        srOriginal.Top = 0;
        srOriginal.Left = 0;
        srOriginal.Bottom = 10;
        srOriginal.Right = 10;
        const auto original = Viewport::FromInclusive(srOriginal);

        SMALL_RECT srRemove;
        srRemove.Top = 3;
        srRemove.Left = 3;
        srRemove.Bottom = 6;
        srRemove.Right = 6;
        const auto remove = Viewport::FromInclusive(srRemove);

        std::vector<Viewport> expected;
        // SMALL_RECT constructed as: Left, Top, Right, Bottom
        // Top View
        expected.emplace_back(Viewport::FromInclusive({ srOriginal.Left, srOriginal.Top, srOriginal.Right, srRemove.Top - 1 }));
        // Bottom View
        expected.emplace_back(Viewport::FromInclusive({ srOriginal.Left, srRemove.Bottom + 1, srOriginal.Right, srOriginal.Bottom }));
        // Left View
        expected.emplace_back(Viewport::FromInclusive({ srOriginal.Left, srRemove.Top, srRemove.Left - 1, srRemove.Bottom }));
        // Right View
        expected.emplace_back(Viewport::FromInclusive({ srRemove.Right + 1, srRemove.Top, srOriginal.Right, srRemove.Bottom }));

        const auto actual = Viewport::Subtract(original, remove);

        VERIFY_ARE_EQUAL(expected.size(), actual.size(), L"Same number of viewports in expected and actual");
        Log::Comment(L"Now validate that each viewport has the expected area.");
        for (size_t i = 0; i < expected.size(); i++)
        {
            const auto& exp = expected.at(i);
            const auto& act = actual.at(i);
            VERIFY_ARE_EQUAL(exp, act);
        }
    }

    TEST_METHOD(SubtractThree)
    {
        SMALL_RECT srOriginal;
        srOriginal.Top = 0;
        srOriginal.Left = 0;
        srOriginal.Bottom = 10;
        srOriginal.Right = 10;
        const auto original = Viewport::FromInclusive(srOriginal);

        SMALL_RECT srRemove;
        srRemove.Top = 3;
        srRemove.Left = 3;
        srRemove.Bottom = 6;
        srRemove.Right = 15;
        const auto remove = Viewport::FromInclusive(srRemove);

        std::vector<Viewport> expected;
        // SMALL_RECT constructed as: Left, Top, Right, Bottom
        // Top View
        expected.emplace_back(Viewport::FromInclusive({ srOriginal.Left, srOriginal.Top, srOriginal.Right, srRemove.Top - 1 }));
        // Bottom View
        expected.emplace_back(Viewport::FromInclusive({ srOriginal.Left, srRemove.Bottom + 1, srOriginal.Right, srOriginal.Bottom }));
        // Left View
        expected.emplace_back(Viewport::FromInclusive({ srOriginal.Left, srRemove.Top, srRemove.Left - 1, srRemove.Bottom }));

        const auto actual = Viewport::Subtract(original, remove);

        VERIFY_ARE_EQUAL(expected.size(), actual.size(), L"Same number of viewports in expected and actual");
        Log::Comment(L"Now validate that each viewport has the expected area.");
        for (size_t i = 0; i < expected.size(); i++)
        {
            const auto& exp = expected.at(i);
            const auto& act = actual.at(i);
            VERIFY_ARE_EQUAL(exp, act);
        }
    }

    TEST_METHOD(SubtractTwo)
    {
        SMALL_RECT srOriginal;
        srOriginal.Top = 0;
        srOriginal.Left = 0;
        srOriginal.Bottom = 10;
        srOriginal.Right = 10;
        const auto original = Viewport::FromInclusive(srOriginal);

        SMALL_RECT srRemove;
        srRemove.Top = 3;
        srRemove.Left = 3;
        srRemove.Bottom = 15;
        srRemove.Right = 15;
        const auto remove = Viewport::FromInclusive(srRemove);

        std::vector<Viewport> expected;
        // SMALL_RECT constructed as: Left, Top, Right, Bottom
        // Top View
        expected.emplace_back(Viewport::FromInclusive({ srOriginal.Left, srOriginal.Top, srOriginal.Right, srRemove.Top - 1 }));
        // Left View
        expected.emplace_back(Viewport::FromInclusive({ srOriginal.Left, srRemove.Top, srRemove.Left - 1, srOriginal.Bottom }));

        const auto actual = Viewport::Subtract(original, remove);

        VERIFY_ARE_EQUAL(expected.size(), actual.size(), L"Same number of viewports in expected and actual");
        Log::Comment(L"Now validate that each viewport has the expected area.");
        for (size_t i = 0; i < expected.size(); i++)
        {
            const auto& exp = expected.at(i);
            const auto& act = actual.at(i);
            VERIFY_ARE_EQUAL(exp, act);
        }
    }

    TEST_METHOD(SubtractOne)
    {
        SMALL_RECT srOriginal;
        srOriginal.Top = 0;
        srOriginal.Left = 0;
        srOriginal.Bottom = 10;
        srOriginal.Right = 10;
        const auto original = Viewport::FromInclusive(srOriginal);

        SMALL_RECT srRemove;
        srRemove.Top = 3;
        srRemove.Left = -12;
        srRemove.Bottom = 15;
        srRemove.Right = 15;
        const auto remove = Viewport::FromInclusive(srRemove);

        std::vector<Viewport> expected;
        // SMALL_RECT constructed as: Left, Top, Right, Bottom
        // Top View
        expected.emplace_back(Viewport::FromInclusive({ srOriginal.Left, srOriginal.Top, srOriginal.Right, srRemove.Top - 1 }));

        const auto foo = expected.cbegin();

        const auto actual = Viewport::Subtract(original, remove);

        VERIFY_ARE_EQUAL(expected.size(), actual.size(), L"Same number of viewports in expected and actual");
        Log::Comment(L"Now validate that each viewport has the expected area.");
        for (size_t i = 0; i < expected.size(); i++)
        {
            const auto& exp = expected.at(i);
            const auto& act = actual.at(i);
            VERIFY_ARE_EQUAL(exp, act);
        }
    }

    TEST_METHOD(SubtractZero)
    {
        SMALL_RECT srOriginal;
        srOriginal.Top = 0;
        srOriginal.Left = 0;
        srOriginal.Bottom = 10;
        srOriginal.Right = 10;
        const auto original = Viewport::FromInclusive(srOriginal);

        SMALL_RECT srRemove;
        srRemove.Top = 12;
        srRemove.Left = 12;
        srRemove.Bottom = 15;
        srRemove.Right = 15;
        const auto remove = Viewport::FromInclusive(srRemove);

        std::vector<Viewport> expected;
        expected.emplace_back(original);

        const auto actual = Viewport::Subtract(original, remove);

        VERIFY_ARE_EQUAL(expected.size(), actual.size(), L"Same number of viewports in expected and actual");
        Log::Comment(L"Now validate that each viewport has the expected area.");
        for (size_t i = 0; i < expected.size(); i++)
        {
            const auto& exp = expected.at(i);
            const auto& act = actual.at(i);
            VERIFY_ARE_EQUAL(exp, act);
        }
    }

    TEST_METHOD(SubtractSame)
    {
        SMALL_RECT srOriginal;
        srOriginal.Top = 0;
        srOriginal.Left = 0;
        srOriginal.Bottom = 10;
        srOriginal.Right = 10;
        const auto original = Viewport::FromInclusive(srOriginal);
        const auto remove = original;

        const auto actual = Viewport::Subtract(original, remove);

        VERIFY_ARE_EQUAL(0u, actual.size(), L"There should be no viewports returned");
    }
};
