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

using System.Web.Razor.Editor;
using System.Web.Razor.Parser;
using System.Web.Razor.Parser.SyntaxTree;
using System.Web.Razor.Resources;
using System.Web.Razor.Test.Framework;
using System.Web.Razor.Text;
using Xunit;
using Xunit.Extensions;

namespace System.Web.Razor.Test.Parser.VB
{
    public class VBErrorTest : VBHtmlCodeParserTestBase
    {
        [Fact]
        public void ParserOutputsErrorAndRecoversToEndOfLineIfExplicitExpressionUnterminated()
        {
            ParseBlockTest(@"(foo
bar",
                new ExpressionBlock(
                    Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
                    Factory.Code("foo").AsExpression()),
                new RazorError(
                    String.Format(
                        RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
                        RazorResources.BlockName_ExplicitExpression,
                        ")", "("),
                    SourceLocation.Zero));
        }

        [Fact]
        public void ParserOutputsZeroLengthCodeSpanIfEofReachedAfterStartOfExplicitExpression()
        {
            ParseBlockTest("(",
                new ExpressionBlock(
                    Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
                    Factory.EmptyVB().AsExpression()),
                new RazorError(
                    String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "explicit expression", ")", "("),
                    SourceLocation.Zero));
        }

        [Fact]
        public void ParserOutputsZeroLengthCodeSpanIfEofReachedAfterAtSign()
        {
            ParseBlockTest(String.Empty,
                new ExpressionBlock(
                    Factory.EmptyVB().AsImplicitExpression(KeywordSet).Accepts(AcceptedCharacters.NonWhiteSpace)),
                new RazorError(
                    RazorResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock,
                    SourceLocation.Zero));
        }

        [Fact]
        public void ParserOutputsZeroLengthCodeSpanIfOnlyWhitespaceFoundAfterAtSign()
        {
            ParseBlockTest(" ",
                new ExpressionBlock(
                    Factory.EmptyVB().AsImplicitExpression(KeywordSet).Accepts(AcceptedCharacters.NonWhiteSpace)),
                new RazorError(
                    RazorResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_VB,
                    SourceLocation.Zero));
        }

        [Fact]
        public void ParserOutputsZeroLengthCodeSpanIfInvalidCharacterFoundAfterAtSign()
        {
            ParseBlockTest("!!!",
                new ExpressionBlock(
                    Factory.EmptyVB().AsImplicitExpression(KeywordSet).Accepts(AcceptedCharacters.NonWhiteSpace)),
                new RazorError(
                    String.Format(RazorResources.ParseError_Unexpected_Character_At_Start_Of_CodeBlock_VB, "!"),
                    SourceLocation.Zero));
        }

        [Theory]
        [InlineData("Code", "End Code", true, true)]
        [InlineData("Do", "Loop", false, false)]
        [InlineData("While", "End While", false, false)]
        [InlineData("If", "End If", false, false)]
        [InlineData("Select Case", "End Select", false, false)]
        [InlineData("For", "Next", false, false)]
        [InlineData("Try", "End Try", false, false)]
        [InlineData("With", "End With", false, false)]
        [InlineData("Using", "End Using", false, false)]
        public void EofBlock(string keyword, string expectedTerminator, bool autoComplete, bool keywordIsMetaCode)
        {
            EofBlockCore(keyword, expectedTerminator, autoComplete, BlockType.Statement, keywordIsMetaCode, c => c.AsStatement());
        }

        [Fact]
        public void EofFunctionsBlock()
        {
            EofBlockCore("Functions", "End Functions", true, BlockType.Functions, true, c => c.AsFunctionsBody());
        }

        private void EofBlockCore(string keyword, string expectedTerminator, bool autoComplete, BlockType blockType, bool keywordIsMetaCode, Func<UnclassifiedCodeSpanConstructor, SpanConstructor> classifier)
        {
            BlockBuilder expected = new BlockBuilder();
            expected.Type = blockType;
            if (keywordIsMetaCode)
            {
                expected.Children.Add(Factory.MetaCode(keyword).Accepts(AcceptedCharacters.None));
                expected.Children.Add(
                classifier(Factory.EmptyVB())
                       .With((SpanEditHandler)(
                            autoComplete ?
                                new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = expectedTerminator } :
                                SpanEditHandler.CreateDefault())));
            }
            else
            {
                expected.Children.Add(
                    classifier(Factory.Code(keyword))
                           .With((SpanEditHandler)(
                                autoComplete ?
                                    new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = expectedTerminator } :
                                    SpanEditHandler.CreateDefault())));
            }

            ParseBlockTest(keyword,
                expected.Build(),
                new RazorError(
                    String.Format(RazorResources.ParseError_BlockNotTerminated, keyword, expectedTerminator),
                    SourceLocation.Zero));
        }

        [Theory]
        [InlineData("Code", "End Code", true)]
        [InlineData("Do", "Loop", false)]
        [InlineData("While", "End While", false)]
        [InlineData("If", "End If", false)]
        [InlineData("Select Case", "End Select", false)]
        [InlineData("For", "Next", false)]
        [InlineData("Try", "End Try", false)]
        [InlineData("With", "End With", false)]
        [InlineData("Using", "End Using", false)]
        public void UnterminatedBlock(string keyword, string expectedTerminator, bool keywordIsMetaCode)
        {
            UnterminatedBlockCore(keyword, expectedTerminator, BlockType.Statement, keywordIsMetaCode, c => c.AsStatement());
        }

        [Fact]
        public void UnterminatedFunctionsBlock()
        {
            UnterminatedBlockCore("Functions", "End Functions", BlockType.Functions, true, c => c.AsFunctionsBody());
        }

        private void UnterminatedBlockCore(string keyword, string expectedTerminator, BlockType blockType, bool keywordIsMetaCode, Func<UnclassifiedCodeSpanConstructor, SpanConstructor> classifier)
        {
            const string blockBody = @"
    ' This block is not correctly terminated!";

            BlockBuilder expected = new BlockBuilder();
            expected.Type = blockType;
            if (keywordIsMetaCode)
            {
                expected.Children.Add(Factory.MetaCode(keyword).Accepts(AcceptedCharacters.None));
                expected.Children.Add(classifier(Factory.Code(blockBody)));
            }
            else
            {
                expected.Children.Add(classifier(Factory.Code(keyword + blockBody)));
            }

            ParseBlockTest(keyword + blockBody,
                expected.Build(),
                new RazorError(
                    String.Format(RazorResources.ParseError_BlockNotTerminated, keyword, expectedTerminator),
                    SourceLocation.Zero));
        }
    }
}
