// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

namespace Microsoft.Build.UnitTests.OM.ObjectModelRemoting
{
    using System;
    using System.Collections.Generic;
    using Microsoft.Build.Evaluation;
    using Xunit;

    public class TestCollectionGroup : IDisposable
    {
        public static string SampleProjectFile = ObjectModelHelpers.CleanupFileContents(@"
                    <Project xmlns='msbuildnamespace' ToolsVersion='2.0' InitialTargets='it' DefaultTargets='dt'>
                        <PropertyGroup Condition=""'$(Configuration)'=='Foo'"">
                            <p>v1</p>
                            <gpt1>Foo$(gp1)</gpt1>
                        </PropertyGroup>
                        <PropertyGroup Condition=""'$(Configuration)'!='Foo'"">
                            <p>v2</p>
                            <gpt1>NotFoo$(gp1)</gpt1>
                        </PropertyGroup>
                        <PropertyGroup>
                            <p2>X$(p)</p2>
                        </PropertyGroup>
                        <ItemGroup>
                            <i Condition=""'$(Configuration)'=='Foo'"" Include='i0'/>
                            <i Include='i1'/>
                            <i Include='$(p)X;i3'/>
                        </ItemGroup>
                        <Target Name='t'>
                            <task/>
                        </Target>
                    </Project>
                ");

        public static string BigProjectFile = ObjectModelHelpers.CleanupFileContents(@"
                    <Project xmlns='msbuildnamespace' ToolsVersion='2.0' InitialTargets='it' DefaultTargets='dt'>
                        <Import Project='pi1.proj' />
                        <Import Project='pi2.proj' Condition=""'$(Configuration)'=='Foo'""/>
                        <Import Project='pi3.proj' Condition='false' Sdk=""FakeSdk"" Version=""1.0"" MinimumVersion=""1.0""/>

                        <UsingTask TaskName='SuperTask' AssemblyFile='af' TaskFactory='AssemblyFactory'>
                           <ParameterGroup>
                              <MyParameter1 ParameterType='System.String' Output='true' Required='false'/>
                              <MyParameter2 ParameterType='System.String' Output='true' Required='false'/>
                           </ParameterGroup>
                       </UsingTask>

                        <UsingTask TaskName='LooserTask' AssemblyFile='af' TaskFactory='AssemblyFactory'>
                           <Task Evaluate='false'>Contents</Task>
                           <ParameterGroup>
                              <YourParameter1 ParameterType='System.String' Output='true' Required='false'/>
                              <YourParameter2 ParameterType='System.String' Output='true' Required='false'/>
                           </ParameterGroup>
                       </UsingTask>

                        <ImportGroup>
                            <Import Project='a.proj' />
                            <Import Project='b.proj' />
                        </ImportGroup>
                        <ImportGroup Condition='false'>
                            <Import Project='c.proj' />
                        </ImportGroup>


                        <PropertyGroup Condition=""'$(Configuration)'=='Foo'"">
                            <p>v1</p>
                        </PropertyGroup>
                        <PropertyGroup Condition=""'$(Configuration)'!='Foo'"">
                            <p>v2</p>
                        </PropertyGroup>
                        <PropertyGroup>
                            <p2>X$(p)</p2>
                        </PropertyGroup>
                        <ItemGroup>
                            <i Condition=""'$(Configuration)'=='Foo'"" Include='i0'/>
                            <i Include='i1'/>
                            <i Include='$(p)X;i3'/>
                            <i2 Include='item2' KeepDuplicates=""false"" KeepMetadata=""CopyToOutputDirectory;TargetPath"" RemoveMetadata=""xx""/>
                            <i2 Remove='item2'/>
                            <i2 Update='item2'/>
                        </ItemGroup>

                        <ItemGroup>
                            <src Condition=""'$(Configuration)'=='Foo'"" Include='foo.cs'/>
                            <src Include='foo2.cs'/>
                            <i4 Include='i' Exclude='j' m2='v2' />
                        </ItemGroup>

                        <ItemGroup Label=""Group1"">
                            <Compile Include=""Constants.cs"">
                                <ExcludeFromStyleCop>true</ExcludeFromStyleCop>
                            </Compile>
                            <Compile Include=""EncodingStringWriter.cs"">
                                <Link>EncodingStringWriter.cs</Link>
                            </Compile>
                            <Compile Include=""EncodingUtilities.cs"">
                                 <Link>EncodingUtilities.cs</Link>
                            </Compile>
                        </ItemGroup>

                        <ItemDefinitionGroup >
                            <i2 m1='v1'>
                                <m2 Condition='true'>v2</m2>
                                <m1>v3</m1>
                            </i2>
                        </ItemDefinitionGroup>

                        <ItemDefinitionGroup>
                            <i3 m1='v1'>
                                <m1>v3</m1>
                            </i3>
                            <i4 />
                        </ItemDefinitionGroup>

                        <Choose>
                            <When Condition=""'$(Configuration)'=='Foo'"">
                              <PropertyGroup>
                                <p>vFoo</p>
                              </PropertyGroup>
                            </When>
                            <When Condition='false'>
                              <PropertyGroup>
                                <p>vFalse</p>
                              </PropertyGroup>
                            </When>
                            <When Condition='true'>
                              <PropertyGroup>
                                <p>vTrue</p>
                              </PropertyGroup>
                            </When>
                            <Otherwise>
                              <PropertyGroup>
                                <p>vOtherwise</p>
                              </PropertyGroup>
                            </Otherwise>
                        </Choose>

                        <Target Name='t'>
                            <task/>
                        </Target>

                        <Target
                            Name=""Frankenstein""
                            Label=""Target1""
                            Returns=""@(_ProjectReferencesFromRAR);@(_ResolvedNativeProjectReferencePaths)""
                            Inputs=""@(_SourceItemsToCopyToOutputDirectory)""
                            BeforeTargets=""Compile""
                            AfterTargets=""Link""
                            Outputs=""@(_SourceItemsToCopyToOutputDirectory->'$(OutDir)%(TargetPath)')""
                            KeepDuplicateOutputs="" '$(MSBuildDisableGetCopyToOutputDirectoryItemsOptimization)' == '' ""
                            DependsOnTargets=""ResolveProjectReferences;ResolveAssemblyReferences"">

                            <ItemGroup>
                                <_ProjectReferencesFromRAR Include=""@(ReferencePath->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))"">
                                <OriginalItemSpec>%(ReferencePath.ProjectReferenceOriginalItemSpec)</OriginalItemSpec>
                                </_ProjectReferencesFromRAR>

                                <Compile Include=""Constants.cs"">
                                    <ExcludeFromStyleCop>true</ExcludeFromStyleCop>
                                </Compile>
                                <Compile Include=""EncodingStringWriter.cs"">
                                    <Link>EncodingStringWriter.cs</Link>
                                </Compile>

                            </ItemGroup>

                            <FindAppConfigFile PrimaryList=""@(None)"" SecondaryList=""@(Content)"" TargetPath=""$(TargetFileName).config"" Condition=""'$(AppConfig)'==''"">
                                <Output TaskParameter=""AppConfigFile"" ItemName=""AppConfigWithTargetPath""/>
                                <Output TaskParameter=""AppConfigFile"" PropertyName=""AppConfig""/>
                            </FindAppConfigFile>

                            <MakeDir Directories=""$(OutDir);$(IntermediateOutputPath);@(DocFileItem->'%(RelativeDir)');@(CreateDirectory)"" ContinueOnError=""True""/>

                            <OnError ExecuteTargets='1'/>
                            <OnError ExecuteTargets='2'/>
                        </Target>

                        <Sdk Name=""sdkName"" Version=""version"" MinimumVersion=""minVersion"" />

                       <ProjectExtensions>
                         <a>x</a>
                         <b>y</b>
                       </ProjectExtensions>
                    </Project>
                ");
        public int RemoteCount { get; }

        internal ProjectCollectionLinker.ConnectedProjectCollections Group { get; }
        internal ProjectCollectionLinker Local { get; }

        internal ProjectCollectionLinker[] Remote { get; } = new ProjectCollectionLinker[2];
        internal TransientIO Disk { get; }
        protected TransientIO ImmutableDisk { get; }
        public IReadOnlyList<string> StdProjectFiles { get; }

        private IReadOnlyDictionary<ProjectCollectionLinker, HashSet<Project>> ImmutableProjects { get; set; }

        protected void TakeSnapshot()
        {
            Assert.Null(this.ImmutableProjects);
            var result = new Dictionary<ProjectCollectionLinker, HashSet<Project>>();
            this.Local.Importing = false;
            result.Add(this.Local, new HashSet<Project>(this.Local.Collection.LoadedProjects));
            foreach (var r in this.Remote)
            {
                r.Importing = false;
                result.Add(r, new HashSet<Project>(r.Collection.LoadedProjects));
            }

            this.ImmutableProjects = result;
        }

        public TestCollectionGroup(int remoteCount, int stdFilesCount)
        {
            this.RemoteCount = 2;

            this.Group = ProjectCollectionLinker.CreateGroup();

            this.Local = this.Group.AddNew();
            this.Remote = new ProjectCollectionLinker[this.RemoteCount];
            for (int i = 0; i < this.RemoteCount; i++)
            {
                this.Remote[i] = this.Group.AddNew();
            }

            this.ImmutableDisk = new TransientIO();
            this.Disk = this.ImmutableDisk.GetSubFolder("Mutable");

            List<string> stdFiles = new List<string>();
            for (int i = 0; i < stdFilesCount; i++)
            {
                stdFiles.Add(this.ImmutableDisk.WriteProjectFile($"Proj{i}.proj", TestCollectionGroup.SampleProjectFile));
            }

            this.StdProjectFiles = stdFiles;
        }

        private void Clear(ProjectCollectionLinker linker)
        {
            linker.Importing = false;
            HashSet<Project> toKeep = null;
            this.ImmutableProjects?.TryGetValue(linker, out toKeep);
            if (toKeep == null)
            {
                linker.Collection.UnloadAllProjects();
            }
            else
            {
                var toUnload = new List<Project>();
                foreach (var p in linker.Collection.LoadedProjects)
                {
                    if (!toKeep.Contains(p))
                    {
                        toUnload.Add(p);
                    }
                }

                foreach (var p in toUnload)
                {
                    linker.Collection.UnloadProject(p);
                    linker.Collection.UnloadProject(p.Xml);
                }
            }
        }
        public void Clear()
        {
            this.Clear(this.Local);
            foreach (var remote in this.Remote)
            {
                this.Clear(remote);
            }

            this.Group.ClearAllRemotes();
            this.Disk.Clear();
        }

        public void Dispose()
        {
            this.Clear();
            this.ImmutableDisk.Dispose();
        }
    }
}
