// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Generator.CSharp.Expressions;
using NUnit.Framework;
using static Microsoft.Generator.CSharp.Expressions.Snippets;

namespace Microsoft.Generator.CSharp.Tests
{
    public class CodeWriterTests
    {
        internal const string NewLine = "\n";
        private CodeWriter _codeWriter = null!;
        private readonly string _mocksFolder = "mocks";
        private readonly string _autoGenerated = "// <auto-generated/>";
        private readonly string _nullableDisable = "#nullable disable";
        private readonly string _licenseString = "License string";
        private string? _header;

        [SetUp]
        public void Setup()
        {
            _codeWriter = new CodeWriter();

            _header = new StringBuilder()
                .Append("// ")
                .Append(_licenseString).Append(NewLine)
                .Append(NewLine)
                .Append(_autoGenerated).Append(NewLine)
                .Append(NewLine)
                .Append(_nullableDisable).Append(NewLine)
                .Append(NewLine)
                .ToString();

            var configFilePath = Path.Combine(AppContext.BaseDirectory, _mocksFolder);
            // initialize the singleton instance of the plugin
            _ = new MockCodeModelPlugin(new GeneratorContext(Configuration.Load(configFilePath)));
        }

        [Test]
        public void GeneratesNewNamesInChildScope()
        {
            var cwd1 = new CodeWriterDeclaration("a");
            var cwd2 = new CodeWriterDeclaration("a");
            _codeWriter.WriteLine($"{cwd1:D}");
            using (_codeWriter.Scope())
            {
                _codeWriter.WriteLine($"{cwd2:D}");
            }

            var sb = new StringBuilder();
            sb.Append(_header);
            sb.Append("a").Append(NewLine);
            sb.Append("{").Append(NewLine);
            sb.Append("a0").Append(NewLine);
            sb.Append("}").Append(NewLine);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }

        [Test]
        public void ScopeLineIsInsideScope()
        {
            var cwd1 = new CodeWriterDeclaration("a");
            var cwd2 = new CodeWriterDeclaration("a");
            using (_codeWriter.Scope($"{cwd1:D}"))
            {
            }

            using (_codeWriter.Scope($"{cwd2:D}"))
            {
            }

            var sb = new StringBuilder();
            sb.Append(_header);
            sb.Append("a").Append(NewLine);
            sb.Append("{").Append(NewLine);
            sb.Append("}").Append(NewLine);
            sb.Append("a").Append(NewLine);
            sb.Append("{").Append(NewLine);
            sb.Append("}").Append(NewLine);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }

        [Test]
        public void VariableNameNotReusedWhenUsedInChildScope()
        {
            var cwd1 = new CodeWriterDeclaration("a");
            var cwd2 = new CodeWriterDeclaration("a");
            using (_codeWriter.Scope())
            {
                _codeWriter.WriteLine($"{cwd1:D}");
            }

            _codeWriter.WriteLine($"{cwd2:D}");

            var sb = new StringBuilder();
            sb.Append(_header);
            sb.Append("{").Append(NewLine);
            sb.Append("a").Append(NewLine);
            sb.Append("}").Append(NewLine);
            sb.Append("a0").Append(NewLine);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }

        [Test]
        public void CorrectlyHandlesCurlyBraces()
        {
            _codeWriter.Append($"public {typeof(string)} Data {{ get; private set; }}");
            var expected = "public string Data { get; private set; }" + NewLine;
            var sb = new StringBuilder();
            sb.Append(_header);
            sb.Append(expected);

            Assert.AreEqual(sb.ToString(),
                _codeWriter.ToString());
        }

        [Test]
        public void FormatInFormat()
        {
            FormattableString fs1 = $"'1' is {typeof(int)}";
            FormattableString fs2 = $"'a' is {typeof(char)} and {fs1} and 'true' is {typeof(bool)}";

            _codeWriter.Append(fs2);
            var expected = "'a' is char and '1' is int and 'true' is bool" + NewLine;
            var sb = new StringBuilder();
            sb.Append(_header);
            sb.Append(expected);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }


        [Test]
        public void EnumerableFormatInFormat()
        {
            _codeWriter.Append($"Multiply:{Enumerable.Range(1, 4).Select(i => (FormattableString)$" {i} * 2 = {i * 2};")}");
            var expected = "Multiply: 1 * 2 = 2; 2 * 2 = 4; 3 * 2 = 6; 4 * 2 = 8;" + NewLine;
            var sb = new StringBuilder();
            sb.Append(_header);
            sb.Append(expected);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }

        [Test]
        public void SingleLineSummary()
        {
            _codeWriter.WriteXmlDocumentationSummary($"Some {typeof(string)} summary.");
            var expected = "/// <summary> Some string summary. </summary>" + NewLine;
            var sb = new StringBuilder();
            sb.Append(_header);
            sb.Append(expected);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }

        [Test]
        public void NoEmptySummary()
        {
            _codeWriter.WriteXmlDocumentationSummary($"{string.Empty}");
            var expected = string.Empty;
            var sb = new StringBuilder();
            sb.Append(expected);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }

        [TestCase(typeof(string), false, "<see cref=\"string\"/>", "")]
        [TestCase(typeof(int), false, "<see cref=\"int\"/>", "")]
        [TestCase(typeof(int), true, "<see cref=\"int\"/>?", "")]
        [TestCase(typeof(List<>), false, "<see cref=\"global::System.Collections.Generic.List{T}\"/>", "System.Collections.Generic")]
        [TestCase(typeof(KeyValuePair<,>), false, "<see cref=\"global::System.Collections.Generic.KeyValuePair{TKey,TValue}\"/>", "System.Collections.Generic")]
        [TestCase(typeof(KeyValuePair<int, string>), true, "<see cref=\"global::System.Collections.Generic.KeyValuePair{TKey,TValue}\"/>? where <c>TKey</c> is of type <see cref=\"int\"/>, where <c>TValue</c> is of type <see cref=\"string\"/>", "System.Collections.Generic")]
        public void SeeCRefType(Type type, bool isNullable, string expectedWritten, string ns)
        {
            var csType = new CSharpType(type).WithNullable(isNullable);
            _codeWriter.WriteXmlDocumentationSummary($"Some {csType:C} summary.");
            var expected = $"/// <summary> Some {expectedWritten} summary. </summary>" + NewLine;
            var sb = new StringBuilder();
            sb.Append(_header);
            if (!string.IsNullOrEmpty(ns))
            {
                sb.Append("using ");
                sb.Append(ns);
                sb.Append(";").Append(NewLine);
                sb.Append(NewLine);
            }
            sb.Append(expected);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }

        [Test]
        public void MultiLineSummary()
        {
            FormattableString fs1 = $@"L04
L05
L06 {typeof(int)}


L09";
            FormattableString fs2 = $@"

L11 {typeof(bool)}
L12

";
            IEnumerable<FormattableString> fss = new[] { fs1, fs2 };
            FormattableString fs = $@"L00
L01
L02 {typeof(string)}

{fss}
L15
L16";
            _codeWriter.WriteXmlDocumentationSummary(fs);

            var sb = new StringBuilder();
            sb.Append(_header);
            sb.Append("/// <summary>").Append(NewLine);
            sb.Append("/// L00").Append(NewLine);
            sb.Append("/// L01").Append(NewLine);
            sb.Append("/// L02 string").Append(NewLine);
            sb.Append("///").Append(NewLine);
            sb.Append("/// L04").Append(NewLine);
            sb.Append("/// L05").Append(NewLine);
            sb.Append("/// L06 int").Append(NewLine);
            sb.Append("///").Append(NewLine);
            sb.Append("///").Append(NewLine);
            sb.Append("/// L09").Append(NewLine);
            sb.Append("///").Append(NewLine);
            sb.Append("/// L11 bool").Append(NewLine);
            sb.Append("/// L12").Append(NewLine);
            sb.Append("///").Append(NewLine);
            sb.Append("///").Append(NewLine);
            sb.Append("/// L15").Append(NewLine);
            sb.Append("/// L16").Append(NewLine);
            sb.Append("/// </summary>").Append(NewLine);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }

        // Validate that the WriteMethodDeclarationNoScope method correctly writes a custom constructor signature method
        [Test]
        public void TestWriteMethodDeclarationNoScope_ConstructorSignature()
        {
            var baseInitializerStatement = new ConstructorInitializer(true, new List<ValueExpression> { new StringLiteralExpression("test", false) });
            var constructorSignature = new ConstructorSignature(new CSharpType(typeof(string)), $"Test constructor summary", $"Test description",
                MethodSignatureModifiers.Public, Array.Empty<Parameter>(), null, baseInitializerStatement);
            var codeWriter = new CodeWriter();
            codeWriter.WriteMethodDeclarationNoScope(constructorSignature);

            var expected = new StringBuilder()
               .Append(_header)
               .Append("public String(): base(\"test\")").Append(NewLine)
               .ToString();
            var result = codeWriter.ToString();
            Assert.AreEqual(expected, result);
        }

        [Test]
        public void CodeWriter_WriteField()
        {
            var field1 = new FieldDeclaration($"To test int", FieldModifiers.Private, typeof(int), "_intConst");
            var field2 = new FieldDeclaration($"To test string", FieldModifiers.Private | FieldModifiers.Static | FieldModifiers.ReadOnly, typeof(string), "_stringValue");
            var field3 = new FieldDeclaration($"To test a field with initialization value", FieldModifiers.Private | FieldModifiers.Static | FieldModifiers.ReadOnly, typeof(string), "withValue")
            {
                InitializationValue = Literal("abc")
            };

            var codeWriter = new CodeWriter();
            codeWriter.WriteField(field1);
            codeWriter.Append($"// test comment");
            codeWriter.WriteField(field2);
            codeWriter.WriteField(field3);

            var expected = new StringBuilder()
                .Append(_header)
                .Append("/// <summary> To test int. </summary>").Append(NewLine)
                .Append("private int _intConst;").Append(NewLine)
                .Append("// test comment").Append(NewLine)
                .Append("/// <summary> To test string. </summary>").Append(NewLine)
                .Append("private static readonly string _stringValue;").Append(NewLine)
                .Append("/// <summary> To test a field with initialization value. </summary>").Append(NewLine)
                .Append("private static readonly string withValue = \"abc\";").Append(NewLine)
                .ToString();

            var result = codeWriter.ToString();

            Assert.AreEqual(expected, result);
        }

        [Test]
        public void CodeWriter_WriteProperty_AutoBody()
        {
            var property1 = new PropertyDeclaration($"To test an auto property without a setter", MethodSignatureModifiers.Public, typeof(string), "Property1", new AutoPropertyBody(false));
            var property2 = new PropertyDeclaration($"To test an auto property with a setter", MethodSignatureModifiers.Public, typeof(string), "Property2", new AutoPropertyBody(true, MethodSignatureModifiers.None));
            var property3 = new PropertyDeclaration($"To test an auto property with an internal setter", MethodSignatureModifiers.Public, typeof(string), "Property3", new AutoPropertyBody(true, MethodSignatureModifiers.Internal));
            var property4 = new PropertyDeclaration($"To test an auto property with an internal setter and initialization value", MethodSignatureModifiers.Public, typeof(string), "Property4", new AutoPropertyBody(true, MethodSignatureModifiers.Internal, Literal("abc")));

            var codeWriter = new CodeWriter();
            codeWriter.WriteProperty(property1);
            codeWriter.Append($"// test comment");
            codeWriter.WriteProperty(property2);
            codeWriter.WriteProperty(property3);
            codeWriter.WriteProperty(property4);

            var expected = new StringBuilder()
                .Append(_header)
                .Append("/// <summary> To test an auto property without a setter. </summary>").Append(NewLine)
                .Append("public string Property1{ get; }").Append(NewLine)
                .Append("// test comment").Append(NewLine)
                .Append("/// <summary> To test an auto property with a setter. </summary>").Append(NewLine)
                .Append("public string Property2{ get;set; }").Append(NewLine)
                .Append("/// <summary> To test an auto property with an internal setter. </summary>").Append(NewLine)
                .Append("public string Property3{ get;internal set; }").Append(NewLine)
                .Append("/// <summary> To test an auto property with an internal setter and initialization value. </summary>").Append(NewLine)
                .Append("public string Property4{ get;internal set; } = \"abc\";").Append(NewLine)
                .ToString();

            var result = codeWriter.ToString();

            Assert.AreEqual(expected, result);
        }

        [Test]
        public void CodeWriter_WriteProperty_AutoBody_WithExplicitInterface()
        {
            var property1 = new PropertyDeclaration($"To test an auto property without a setter", MethodSignatureModifiers.Public, typeof(int), nameof(IList<string>.Count), new AutoPropertyBody(false), ExplicitInterface: typeof(IList<string>));
            var property2 = new PropertyDeclaration($"To test an auto property with a setter", MethodSignatureModifiers.Public, typeof(bool), nameof(IList<string>.IsReadOnly), new AutoPropertyBody(true, MethodSignatureModifiers.None), ExplicitInterface: typeof(IList<string>));
            var property3 = new PropertyDeclaration($"To test an auto property with an internal setter", MethodSignatureModifiers.Public, typeof(int), nameof(IReadOnlyList<string>.Count), new AutoPropertyBody(true, MethodSignatureModifiers.Internal), ExplicitInterface: typeof(IReadOnlyList<string>));

            var codeWriter = new CodeWriter();
            codeWriter.WriteProperty(property1);
            codeWriter.Append($"// test comment");
            codeWriter.WriteProperty(property2);
            codeWriter.WriteProperty(property3);

            var expected = new StringBuilder()
                .Append(_header)
                .Append("using System.Collections.Generic;").Append(NewLine).Append(NewLine)
                .Append("/// <summary> To test an auto property without a setter. </summary>").Append(NewLine)
                .Append("public int global::System.Collections.Generic.IList<string>.Count{ get; }").Append(NewLine)
                .Append("// test comment").Append(NewLine)
                .Append("/// <summary> To test an auto property with a setter. </summary>").Append(NewLine)
                .Append("public bool global::System.Collections.Generic.IList<string>.IsReadOnly{ get;set; }").Append(NewLine)
                .Append("/// <summary> To test an auto property with an internal setter. </summary>").Append(NewLine)
                .Append("public int global::System.Collections.Generic.IReadOnlyList<string>.Count{ get;internal set; }").Append(NewLine)
                .ToString();

            var result = codeWriter.ToString();

            Assert.AreEqual(expected, result);
        }

        [Test]
        public void CodeWriter_WriteProperty_ExpressionBody()
        {
            var property1 = new PropertyDeclaration($"To test an expression property with string type", MethodSignatureModifiers.Public, typeof(string), "Property1", new ExpressionPropertyBody(Literal("abc")));
            var property2 = new PropertyDeclaration($"To test an expression property with int type", MethodSignatureModifiers.Public, typeof(int), "Property2", new ExpressionPropertyBody(Literal(299792458)));

            var codeWriter = new CodeWriter();
            codeWriter.WriteProperty(property1);
            codeWriter.Append($"// test comment");
            codeWriter.WriteProperty(property2);

            var expected = new StringBuilder()
                .Append(_header)
                .Append("/// <summary> To test an expression property with string type. </summary>").Append(NewLine)
                .Append("public string Property1 => \"abc\";").Append(NewLine)
                .Append("// test comment").Append(NewLine)
                .Append("/// <summary> To test an expression property with int type. </summary>").Append(NewLine)
                .Append("public int Property2 => 299792458;").Append(NewLine)
                .ToString();

            var result = codeWriter.ToString();

            Assert.AreEqual(expected, result);
        }

        [Test]
        public void CodeWriter_WriteProperty_ExpressionBody_WithExplicitInterface()
        {
            var property1 = new PropertyDeclaration($"To test an expression property with int type", MethodSignatureModifiers.Public, typeof(int), nameof(IList<string>.Count), new ExpressionPropertyBody(Literal(299792458)), ExplicitInterface: typeof(IList<string>));

            var codeWriter = new CodeWriter();
            codeWriter.Append($"// test comment");
            codeWriter.WriteProperty(property1);

            var expected = new StringBuilder()
                .Append(_header)
                .Append("using System.Collections.Generic;").Append(NewLine).Append(NewLine)
                .Append("// test comment").Append(NewLine)
                .Append("/// <summary> To test an expression property with int type. </summary>").Append(NewLine)
                .Append("public int global::System.Collections.Generic.IList<string>.Count => 299792458;").Append(NewLine)
                .ToString();

            var result = codeWriter.ToString();

            Assert.AreEqual(expected, result);
        }

        [Test]
        public void CodeWriter_WriteProperty_MethodPropertyBody()
        {
            var property1 = new PropertyDeclaration($"To test an auto property without a setter", MethodSignatureModifiers.Public, typeof(string), "Property1", new MethodPropertyBody(Return(Literal("abc"))));
            var property2 = new PropertyDeclaration($"To test an auto property with a setter", MethodSignatureModifiers.Public, typeof(string), "Property2", new MethodPropertyBody(Return(Literal("abc")), Assign(new FormattableStringToExpression($"Property2"), new KeywordExpression("value", null))));
            var property3 = new PropertyDeclaration($"To test an auto property with an internal setter", MethodSignatureModifiers.Public, typeof(string), "Property3", new MethodPropertyBody(Return(Literal("abc")), Assign(new FormattableStringToExpression($"Property3"), new KeywordExpression("value", null)), MethodSignatureModifiers.Internal));

            var codeWriter = new CodeWriter();
            codeWriter.WriteProperty(property1);
            codeWriter.Append($"// test comment");
            codeWriter.WriteProperty(property2);
            codeWriter.WriteProperty(property3);

            var expected = new StringBuilder()
                .Append(_header)
                .Append("/// <summary> To test an auto property without a setter. </summary>").Append(NewLine)
                .Append("public string Property1").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("get").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("return \"abc\";").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("// test comment").Append(NewLine)
                .Append("/// <summary> To test an auto property with a setter. </summary>").Append(NewLine)
                .Append("public string Property2").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("get").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("return \"abc\";").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("set").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("Property2 = value;").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("/// <summary> To test an auto property with an internal setter. </summary>").Append(NewLine)
                .Append("public string Property3").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("get").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("return \"abc\";").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("internal set").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("Property3 = value;").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("}").Append(NewLine)
                .ToString();

            var result = codeWriter.ToString();

            Assert.AreEqual(expected, result);
        }

        [Test]
        public void CodeWriter_WriteProperty_MethodPropertyBody_WithExplicitInterface()
        {
            var property1 = new PropertyDeclaration($"To test an auto property without a setter", MethodSignatureModifiers.Public, typeof(int), nameof(IList<string>.Count), new MethodPropertyBody(Return(Literal(299792458))), ExplicitInterface: typeof(IList<string>));
            var property2 = new PropertyDeclaration($"To test an auto property with a setter", MethodSignatureModifiers.Public, typeof(bool), nameof(IList<string>.IsReadOnly), new MethodPropertyBody(Return(True), Assign(new FormattableStringToExpression($"{nameof(IList<string>.IsReadOnly)}"), new KeywordExpression("value", null))), ExplicitInterface: typeof(IList<string>));
            var property3 = new PropertyDeclaration($"To test an auto property with an internal setter", MethodSignatureModifiers.Public, typeof(int), nameof(IReadOnlyList<string>.Count), new MethodPropertyBody(Return(Literal(299792458)), Assign(new FormattableStringToExpression($"{nameof(IReadOnlyList<string>.Count)}"), new KeywordExpression("value", null)), MethodSignatureModifiers.Internal), ExplicitInterface: typeof(IReadOnlyList<string>));

            var codeWriter = new CodeWriter();
            codeWriter.WriteProperty(property1);
            codeWriter.Append($"// test comment");
            codeWriter.WriteProperty(property2);
            codeWriter.WriteProperty(property3);

            var expected = new StringBuilder()
                .Append(_header)
                .Append("using System.Collections.Generic;").Append(NewLine).Append(NewLine)
                .Append("/// <summary> To test an auto property without a setter. </summary>").Append(NewLine)
                .Append("public int global::System.Collections.Generic.IList<string>.Count").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("get").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("return 299792458;").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("// test comment").Append(NewLine)
                .Append("/// <summary> To test an auto property with a setter. </summary>").Append(NewLine)
                .Append("public bool global::System.Collections.Generic.IList<string>.IsReadOnly").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("get").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("return true;").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("set").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("IsReadOnly = value;").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("/// <summary> To test an auto property with an internal setter. </summary>").Append(NewLine)
                .Append("public int global::System.Collections.Generic.IReadOnlyList<string>.Count").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("get").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("return 299792458;").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("internal set").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("Count = value;").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("}").Append(NewLine)
                .ToString();

            var result = codeWriter.ToString();

            Assert.AreEqual(expected, result);
        }

        [Test]
        public void CodeWriter_WriteProperty_IndexerProperty_AutoBody()
        {
            var p1 = new Parameter("p1", $"p1", typeof(int), null, ValidationType.None, null);
            var indexer1 = new IndexerDeclaration($"To test an auto property without a setter", MethodSignatureModifiers.Public, typeof(float), p1, new AutoPropertyBody(false));
            var p2 = new Parameter("p2", $"p2", typeof(string), null, ValidationType.None, null);
            var indexer2 = new IndexerDeclaration($"To test an auto property with a setter", MethodSignatureModifiers.Public, typeof(bool), p2, new AutoPropertyBody(true, MethodSignatureModifiers.None));
            var p3 = new Parameter("p3", $"p3", typeof(float), null, ValidationType.None, null);
            var indexer3 = new IndexerDeclaration($"To test an auto property with an internal setter", MethodSignatureModifiers.Public, typeof(double), p3, new AutoPropertyBody(true, MethodSignatureModifiers.Internal));
            var p4 = new Parameter("p4", $"p4", typeof(double), null, ValidationType.None, null);
            var indexer4 = new IndexerDeclaration($"To test an auto property with an internal setter and initialization value", MethodSignatureModifiers.Public, typeof(string), p4, new AutoPropertyBody(true, MethodSignatureModifiers.Internal, Literal("abc")));

            var codeWriter = new CodeWriter();
            codeWriter.WriteProperty(indexer1);
            codeWriter.Append($"// test comment");
            codeWriter.WriteProperty(indexer2);
            codeWriter.WriteProperty(indexer3);
            codeWriter.WriteProperty(indexer4);

            var expected = new StringBuilder()
                .Append(_header)
                .Append("/// <summary> To test an auto property without a setter. </summary>").Append(NewLine)
                .Append("public float this[int p1]{ get; }").Append(NewLine)
                .Append("// test comment").Append(NewLine)
                .Append("/// <summary> To test an auto property with a setter. </summary>").Append(NewLine)
                .Append("public bool this[string p2]{ get;set; }").Append(NewLine)
                .Append("/// <summary> To test an auto property with an internal setter. </summary>").Append(NewLine)
                .Append("public double this[float p3]{ get;internal set; }").Append(NewLine)
                .Append("/// <summary> To test an auto property with an internal setter and initialization value. </summary>").Append(NewLine)
                .Append("public string this[double p4]{ get;internal set; } = \"abc\";").Append(NewLine)
                .ToString();

            var result = codeWriter.ToString();

            Assert.AreEqual(expected, result);
        }

        [Test]
        public void CodeWriter_WriteProperty_IndexerProperty_AutoBody_WithExplicitInterface()
        {
            var index = new Parameter("index", $"index", typeof(int), null, ValidationType.None, null);
            var indexer1 = new IndexerDeclaration($"To test an auto property without a setter", MethodSignatureModifiers.Public, typeof(string), index, new AutoPropertyBody(false), ExplicitInterface: typeof(IReadOnlyList<string>));
            var indexer2 = new IndexerDeclaration($"To test an auto property with a setter", MethodSignatureModifiers.Public, typeof(bool), index, new AutoPropertyBody(true, MethodSignatureModifiers.None), ExplicitInterface: typeof(IList<bool>));
            var indexer3 = new IndexerDeclaration($"To test an auto property with an internal setter", MethodSignatureModifiers.Public, typeof(double), index, new AutoPropertyBody(true, MethodSignatureModifiers.Internal), ExplicitInterface: typeof(IReadOnlyList<double>));

            var codeWriter = new CodeWriter();
            codeWriter.WriteProperty(indexer1);
            codeWriter.Append($"// test comment");
            codeWriter.WriteProperty(indexer2);
            codeWriter.WriteProperty(indexer3);

            var expected = new StringBuilder()
                .Append(_header)
                .Append("using System.Collections.Generic;").Append(NewLine).Append(NewLine)
                .Append("/// <summary> To test an auto property without a setter. </summary>").Append(NewLine)
                .Append("public string global::System.Collections.Generic.IReadOnlyList<string>.this[int index]{ get; }").Append(NewLine)
                .Append("// test comment").Append(NewLine)
                .Append("/// <summary> To test an auto property with a setter. </summary>").Append(NewLine)
                .Append("public bool global::System.Collections.Generic.IList<bool>.this[int index]{ get;set; }").Append(NewLine)
                .Append("/// <summary> To test an auto property with an internal setter. </summary>").Append(NewLine)
                .Append("public double global::System.Collections.Generic.IReadOnlyList<double>.this[int index]{ get;internal set; }").Append(NewLine)
                .ToString();

            var result = codeWriter.ToString();

            Assert.AreEqual(expected, result);
        }

        [Test]
        public void CodeWriter_WriteProperty_IndexerProperty_ExpressionBody()
        {
            var p1 = new Parameter("p1", $"p1", typeof(int), null, ValidationType.None, null);
            var indexer1 = new IndexerDeclaration($"To test an expression property with string type", MethodSignatureModifiers.Public, typeof(string), p1, new ExpressionPropertyBody(Literal("abc")));
            var p2 = new Parameter("p2", $"p2", typeof(string), null, ValidationType.None, null);
            var indexer2 = new IndexerDeclaration($"To test an expression property with int type", MethodSignatureModifiers.Public, typeof(int), p2, new ExpressionPropertyBody(Literal(299792458)));

            var codeWriter = new CodeWriter();
            codeWriter.WriteProperty(indexer1);
            codeWriter.Append($"// test comment");
            codeWriter.WriteProperty(indexer2);

            var expected = new StringBuilder()
                .Append(_header)
                .Append("/// <summary> To test an expression property with string type. </summary>").Append(NewLine)
                .Append("public string this[int p1] => \"abc\";").Append(NewLine)
                .Append("// test comment").Append(NewLine)
                .Append("/// <summary> To test an expression property with int type. </summary>").Append(NewLine)
                .Append("public int this[string p2] => 299792458;").Append(NewLine)
                .ToString();

            var result = codeWriter.ToString();

            Assert.AreEqual(expected, result);
        }

        [Test]
        public void CodeWriter_WriteProperty_IndexerProperty_ExpressionBody_WithExplicitInterface()
        {
            var p1 = new Parameter("index", $"index", typeof(int), null, ValidationType.None, null);
            var indexer1 = new IndexerDeclaration($"To test an expression property with string type", MethodSignatureModifiers.Public, typeof(string), p1, new ExpressionPropertyBody(Literal("abc")), ExplicitInterface: typeof(IReadOnlyList<string>));
            var p2 = new Parameter("key", $"key", typeof(string), null, ValidationType.None, null);
            var indexer2 = new IndexerDeclaration($"To test an expression property with int type", MethodSignatureModifiers.Public, typeof(int), p2, new ExpressionPropertyBody(Literal(299792458)), ExplicitInterface: typeof(IReadOnlyDictionary<string, int>));

            var codeWriter = new CodeWriter();
            codeWriter.WriteProperty(indexer1);
            codeWriter.Append($"// test comment");
            codeWriter.WriteProperty(indexer2);

            var expected = new StringBuilder()
                .Append(_header)
                .Append("using System.Collections.Generic;").Append(NewLine).Append(NewLine)
                .Append("/// <summary> To test an expression property with string type. </summary>").Append(NewLine)
                .Append("public string global::System.Collections.Generic.IReadOnlyList<string>.this[int index] => \"abc\";").Append(NewLine)
                .Append("// test comment").Append(NewLine)
                .Append("/// <summary> To test an expression property with int type. </summary>").Append(NewLine)
                .Append("public int global::System.Collections.Generic.IReadOnlyDictionary<string, int>.this[string key] => 299792458;").Append(NewLine)
                .ToString();

            var result = codeWriter.ToString();

            Assert.AreEqual(expected, result);
        }

        [Test]
        public void CodeWriter_WriteProperty_IndexerProperty_MethodPropertyBody()
        {
            var p1 = new Parameter("p1", $"p1", typeof(int), null, ValidationType.None, null);
            var indexer1 = new IndexerDeclaration($"To test a method property without a setter", MethodSignatureModifiers.Public, typeof(string), p1, new MethodPropertyBody(Return(Literal("abc"))));
            var p2 = new Parameter("p2", $"p2", typeof(int), null, ValidationType.None, null);
            var indexer2 = new IndexerDeclaration($"To test a method property with a setter", MethodSignatureModifiers.Public, typeof(string), p2, new MethodPropertyBody(Return(Literal("abc")), Assign(new FormattableStringToExpression($"Property2"), new KeywordExpression("value", null))));
            var p3 = new Parameter("p3", $"p3", typeof(int), null, ValidationType.None, null);
            var indexer3 = new IndexerDeclaration($"To test a method property with an internal setter", MethodSignatureModifiers.Public, typeof(string), p3, new MethodPropertyBody(Return(Literal("abc")), Assign(new FormattableStringToExpression($"Property3"), new KeywordExpression("value", null)), MethodSignatureModifiers.Internal));

            var codeWriter = new CodeWriter();
            codeWriter.WriteProperty(indexer1);
            codeWriter.Append($"// test comment");
            codeWriter.WriteProperty(indexer2);
            codeWriter.WriteProperty(indexer3);

            var expected = new StringBuilder()
                .Append(_header)
                .Append("/// <summary> To test a method property without a setter. </summary>").Append(NewLine)
                .Append("public string this[int p1]").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("get").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("return \"abc\";").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("// test comment").Append(NewLine)
                .Append("/// <summary> To test a method property with a setter. </summary>").Append(NewLine)
                .Append("public string this[int p2]").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("get").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("return \"abc\";").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("set").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("Property2 = value;").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("/// <summary> To test a method property with an internal setter. </summary>").Append(NewLine)
                .Append("public string this[int p3]").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("get").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("return \"abc\";").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("internal set").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("Property3 = value;").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("}").Append(NewLine)
                .ToString();

            var result = codeWriter.ToString();

            Assert.AreEqual(expected, result);
        }

        [Test]
        public void CodeWriter_WriteProperty_IndexerProperty_MethodPropertyBody_WithExplicitInterface()
        {
            var index = new Parameter("index", $"index", typeof(int), null, ValidationType.None, null);
            var indexer1 = new IndexerDeclaration($"To test a method property without a setter", MethodSignatureModifiers.Public, typeof(string), index, new MethodPropertyBody(Return(Literal("abc"))), ExplicitInterface: typeof(IReadOnlyList<string>));
            var indexer2 = new IndexerDeclaration($"To test a method property with a setter", MethodSignatureModifiers.Public, typeof(string), index, new MethodPropertyBody(Return(Literal("abc")), Assign(new FormattableStringToExpression($"Property2"), new KeywordExpression("value", null))), ExplicitInterface: typeof(IList<string>));
            var indexer3 = new IndexerDeclaration($"To test a method property with an internal setter", MethodSignatureModifiers.Public, typeof(string), index, new MethodPropertyBody(Return(Literal("abc")), Assign(new FormattableStringToExpression($"Property3"), new KeywordExpression("value", null)), MethodSignatureModifiers.Internal), ExplicitInterface: typeof(IReadOnlyDictionary<int, string>));

            var codeWriter = new CodeWriter();
            codeWriter.WriteProperty(indexer1);
            codeWriter.Append($"// test comment");
            codeWriter.WriteProperty(indexer2);
            codeWriter.WriteProperty(indexer3);

            var expected = new StringBuilder()
                .Append(_header)
                .Append("using System.Collections.Generic;").Append(NewLine).Append(NewLine)
                .Append("/// <summary> To test a method property without a setter. </summary>").Append(NewLine)
                .Append("public string global::System.Collections.Generic.IReadOnlyList<string>.this[int index]").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("get").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("return \"abc\";").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("// test comment").Append(NewLine)
                .Append("/// <summary> To test a method property with a setter. </summary>").Append(NewLine)
                .Append("public string global::System.Collections.Generic.IList<string>.this[int index]").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("get").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("return \"abc\";").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("set").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("Property2 = value;").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("/// <summary> To test a method property with an internal setter. </summary>").Append(NewLine)
                .Append("public string global::System.Collections.Generic.IReadOnlyDictionary<int, string>.this[int index]").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("get").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("return \"abc\";").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("internal set").Append(NewLine)
                .Append("{").Append(NewLine)
                .Append("Property3 = value;").Append(NewLine)
                .Append("}").Append(NewLine)
                .Append("}").Append(NewLine)
                .ToString();

            var result = codeWriter.ToString();

            Assert.AreEqual(expected, result);
        }
    }
}
