// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.

using System.IO;
using System.Web.Razor.Text;
using Xunit;
using Assert = Microsoft.TestCommon.AssertEx;

namespace System.Web.Razor.Test.Text
{
    public class BufferingTextReaderTest : LookaheadTextReaderTestBase
    {
        private const string TestString = "abcdefg";

        private class DisposeTestMockTextReader : TextReader
        {
            public bool Disposed { get; set; }

            protected override void Dispose(bool disposing)
            {
                base.Dispose(disposing);
                Disposed = true;
            }
        }

        protected override LookaheadTextReader CreateReader(string testString)
        {
            return new BufferingTextReader(new StringReader(testString));
        }

        [Fact]
        public void ConstructorRequiresNonNullSourceReader()
        {
            Assert.ThrowsArgumentNull(() => new BufferingTextReader(null), "source");
        }

        [Fact]
        public void PeekReturnsCurrentCharacterWithoutAdvancingPosition()
        {
            RunPeekTest("abc", peekAt: 2);
        }

        [Fact]
        public void PeekReturnsNegativeOneAtEndOfSourceReader()
        {
            RunPeekTest("abc", peekAt: 3);
        }

        [Fact]
        public void ReadReturnsCurrentCharacterAndAdvancesToNextCharacter()
        {
            RunReadTest("abc", readAt: 2);
        }

        [Fact]
        public void EndingLookaheadReturnsReaderToPreviousLocation()
        {
            RunLookaheadTest("abcdefg", "abcb",
                             Read,
                             Lookahead(
                                 Read,
                                 Read),
                             Read);
        }

        [Fact]
        public void MultipleLookaheadsCanBePerformed()
        {
            RunLookaheadTest("abcdefg", "abcbcdc",
                             Read,
                             Lookahead(
                                 Read,
                                 Read),
                             Read,
                             Lookahead(
                                 Read,
                                 Read),
                             Read);
        }

        [Fact]
        public void LookaheadsCanBeNested()
        {
            RunLookaheadTest("abcdefg", "abcdefebc",
                             Read, // Appended: "a" Reader: "bcdefg"
                             Lookahead( // Reader: "bcdefg"
                                 Read, // Appended: "b" Reader: "cdefg";
                                 Read, // Appended: "c" Reader: "defg";
                                 Read, // Appended: "d" Reader: "efg";
                                 Lookahead( // Reader: "efg"
                                     Read, // Appended: "e" Reader: "fg";
                                     Read // Appended: "f" Reader: "g";
                                     ), // Reader: "efg"
                                 Read // Appended: "e" Reader: "fg";
                                 ), // Reader: "bcdefg"
                             Read, // Appended: "b" Reader: "cdefg";
                             Read); // Appended: "c" Reader: "defg";
        }

        [Fact]
        public void SourceLocationIsZeroWhenInitialized()
        {
            RunSourceLocationTest("abcdefg", SourceLocation.Zero, checkAt: 0);
        }

        [Fact]
        public void CharacterAndAbsoluteIndicesIncreaseAsCharactersAreRead()
        {
            RunSourceLocationTest("abcdefg", new SourceLocation(4, 0, 4), checkAt: 4);
        }

        [Fact]
        public void CharacterAndAbsoluteIndicesIncreaseAsSlashRInTwoCharacterNewlineIsRead()
        {
            RunSourceLocationTest("f\r\nb", new SourceLocation(2, 0, 2), checkAt: 2);
        }

        [Fact]
        public void CharacterIndexResetsToZeroAndLineIndexIncrementsWhenSlashNInTwoCharacterNewlineIsRead()
        {
            RunSourceLocationTest("f\r\nb", new SourceLocation(3, 1, 0), checkAt: 3);
        }

        [Fact]
        public void CharacterIndexResetsToZeroAndLineIndexIncrementsWhenSlashRInSingleCharacterNewlineIsRead()
        {
            RunSourceLocationTest("f\rb", new SourceLocation(2, 1, 0), checkAt: 2);
        }

        [Fact]
        public void CharacterIndexResetsToZeroAndLineIndexIncrementsWhenSlashNInSingleCharacterNewlineIsRead()
        {
            RunSourceLocationTest("f\nb", new SourceLocation(2, 1, 0), checkAt: 2);
        }

        [Fact]
        public void EndingLookaheadResetsRawCharacterAndLineIndexToValuesWhenLookaheadBegan()
        {
            RunEndLookaheadUpdatesSourceLocationTest();
        }

        [Fact]
        public void OnceBufferingBeginsReadsCanContinuePastEndOfBuffer()
        {
            RunLookaheadTest("abcdefg", "abcbcdefg",
                             Read,
                             Lookahead(Read(2)),
                             Read(2),
                             ReadToEnd);
        }

        [Fact]
        public void DisposeDisposesSourceReader()
        {
            RunDisposeTest(r => r.Dispose());
        }

        [Fact]
        public void CloseDisposesSourceReader()
        {
            RunDisposeTest(r => r.Close());
        }

        [Fact]
        public void ReadWithBufferSupportsLookahead()
        {
            RunBufferReadTest((reader, buffer, index, count) => reader.Read(buffer, index, count));
        }

        [Fact]
        public void ReadBlockSupportsLookahead()
        {
            RunBufferReadTest((reader, buffer, index, count) => reader.ReadBlock(buffer, index, count));
        }

        [Fact]
        public void ReadLineSupportsLookahead()
        {
            RunReadUntilTest(r => r.ReadLine(), expectedRaw: 8, expectedChar: 0, expectedLine: 2);
        }

        [Fact]
        public void ReadToEndSupportsLookahead()
        {
            RunReadUntilTest(r => r.ReadToEnd(), expectedRaw: 11, expectedChar: 3, expectedLine: 2);
        }

        [Fact]
        public void ReadLineMaintainsCorrectCharacterPosition()
        {
            RunSourceLocationTest("abc\r\ndef", new SourceLocation(5, 1, 0), r => r.ReadLine());
        }

        [Fact]
        public void ReadToEndWorksAsInNormalTextReader()
        {
            RunReadToEndTest();
        }

        [Fact]
        public void CancelBacktrackStopsNextEndLookaheadFromBacktracking()
        {
            RunLookaheadTest("abcdefg", "abcdefg",
                             Lookahead(
                                 Read(2),
                                 CancelBacktrack
                                 ),
                             ReadToEnd);
        }

        [Fact]
        public void CancelBacktrackThrowsInvalidOperationExceptionIfCalledOutsideOfLookahead()
        {
            RunCancelBacktrackOutsideLookaheadTest();
        }

        [Fact]
        public void CancelBacktrackOnlyCancelsBacktrackingForInnermostNestedLookahead()
        {
            RunLookaheadTest("abcdefg", "abcdabcdefg",
                             Lookahead(
                                 Read(2),
                                 Lookahead(
                                     Read,
                                     CancelBacktrack
                                     ),
                                 Read
                                 ),
                             ReadToEnd);
        }

        [Fact]
        public void BacktrackBufferIsClearedWhenEndReachedAndNoCurrentLookaheads()
        {
            // Arrange
            StringReader source = new StringReader(TestString);
            BufferingTextReader reader = new BufferingTextReader(source);

            reader.Read(); // Reader: "bcdefg"
            using (reader.BeginLookahead())
            {
                reader.Read(); // Reader: "cdefg"
            } // Reader: "bcdefg"
            reader.Read(); // Reader: "cdefg"
            Assert.NotNull(reader.Buffer); // Verify our assumption that the buffer still exists

            // Act
            reader.Read();

            // Assert
            Assert.False(reader.Buffering, "The buffer was not reset when the end was reached");
            Assert.Equal(0, reader.Buffer.Length);
        }

        private static void RunDisposeTest(Action<LookaheadTextReader> triggerAction)
        {
            // Arrange
            DisposeTestMockTextReader source = new DisposeTestMockTextReader();
            LookaheadTextReader reader = new BufferingTextReader(source);

            // Act
            triggerAction(reader);

            // Assert
            Assert.True(source.Disposed);
        }
    }
}