// 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 Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.CSharp.UnitTests.PDB
{
    public class PDBTupleTests : CSharpPDBTestBase
    {
        [Fact]
        public void Local()
        {
            var source = WithWindowsLineBreaks(
@"class C
{
    static void F()
    {
        (int A, int B, (int C, int), int, int, int G, int H, int I) t = (1, 2, (3, 4), 5, 6, 7, 8, 9);
    }
}");
            var comp = CreateCompilation(source, options: TestOptions.DebugDll);
            comp.VerifyPdb(
@"<symbols>
  <files>
    <file id=""1"" name="""" language=""C#"" />
  </files>
  <methods>
    <method containingType=""C"" name=""F"">
      <customDebugInfo>
        <using>
          <namespace usingCount=""0"" />
        </using>
        <tupleElementNames>
          <local elementNames=""|A|B||||G|H|I|C||"" slotIndex=""0"" localName=""t"" scopeStart=""0x0"" scopeEnd=""0x0"" />
        </tupleElementNames>
        <encLocalSlotMap>
          <slot kind=""0"" offset=""71"" />
        </encLocalSlotMap>
      </customDebugInfo>
      <sequencePoints>
        <entry offset=""0x0"" startLine=""4"" startColumn=""5"" endLine=""4"" endColumn=""6"" document=""1"" />
        <entry offset=""0x1"" startLine=""5"" startColumn=""9"" endLine=""5"" endColumn=""103"" document=""1"" />
        <entry offset=""0x1b"" startLine=""6"" startColumn=""5"" endLine=""6"" endColumn=""6"" document=""1"" />
      </sequencePoints>
      <scope startOffset=""0x0"" endOffset=""0x1c"">
        <local name=""t"" il_index=""0"" il_start=""0x0"" il_end=""0x1c"" attributes=""0"" />
      </scope>
    </method>
  </methods>
</symbols>");
        }

        [Fact]
        public void Constant()
        {
            var source = WithWindowsLineBreaks(
@"class C<T>
{
    static (int, int) F;
}
class C
{
    static void F()
    {
        const C<(int A, int B)> c = null;
    }
}");
            var comp = CreateCompilation(source, options: TestOptions.DebugDll);
            comp.VerifyPdb(
@"<symbols>
  <files>
    <file id=""1"" name="""" language=""C#"" />
  </files>
  <methods>
    <method containingType=""C"" name=""F"">
      <customDebugInfo>
        <using>
          <namespace usingCount=""0"" />
        </using>
        <tupleElementNames>
          <local elementNames=""|A|B"" slotIndex=""-1"" localName=""c"" scopeStart=""0x0"" scopeEnd=""0x2"" />
        </tupleElementNames>
      </customDebugInfo>
      <sequencePoints>
        <entry offset=""0x0"" startLine=""8"" startColumn=""5"" endLine=""8"" endColumn=""6"" document=""1"" />
        <entry offset=""0x1"" startLine=""10"" startColumn=""5"" endLine=""10"" endColumn=""6"" document=""1"" />
      </sequencePoints>
      <scope startOffset=""0x0"" endOffset=""0x2"">
        <constant name=""c"" value=""null"" signature=""C`1{System.ValueTuple`2{Int32, Int32}}"" />
      </scope>
    </method>
  </methods>
</symbols>");
        }

        [Fact]
        public void TuplesAndDynamic()
        {
            var source = WithWindowsLineBreaks(
@"class C<T>
{
}
class C
{
    static void F()
    {
        {
            (dynamic A, object B, object)[] x;
            const C<(object, dynamic, object C)> y = null;
        }
        {
            const C<(object A, object)> x = null;
        }
        {
            const C<(object, dynamic)> x = null;
            const C<(object, dynamic B)> y = null;
        }
    }
}");
            var comp = CreateCompilation(source, options: TestOptions.DebugDll);
            comp.VerifyPdb(
@"<symbols>
  <files>
    <file id=""1"" name="""" language=""C#"" />
  </files>
  <methods>
    <method containingType=""C"" name=""F"">
      <customDebugInfo>
        <using>
          <namespace usingCount=""0"" />
        </using>
        <dynamicLocals>
          <bucket flags=""00100"" slotId=""0"" localName=""x"" />
          <bucket flags=""00010"" slotId=""0"" localName=""y"" />
          <bucket flags=""0001"" slotId=""0"" localName=""x"" />
          <bucket flags=""0001"" slotId=""0"" localName=""y"" />
        </dynamicLocals>
        <tupleElementNames>
          <local elementNames=""|A|B|"" slotIndex=""0"" localName=""x"" scopeStart=""0x0"" scopeEnd=""0x0"" />
          <local elementNames=""|||C"" slotIndex=""-1"" localName=""y"" scopeStart=""0x1"" scopeEnd=""0x3"" />
          <local elementNames=""|A|"" slotIndex=""-1"" localName=""x"" scopeStart=""0x3"" scopeEnd=""0x5"" />
          <local elementNames=""||B"" slotIndex=""-1"" localName=""y"" scopeStart=""0x5"" scopeEnd=""0x7"" />
        </tupleElementNames>
        <encLocalSlotMap>
          <slot kind=""0"" offset=""58"" />
        </encLocalSlotMap>
      </customDebugInfo>
      <sequencePoints>
        <entry offset=""0x0"" startLine=""7"" startColumn=""5"" endLine=""7"" endColumn=""6"" document=""1"" />
        <entry offset=""0x1"" startLine=""8"" startColumn=""9"" endLine=""8"" endColumn=""10"" document=""1"" />
        <entry offset=""0x2"" startLine=""11"" startColumn=""9"" endLine=""11"" endColumn=""10"" document=""1"" />
        <entry offset=""0x3"" startLine=""12"" startColumn=""9"" endLine=""12"" endColumn=""10"" document=""1"" />
        <entry offset=""0x4"" startLine=""14"" startColumn=""9"" endLine=""14"" endColumn=""10"" document=""1"" />
        <entry offset=""0x5"" startLine=""15"" startColumn=""9"" endLine=""15"" endColumn=""10"" document=""1"" />
        <entry offset=""0x6"" startLine=""18"" startColumn=""9"" endLine=""18"" endColumn=""10"" document=""1"" />
        <entry offset=""0x7"" startLine=""19"" startColumn=""5"" endLine=""19"" endColumn=""6"" document=""1"" />
      </sequencePoints>
      <scope startOffset=""0x0"" endOffset=""0x8"">
        <scope startOffset=""0x1"" endOffset=""0x3"">
          <local name=""x"" il_index=""0"" il_start=""0x1"" il_end=""0x3"" attributes=""0"" />
          <constant name=""y"" value=""null"" signature=""C`1{System.ValueTuple`3{Object, Object, Object}}"" />
        </scope>
        <scope startOffset=""0x3"" endOffset=""0x5"">
          <constant name=""x"" value=""null"" signature=""C`1{System.ValueTuple`2{Object, Object}}"" />
        </scope>
        <scope startOffset=""0x5"" endOffset=""0x7"">
          <constant name=""x"" value=""null"" signature=""C`1{System.ValueTuple`2{Object, Object}}"" />
          <constant name=""y"" value=""null"" signature=""C`1{System.ValueTuple`2{Object, Object}}"" />
        </scope>
      </scope>
    </method>
  </methods>
</symbols>");
        }

        [Fact]
        public void MultiByteCharacters()
        {
            var source = WithWindowsLineBreaks(
@"class C
{
    static void F()
    {
        (int \u1234, int, int \u005f\u1200\u005f) \u1200 = (1, 2, 3);
    }
}");
            var comp = CreateCompilation(source, options: TestOptions.DebugDll);
            comp.VerifyPdb(
string.Format(@"<symbols>
  <files>
    <file id=""1"" name="""" language=""C#"" />
  </files>
  <methods>
    <method containingType=""C"" name=""F"">
      <customDebugInfo>
        <using>
          <namespace usingCount=""0"" />
        </using>
        <tupleElementNames>
          <local elementNames=""|{0}||{1}"" slotIndex=""0"" localName=""{2}"" scopeStart=""0x0"" scopeEnd=""0x0"" />
        </tupleElementNames>
        <encLocalSlotMap>
          <slot kind=""0"" offset=""53"" />
        </encLocalSlotMap>
      </customDebugInfo>
      <sequencePoints>
        <entry offset=""0x0"" startLine=""4"" startColumn=""5"" endLine=""4"" endColumn=""6"" document=""1"" />
        <entry offset=""0x1"" startLine=""5"" startColumn=""9"" endLine=""5"" endColumn=""70"" document=""1"" />
        <entry offset=""0xa"" startLine=""6"" startColumn=""5"" endLine=""6"" endColumn=""6"" document=""1"" />
      </sequencePoints>
      <scope startOffset=""0x0"" endOffset=""0xb"">
        <local name=""{2}"" il_index=""0"" il_start=""0x0"" il_end=""0xb"" attributes=""0"" />
      </scope>
    </method>
  </methods>
</symbols>",
    "\u1234",
    "_\u1200_",
    "\u1200"));
        }

        [Fact]
        public void DeconstructionForeach()
        {
            var source = WithWindowsLineBreaks(
@"class C
{
    static void F(System.Collections.Generic.IEnumerable<(int a, int b)> ie)
    { //4,5
        foreach (         //5,9
            var (a, b)    //6,13
            in            //7,13
            ie)           //8,13
        { //9,9
        } //10,9
    } //11,5
}");
            var comp = CreateCompilation(source, options: TestOptions.DebugDll);
            comp.VerifyPdb(
string.Format(@"<symbols>
  <files>
    <file id=""1"" name="""" language=""C#"" />
  </files>
  <methods>
    <method containingType=""C"" name=""F"" parameterNames=""ie"">
      <customDebugInfo>
        <using>
          <namespace usingCount=""0"" />
        </using>
        <encLocalSlotMap>
          <slot kind=""5"" offset=""17"" />
          <slot kind=""0"" offset=""59"" />
          <slot kind=""0"" offset=""62"" />
        </encLocalSlotMap>
      </customDebugInfo>
      <sequencePoints>
        <entry offset=""0x0"" startLine=""4"" startColumn=""5"" endLine=""4"" endColumn=""6"" document=""1"" />
        <entry offset=""0x1"" startLine=""5"" startColumn=""9"" endLine=""5"" endColumn=""16"" document=""1"" />
        <entry offset=""0x2"" startLine=""8"" startColumn=""13"" endLine=""8"" endColumn=""15"" document=""1"" />
        <entry offset=""0x9"" hidden=""true"" document=""1"" />
        <entry offset=""0xb"" startLine=""6"" startColumn=""13"" endLine=""6"" endColumn=""23"" document=""1"" />
        <entry offset=""0x1e"" startLine=""9"" startColumn=""9"" endLine=""9"" endColumn=""10"" document=""1"" />
        <entry offset=""0x1f"" startLine=""10"" startColumn=""9"" endLine=""10"" endColumn=""10"" document=""1"" />
        <entry offset=""0x20"" startLine=""7"" startColumn=""13"" endLine=""7"" endColumn=""15"" document=""1"" />
        <entry offset=""0x2a"" hidden=""true"" document=""1"" />
        <entry offset=""0x34"" hidden=""true"" document=""1"" />
        <entry offset=""0x35"" startLine=""11"" startColumn=""5"" endLine=""11"" endColumn=""6"" document=""1"" />
      </sequencePoints>
      <scope startOffset=""0x0"" endOffset=""0x36"">
        <scope startOffset=""0xb"" endOffset=""0x20"">
          <local name=""a"" il_index=""1"" il_start=""0xb"" il_end=""0x20"" attributes=""0"" />
          <local name=""b"" il_index=""2"" il_start=""0xb"" il_end=""0x20"" attributes=""0"" />
        </scope>
      </scope>
    </method>
  </methods>
</symbols>"));
        }

        [WorkItem(17947, "https://github.com/dotnet/roslyn/issues/17947")]
        [Fact]
        public void VariablesAndConstantsInUnreachableCode()
        {
            string source = WithWindowsLineBreaks(@"
class C
{
    void F()
    {
        (int a, int b)[] v1 = null;
        const (int a, int b)[] c1 = null;

        throw null;

        (int a, int b)[] v2 = null; 
        const (int a, int b)[] c2 = null;

        { 
            (int a, int b)[] v3 = null; 
            const (int a, int b)[] c3 = null;
        }
    }
}
");
            var c = CreateCompilation(source, options: TestOptions.DebugDll);
            var v = CompileAndVerify(c);
            v.VerifyIL("C.F", @"
{
  // Code size        5 (0x5)
  .maxstack  1
  .locals init (System.ValueTuple<int, int>[] V_0, //v1
                System.ValueTuple<int, int>[] V_1, //v2
                System.ValueTuple<int, int>[] V_2) //v3
  IL_0000:  nop
  IL_0001:  ldnull
  IL_0002:  stloc.0
  IL_0003:  ldnull
  IL_0004:  throw
}
");

            c.VerifyPdb(@"
<symbols>
  <files>
    <file id=""1"" name="""" language=""C#"" />
  </files>
  <methods>
    <method containingType=""C"" name=""F"">
      <customDebugInfo>
        <using>
          <namespace usingCount=""0"" />
        </using>
        <tupleElementNames>
          <local elementNames=""|a|b"" slotIndex=""0"" localName=""v1"" scopeStart=""0x0"" scopeEnd=""0x0"" />
          <local elementNames=""|a|b"" slotIndex=""1"" localName=""v2"" scopeStart=""0x0"" scopeEnd=""0x0"" />
          <local elementNames=""|a|b"" slotIndex=""-1"" localName=""c1"" scopeStart=""0x0"" scopeEnd=""0x5"" />
          <local elementNames=""|a|b"" slotIndex=""-1"" localName=""c2"" scopeStart=""0x0"" scopeEnd=""0x5"" />
        </tupleElementNames>
        <encLocalSlotMap>
          <slot kind=""0"" offset=""28"" />
          <slot kind=""0"" offset=""133"" />
          <slot kind=""0"" offset=""232"" />
        </encLocalSlotMap>
      </customDebugInfo>
      <sequencePoints>
        <entry offset=""0x0"" startLine=""5"" startColumn=""5"" endLine=""5"" endColumn=""6"" document=""1"" />
        <entry offset=""0x1"" startLine=""6"" startColumn=""9"" endLine=""6"" endColumn=""36"" document=""1"" />
        <entry offset=""0x3"" startLine=""9"" startColumn=""9"" endLine=""9"" endColumn=""20"" document=""1"" />
      </sequencePoints>
      <scope startOffset=""0x0"" endOffset=""0x5"">
        <local name=""v1"" il_index=""0"" il_start=""0x0"" il_end=""0x5"" attributes=""0"" />
        <local name=""v2"" il_index=""1"" il_start=""0x0"" il_end=""0x5"" attributes=""0"" />
        <constant name=""c1"" value=""null"" signature=""System.ValueTuple`2{Int32, Int32}[]"" />
        <constant name=""c2"" value=""null"" signature=""System.ValueTuple`2{Int32, Int32}[]"" />
      </scope>
    </method>
  </methods>
</symbols>

");
        }
    }
}
