// 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 System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
using static Roslyn.Test.Utilities.TestMetadata;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.SymbolId
{
    public partial class SymbolKeyTest : SymbolKeyTestBase
    {
        #region "Metadata vs. Source"

        [Fact]
        public void M2SNamedTypeSymbols01()
        {
            var src1 = """
                using System;

                public delegate void D(int p1, string p2);

                namespace N1.N2
                {
                    public interface I { }
                    namespace N3
                    {
                        public class C 
                        {
                            public struct S 
                            {
                                public enum E { Zero, One, Two }
                                public void M(int n) { Console.WriteLine(n); }
                            }
                        }
                    }
                }
                """;

            var src2 = """
                using System;
                using N1.N2.N3;

                public class App : C
                {
                    private event D myEvent;
                    internal N1.N2.I Prop { get; set; }
                    protected C.S.E this[int x] { set { } }
                    public void M(C.S s) { s.M(123); }
                }
                """;

            var comp1 = CreateCompilation(src1);

            // Compilation to Compilation
            var comp2 = (Compilation)CreateCompilation(src2, new MetadataReference[] { new CSharpCompilationReference(comp1) });

            var originalSymbols = GetSourceSymbols(comp1, SymbolCategory.DeclaredType).OrderBy(s => s.Name).ToList();
            Assert.Equal(5, originalSymbols.Count);

            // ---------------------------
            // Metadata symbols
            var typesym = comp2.SourceModule.GlobalNamespace.GetTypeMembers("App").FirstOrDefault();

            // 'D'
            var member01 = (typesym.GetMembers("myEvent").Single() as IEventSymbol).Type;

            // 'I'
            var member02 = (typesym.GetMembers("Prop").Single() as IPropertySymbol).Type;

            // 'C'
            var member03 = typesym.BaseType;

            // 'S'
            var member04 = (typesym.GetMembers("M").Single() as IMethodSymbol).Parameters[0].Type;

            // 'E'
            var member05 = (typesym.GetMembers(WellKnownMemberNames.Indexer).Single() as IPropertySymbol).Type;

            ResolveAndVerifySymbol(member03, originalSymbols[0], comp1, SymbolKeyComparison.None);
            ResolveAndVerifySymbol(member01, originalSymbols[1], comp1, SymbolKeyComparison.None);
            ResolveAndVerifySymbol(member05, originalSymbols[2], comp1, SymbolKeyComparison.None);
            ResolveAndVerifySymbol(member02, originalSymbols[3], comp1, SymbolKeyComparison.None);
            ResolveAndVerifySymbol(member04, originalSymbols[4], comp1, SymbolKeyComparison.None);
        }

        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542700")]
        public void M2SNonTypeMemberSymbols01()
        {
            var src1 = """
                using System;

                namespace N1
                {
                    public interface IGoo
                    {
                        void M(int p1, int p2);
                        void M(params short[] ary);

                        void M(string p1);
                        void M(ref string p1);
                    }

                    public struct S
                    {
                        public event Action<S> PublicEvent { add { } remove { } }
                        public IGoo PublicField;
                        public string PublicProp { get; set; }
                        public short this[sbyte p] { get { return p; } }
                    }
                }
                """;

            var src2 = """
                using System;
                using AN = N1;

                public class App
                {
                    static void Main()
                    {
                        var obj = new AN.S();

                        /*<bind0>*/obj.PublicEvent/*</bind0>*/ += EH;

                        var igoo = /*<bind1>*/obj.PublicField/*</bind1>*/;

                        /*<bind3>*/igoo.M(/*<bind2>*/obj.PublicProp/*</bind2>*/)/*</bind3>*/;

                        /*<bind5>*/igoo.M(obj[12], /*<bind4>*/obj[123]/*</bind4>*/)/*</bind5>*/;
                    }

                    static void EH(AN.S s) { }
                }
                """;

            var comp1 = CreateCompilation(src1);

            // Compilation to Assembly
            var comp2 = CreateCompilation(src2, new MetadataReference[] { comp1.EmitToImageReference() });

            // ---------------------------
            // Source symbols
            var originalSymbols = GetSourceSymbols(comp1, SymbolCategory.NonTypeMember | SymbolCategory.Parameter).ToList();
            originalSymbols = originalSymbols.Where(s => !s.IsAccessor() && s.Kind != SymbolKind.Parameter).OrderBy(s => s.Name).Select(s => s).ToList();
            Assert.Equal(8, originalSymbols.Count);

            // ---------------------------
            // Metadata symbols
            var bindingtuples = GetBindingNodesAndModel<ExpressionSyntax>(comp2);
            var model = bindingtuples.Item2;
            var list = bindingtuples.Item1;
            Assert.Equal(6, list.Count);

            // event
            ResolveAndVerifySymbol(list[0], originalSymbols[4], model, comp1, SymbolKeyComparison.None);

            // field
            ResolveAndVerifySymbol(list[1], originalSymbols[5], model, comp1, SymbolKeyComparison.None);

            // prop
            ResolveAndVerifySymbol(list[2], originalSymbols[6], model, comp1, SymbolKeyComparison.None);

            // index:
            ResolveAndVerifySymbol(list[4], originalSymbols[7], model, comp1, SymbolKeyComparison.None);

            // M(string p1)
            ResolveAndVerifySymbol(list[3], originalSymbols[2], model, comp1, SymbolKeyComparison.None);

            // M(params short[] ary)
            ResolveAndVerifySymbol(list[5], originalSymbols[1], model, comp1, SymbolKeyComparison.None);
        }

        #endregion

        #region "Metadata vs. Metadata"

        [Fact]
        public void M2MMultiTargetingMsCorLib01()
        {
            var src1 = """
                using System;
                using System.IO;

                public class A
                {
                    public FileInfo GetFileInfo(string path)
                    {
                        if (File.Exists(path))
                        {
                            return new FileInfo(path);
                        }

                        return null;
                    }

                    public void PrintInfo(Array ary, ref DateTime time)
                    {
                        if (ary != null)
                            Console.WriteLine(ary);
                        else
                            Console.WriteLine("null");

                        time = DateTime.Now;
                    }
                }
                """;

            var src2 = """
                using System;

                class Test
                {
                    static void Main()
                    {
                        var a = new A();
                        var fi = a.GetFileInfo(null);
                        Console.WriteLine(fi);

                        var dt = DateTime.Now;
                        var ary = Array.CreateInstance(typeof(string), 2);
                        a.PrintInfo(ary, ref dt);
                    }
                }
                """;
            var comp20 = (Compilation)CreateEmptyCompilation(src1, new[] { Net40.mscorlib });

            // "Compilation 2 Assembly"
            var comp40 = (Compilation)CreateCompilation(src2, new MetadataReference[] { comp20.EmitToImageReference() });

            var typeA = comp20.SourceModule.GlobalNamespace.GetTypeMembers("A").Single();
            var mem20_1 = typeA.GetMembers("GetFileInfo").Single() as IMethodSymbol;
            var mem20_2 = typeA.GetMembers("PrintInfo").Single() as IMethodSymbol;

            // FileInfo
            var mtsym20_1 = mem20_1.ReturnType;
            Assert.Equal(2, mem20_2.Parameters.Length);

            // Array
            var mtsym20_2 = mem20_2.Parameters[0].Type;

            // ref DateTime
            var mtsym20_3 = mem20_2.Parameters[1].Type;

            // ====================
            var typeTest = comp40.SourceModule.GlobalNamespace.GetTypeMembers("Test").FirstOrDefault();
            var mem40 = typeTest.GetMembers("Main").Single() as IMethodSymbol;
            var list = GetBlockSyntaxList(mem40);
            var model = comp40.GetSemanticModel(comp40.SyntaxTrees.First());

            foreach (var body in list)
            {
                var df = model.AnalyzeDataFlow(body.Statements.First(), body.Statements.Last());
                foreach (var local in df.VariablesDeclared)
                {
                    var localType = ((ILocalSymbol)local).Type;

                    if (local.Name == "fi")
                    {
                        ResolveAndVerifySymbol(localType, mtsym20_1, comp20, SymbolKeyComparison.None);
                    }
                    else if (local.Name == "ary")
                    {
                        ResolveAndVerifySymbol(localType, mtsym20_2, comp20, SymbolKeyComparison.None);
                    }
                    else if (local.Name == "dt")
                    {
                        ResolveAndVerifySymbol(localType, mtsym20_3, comp20, SymbolKeyComparison.None);
                    }
                }
            }
        }

        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546255")]
        public void M2MMultiTargetingMsCorLib02()
        {
            var src1 = """
                using System;
                namespace Mscorlib20
                {
                    public interface IGoo
                    {
                        // interface
                        IDisposable Prop { get; set; }
                        // class
                        Exception this[ArgumentException t] { get; }
                    }

                    public class CGoo : IGoo
                    {
                        // enum
                        public DayOfWeek PublicField;
                        // delegate
                        public event System.Threading.ParameterizedThreadStart PublicEventField;

                        public IDisposable Prop { get; set; }
                        public Exception this[ArgumentException t] { get { return t; } }
                    }
                }
                """;

            var src2 = """
                using System;
                using N20 = Mscorlib20;

                class Test
                {
                    public IDisposable M()
                    {
                        var obj = new N20::CGoo();
                        N20.IGoo igoo = obj;

                        /*<bind0>*/obj.PublicEventField/*</bind0>*/ += /*<bind1>*/MyEveHandler/*</bind1>*/;
                        var local = /*<bind2>*/igoo[null]/*</bind2>*/;

                        if (/*<bind3>*/obj.PublicField /*</bind3>*/== DayOfWeek.Friday)
                        {
                            return /*<bind4>*/(obj as N20.IGoo).Prop/*</bind4>*/;
                        }
                        return null;
                    }

                    public void MyEveHandler(object o) { }
                }
                """;
            var comp20 = CreateEmptyCompilation(src1, new[] { Net40.mscorlib });

            // "Compilation ref Compilation"
            var comp40 = CreateCompilation(src2, new[] { new CSharpCompilationReference(comp20) });

            var originals = GetSourceSymbols(comp20, SymbolCategory.NonTypeMember | SymbolCategory.Parameter);
            var originalSymbols = originals.Where(s => !s.IsAccessor() && s.Kind != SymbolKind.Parameter).OrderBy(s => s.Name).ToList();

            // IGoo.Prop, CGoo.Prop, Event, Field, IGoo.This, CGoo.This
            Assert.Equal(6, originalSymbols.Count);

            // ====================
            var bindingtuples = GetBindingNodesAndModel<ExpressionSyntax>(comp40);
            var model = bindingtuples.Item2;
            var list = bindingtuples.Item1;
            Assert.Equal(5, list.Count);

            // PublicEventField
            ResolveAndVerifySymbol(list[0], originalSymbols[2], model, comp20);

            // delegate ParameterizedThreadStart
            ResolveAndVerifyTypeSymbol(list[0], (originalSymbols[2] as IEventSymbol).Type, model, comp20);

            // MethodGroup
            ResolveAndVerifyTypeSymbol(list[1], (originalSymbols[2] as IEventSymbol).Type, model, comp20);

            // Indexer
            ResolveAndVerifySymbol(list[2], originalSymbols[4], model, comp20);

            // class Exception
            ResolveAndVerifyTypeSymbol(list[2], (originalSymbols[4] as IPropertySymbol).Type, model, comp20);

            // PublicField
            ResolveAndVerifySymbol(list[3], originalSymbols[3], model, comp20);

            // enum DayOfWeek
            ResolveAndVerifyTypeSymbol(list[3], (originalSymbols[3] as IFieldSymbol).Type, model, comp20);

            // Prop
            ResolveAndVerifySymbol(list[4], originalSymbols[0], model, comp20);

            // interface IDisposable
            ResolveAndVerifyTypeSymbol(list[4], (originalSymbols[0] as IPropertySymbol).Type, model, comp20);
        }

        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546255")]
        public void M2MMultiTargetingMsCorLib03()
        {
            var src1 = """
                using System;
                namespace Mscorlib20
                {
                    public interface IGoo
                    {
                        // interface
                        IDisposable Prop { get; set; }
                        // class
                        Exception this[ArgumentException t] { get; }
                    }

                    public class CGoo : IGoo
                    {
                        // explicit
                        IDisposable IGoo.Prop { get; set; }
                        Exception IGoo.this[ArgumentException t] { get { return t; } }
                    }
                }
                """;

            var src2 = """
                using System;
                using N20 = Mscorlib20;

                class Test
                {
                    public IDisposable M()
                    {
                        N20.IGoo igoo = new N20::CGoo();

                        var local = /*<bind0>*/igoo[new ArgumentException()]/*</bind0>*/;
                        return /*<bind1>*/igoo.Prop/*</bind1>*/;
                    }
                }
                """;
            var comp20 = CreateEmptyCompilation(src1, new[] { Net40.mscorlib });

            // "Compilation ref Compilation"
            var comp40 = CreateCompilation(src2, new[] { new CSharpCompilationReference(comp20) });

            var originals = GetSourceSymbols(comp20, SymbolCategory.NonTypeMember | SymbolCategory.Parameter);
            var originalSymbols = originals.Where(s => !s.IsAccessor() && s.Kind != SymbolKind.Parameter).OrderBy(s => s.Name).ToList();

            // CGoo.Prop, CGoo.This, IGoo.Prop, IGoo.This
            Assert.Equal(4, originalSymbols.Count);

            // ====================
            var bindingtuples = GetBindingNodesAndModel<ExpressionSyntax>(comp40);
            var model = bindingtuples.Item2;
            var list = bindingtuples.Item1;
            Assert.Equal(2, list.Count);

            // Indexer
            ResolveAndVerifySymbol(list[0], originalSymbols[3], model, comp20);

            // class Exception
            ResolveAndVerifyTypeSymbol(list[0], (originalSymbols[3] as IPropertySymbol).Type, model, comp20);

            // Prop
            ResolveAndVerifySymbol(list[1], originalSymbols[2], model, comp20);

            // interface IDisposable
            ResolveAndVerifyTypeSymbol(list[1], (originalSymbols[2] as IPropertySymbol).Type, model, comp20);
        }

        #endregion
    }
}
