// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using Xunit;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit.Abstractions;

namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Parsing
{
    [CompilerTrait(CompilerFeature.ReadOnlyReferences)]
    public class ReadOnlyStructs : ParsingTests
    {
        public ReadOnlyStructs(ITestOutputHelper output) : base(output) { }

        protected override SyntaxTree ParseTree(string text, CSharpParseOptions options)
        {
            return SyntaxFactory.ParseSyntaxTree(text, options: options);
        }

        [Fact]
        public void ReadOnlyStructSimple()
        {
            var text = @"
class Program
{
    readonly struct S1{}

    public readonly struct S2{}

    readonly public struct S3{}
}
";

            var comp = CreateCompilationWithMscorlib461(text, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.Latest), options: TestOptions.DebugDll);
            comp.VerifyDiagnostics(
            );

            var s1 = comp.GetTypeByMetadataName("Program+S1");
            Assert.False(s1.IsRefLikeType);
            Assert.True(s1.IsReadOnly);
            Assert.Equal(Accessibility.Private, s1.DeclaredAccessibility);
            Assert.Equal(TypeKind.Struct, s1.TypeKind);

            var s2 = comp.GetTypeByMetadataName("Program+S2");
            Assert.False(s2.IsRefLikeType);
            Assert.True(s2.IsReadOnly);
            Assert.Equal(Accessibility.Public, s2.DeclaredAccessibility);
            Assert.Equal(TypeKind.Struct, s2.TypeKind);

            var s3 = comp.GetTypeByMetadataName("Program+S3");
            Assert.False(s3.IsRefLikeType);
            Assert.True(s3.IsReadOnly);
            Assert.Equal(Accessibility.Public, s3.DeclaredAccessibility);
            Assert.Equal(TypeKind.Struct, s3.TypeKind);
        }

        [Fact]
        public void ReadOnlyStructSimpleLangVer()
        {
            var text = @"
class Program
{
    readonly struct S1{}

    public readonly struct S2{}

    readonly public struct S3{}
}
";

            var comp = CreateCompilationWithMscorlib461(text, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7), options: TestOptions.DebugDll);
            comp.VerifyDiagnostics(
                // (4,5): error CS8107: Feature 'readonly structs' is not available in C# 7. Please use language version 7.2 or greater.
                //     readonly struct S1{}
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "readonly").WithArguments("readonly structs", "7.2").WithLocation(4, 5),
                // (6,12): error CS8107: Feature 'readonly structs' is not available in C# 7. Please use language version 7.2 or greater.
                //     public readonly struct S2{}
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "readonly").WithArguments("readonly structs", "7.2").WithLocation(6, 12),
                // (8,5): error CS8107: Feature 'readonly structs' is not available in C# 7. Please use language version 7.2 or greater.
                //     readonly public struct S3{}
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "readonly").WithArguments("readonly structs", "7.2").WithLocation(8, 5)
            );
        }

        [Fact]
        public void ReadOnlyClassErr()
        {
            var text = @"
class Program
{
    readonly class S1{}

    public readonly delegate ref readonly int S2();

    readonly public interface S3{}
}
";

            var comp = CreateCompilationWithMscorlib461(text, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.Latest), options: TestOptions.DebugDll);
            comp.VerifyDiagnostics(
                // (4,20): error CS0106: The modifier 'readonly' is not valid for this item
                //     readonly class S1{}
                Diagnostic(ErrorCode.ERR_BadMemberFlag, "S1").WithArguments("readonly").WithLocation(4, 20),
                // (6,47): error CS0106: The modifier 'readonly' is not valid for this item
                //     public readonly delegate ref readonly int S2();
                Diagnostic(ErrorCode.ERR_BadMemberFlag, "S2").WithArguments("readonly").WithLocation(6, 47),
                // (8,31): error CS0106: The modifier 'readonly' is not valid for this item
                //     readonly public interface S3{}
                Diagnostic(ErrorCode.ERR_BadMemberFlag, "S3").WithArguments("readonly").WithLocation(8, 31)
            );

            var s1 = comp.GetTypeByMetadataName("Program+S1");
            Assert.False(s1.IsRefLikeType);
            Assert.False(s1.IsReadOnly);
            Assert.Equal(Accessibility.Private, s1.DeclaredAccessibility);
            Assert.Equal(TypeKind.Class, s1.TypeKind);

            var s2 = comp.GetTypeByMetadataName("Program+S2");
            Assert.False(s2.IsRefLikeType);
            Assert.False(s2.IsReadOnly);
            Assert.Equal(Accessibility.Public, s2.DeclaredAccessibility);
            Assert.Equal(TypeKind.Delegate, s2.TypeKind);

            var s3 = comp.GetTypeByMetadataName("Program+S3");
            Assert.False(s3.IsRefLikeType);
            Assert.False(s3.IsReadOnly);
            Assert.Equal(Accessibility.Public, s3.DeclaredAccessibility);
            Assert.Equal(TypeKind.Interface, s3.TypeKind);
        }

        [Fact]
        public void ReadOnlyRefStruct()
        {
            var text = @"
class Program
{
    readonly ref struct S1{}

    unsafe readonly public ref struct S2{}
}
";

            var comp = CreateCompilationWithMscorlib461(text, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.Latest), options: TestOptions.DebugDll.WithAllowUnsafe(true));
            comp.VerifyDiagnostics(
            );

            var s1 = comp.GetTypeByMetadataName("Program+S1");
            Assert.True(s1.IsRefLikeType);
            Assert.True(s1.IsReadOnly);
            Assert.Equal(Accessibility.Private, s1.DeclaredAccessibility);
            Assert.Equal(TypeKind.Struct, s1.TypeKind);

            var s2 = comp.GetTypeByMetadataName("Program+S2");
            Assert.True(s2.IsRefLikeType);
            Assert.True(s2.IsReadOnly);
            Assert.Equal(Accessibility.Public, s2.DeclaredAccessibility);
            Assert.Equal(TypeKind.Struct, s2.TypeKind);
        }

        [Fact]
        public void ReadOnlyStructPartialMatchingModifiers()
        {
            var text = @"
class Program
{
    readonly partial struct S1{}

    readonly partial struct S1{}

    readonly ref partial struct S2{}

    readonly ref partial struct S2{}
}
";

            var comp = CreateCompilationWithMscorlib461(text, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.Latest), options: TestOptions.DebugDll);
            comp.VerifyDiagnostics(
            );

            var s1 = comp.GetTypeByMetadataName("Program+S1");
            Assert.False(s1.IsRefLikeType);
            Assert.True(s1.IsReadOnly);

            var s2 = comp.GetTypeByMetadataName("Program+S2");
            Assert.True(s2.IsRefLikeType);
            Assert.True(s2.IsReadOnly);
        }

        [WorkItem(19808, "https://github.com/dotnet/roslyn/issues/19808")]
        [Fact]
        public void ReadOnlyStructPartialNotMatchingModifiers()
        {
            var text = @"
class Program
{
    readonly partial struct S1{}

    readonly ref partial struct S1{}

    readonly partial struct S2{}

    partial struct S2{}

    readonly ref partial struct S3{}

    partial struct S3{}
}
";

            var comp = CreateCompilationWithMscorlib461(text, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.Latest), options: TestOptions.DebugDll);
            comp.VerifyDiagnostics(
            );

            var s1 = comp.GetTypeByMetadataName("Program+S1");
            Assert.True(s1.IsRefLikeType);
            Assert.True(s1.IsReadOnly);

            var s2 = comp.GetTypeByMetadataName("Program+S2");
            Assert.False(s2.IsRefLikeType);
            Assert.True(s2.IsReadOnly);

            var s3 = comp.GetTypeByMetadataName("Program+S3");
            Assert.True(s3.IsRefLikeType);
            Assert.True(s3.IsReadOnly);
        }
    }
}
