// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectJsonMigration;
using Microsoft.DotNet.Internal.ProjectModel;
using Microsoft.DotNet.Tools.Test.Utilities;
using NuGet.Frameworks;
using Xunit;
using FluentAssertions;
using Microsoft.DotNet.ProjectJsonMigration.Rules;
using Microsoft.DotNet.ProjectJsonMigration.Tests;

namespace Microsoft.DotNet.ProjectJsonMigration.Tests
{
    public class GivenThatIWantToMigrateConfigurations : TestBase
    {
        [Fact]
        public void ConfigurationBuildOptionsProduceExpectedPropertiesInAGroupWithACondition()
        {
            var mockProj = RunConfigurationsRuleOnPj(@"
                {
                    ""configurations"": {
                        ""testconfig"": {
                            ""buildOptions"": {
                                ""emitEntryPoint"": ""true"",
                                ""debugType"": ""full""
                            }
                        }
                    }
                }");

            mockProj.Properties.Count(
                prop => prop.Name == "OutputType" || prop.Name == "DebugType").Should().Be(2);

            mockProj.Properties.First(p => p.Name == "OutputType")
                .Parent.Condition.Should()
                .Contain("'$(Configuration)' == 'testconfig'");
        }

        [Fact]
        public void FrameworksBuildOptionsProduceExpectedPropertiesInAGroupWithACondition()
        {
            var mockProj = RunConfigurationsRuleOnPj(@"
                {
                    ""frameworks"": {
                        ""netcoreapp1.0"": {
                            ""buildOptions"": {
                                ""emitEntryPoint"": ""true"",
                                ""debugType"": ""full""
                            }
                        }
                    }
                }");

            mockProj.Properties.Count(
                prop => prop.Name == "OutputType" || prop.Name == "DebugType").Should().Be(2);

            mockProj.Properties.First(p => p.Name == "OutputType")
                .Parent.Condition.Should()
                .Contain("'$(TargetFramework)' == 'netcoreapp1.0'");
        }

        [Fact]
        public void ItDoesNotAddADefineForTheFramework()
        {
            var mockProj = RunConfigurationsRuleOnPj(@"
                {
                    ""frameworks"": {
                        ""netcoreapp1.0"": {
                        }
                    }
                }");

            mockProj.Properties.Count(
                prop => prop.Name == "DefineConstants" && prop.Value.Contains("NETCOREAPP10")).Should().Be(0);
        }

        [Fact]
        public void ItDoesNotAddADefineForReleaseConfiguration()
        {
            var mockProj = RunRulesOnPj(@"
                {
                    ""frameworks"": {
                        ""netcoreapp1.0"": {
                        }
                    }
                }",
                new IMigrationRule[]
                {
                    new AddDefaultsToProjectRule(),
                    new MigrateConfigurationsRule(),
                    new RemoveDefaultsFromProjectRule()
                });

            mockProj.Properties.Count(
                prop => prop.Name == "DefineConstants" && prop.Value.Contains("Release")).Should().Be(0);
        }

        [Fact]
        public void ItDoesNotAddADefineForDebugConfiguration()
        {
            var mockProj = RunRulesOnPj(@"
                {
                    ""frameworks"": {
                        ""netcoreapp1.0"": {
                        }
                    }
                }",
                new IMigrationRule[]
                {
                    new AddDefaultsToProjectRule(),
                    new MigrateConfigurationsRule(),
                    new RemoveDefaultsFromProjectRule()
                });

            mockProj.Properties.Count(
                prop => prop.Name == "DefineConstants" && prop.Value.Contains("Debug")).Should().Be(0);
        }

        [Fact]
        public void ConfigurationBuildOptionsPropertiesAreNotWrittenWhenTheyOverlapWithBuildOptions()
        {
            var mockProj = RunConfigurationsAndBuildOptionsRuleOnPj(@"
                {
                    ""buildOptions"": {
                        ""emitEntryPoint"": ""true"",
                        ""debugType"": ""full""
                    },
                    ""configurations"": {
                        ""testconfig"": {
                            ""buildOptions"": {
                                ""emitEntryPoint"": ""true"",
                                ""debugType"": ""full""
                            }
                        }
                    }
                }");

            mockProj.Properties.Count(property =>
                    property.Name == "OutputType" || property.Name == "DebugType")
                .Should().Be(2);

            foreach (var property in mockProj.Properties.Where(property =>
                    property.Name == "OutputType" || property.Name == "DebugType"))
            {
                property.Parent.Condition.Should().Be(string.Empty);
            }
        }

        [Fact]
        public void ConfigurationBuildOptionsIncludesAndRemoveAreWrittenWhenTheyDifferFromBaseBuildOptions()
        {
            var mockProj = RunConfigurationsAndBuildOptionsRuleOnPj(@"
                {
                    ""buildOptions"": {
                        ""copyToOutput"": {
                            ""include"": [""src""],
                            ""exclude"": [""anothersource"", ""rootfile.cs""],
                            ""includeFiles"": [""src/file1.cs"", ""src/file2.cs""],
                            ""excludeFiles"": [""anothersource/file2.cs""]
                        }
                    },
                    ""configurations"": {
                        ""testconfig"": {
                            ""buildOptions"": {
                                ""copyToOutput"": {
                                    ""include"": [""root"", ""src"", ""rootfile.cs""],
                                    ""exclude"": [""anothersource"", ""root/rootfile.cs""],
                                    ""includeFiles"": [""src/file1.cs"", ""src/file2.cs""],
                                    ""excludeFiles"": [""src/file3.cs""]
                                }
                            }
                        }
                    }
                }");

            var contentItems = mockProj.Items.Where(item => item.ItemType == "None");

            contentItems.Count().Should().Be(8);

            contentItems.Where(i => i.ConditionChain().Count() == 0).Should().HaveCount(4);

            contentItems.Where(i =>
                i.ConditionChain().Count() == 0 &&
                i.Metadata.Any(m => m.Value == "PreserveNewest")).Should().HaveCount(2);
            contentItems.Where(i =>
                i.ConditionChain().Count() == 0 &&
                i.Metadata.Any(m => m.Value == "Never")).Should().HaveCount(2);

            contentItems.Where(i => i.ConditionChain().Count() == 1).Should().HaveCount(4);
            contentItems.Where(i =>
                i.ConditionChain().Count() == 1 &&
                i.Metadata.Any(m => m.Value == "PreserveNewest")).Should().HaveCount(1);
            contentItems.Where(i =>
                i.ConditionChain().Count() == 1 &&
                i.Metadata.Any(m => m.Value == "Never")).Should().HaveCount(2);

            contentItems.First(i =>
                i.ConditionChain().Count() == 1 &&
                i.Metadata.Any(m => m.Value == "PreserveNewest") &&
                !string.IsNullOrEmpty(i.Update)).Update.Should().Be(@"root;rootfile.cs");

            contentItems.First(i =>
                i.ConditionChain().Count() == 1 &&
                i.Metadata.Any(m => m.Value == "Never") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Contains(@"src\file3.cs")).Update.Should().Be(@"src\file3.cs");

            contentItems.First(i =>
                i.ConditionChain().Count() == 1 &&
                i.Metadata.Any(m => m.Value == "Never") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Contains(@"root\rootfile.cs")).Update.Should().Be(@"root\rootfile.cs");

            contentItems.First(i =>
                i.ConditionChain().Count() == 0 &&
                i.Metadata.Any(m => m.Value == "PreserveNewest") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Contains(@"src\file1.cs")).Update.Should().Be(@"src\file1.cs;src\file2.cs");

            contentItems.First(i =>
                i.ConditionChain().Count() == 0 &&
                i.Metadata.Any(m => m.Value == "PreserveNewest") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Equals(@"src")).Update.Should().Be(@"src");

            contentItems.First(i =>
                i.ConditionChain().Count() == 0 &&
                i.Metadata.Any(m => m.Value == "Never") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Equals(@"anothersource\file2.cs")).Update.Should().Be(@"anothersource\file2.cs");

            contentItems.First(i =>
                i.ConditionChain().Count() == 0 &&
                i.Metadata.Any(m => m.Value == "Never") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Equals(@"anothersource;rootfile.cs")).Update.Should().Be(@"anothersource;rootfile.cs");
        }

        [Fact]
        public void ConfigurationBuildOptionsWhichHaveDifferentExcludesThanBuildOptionsOverwrites()
        {
            var mockProj = RunConfigurationsAndBuildOptionsRuleOnPj(@"
                {
                    ""buildOptions"": {
                        ""copyToOutput"": {
                            ""include"": [""src""],
                            ""exclude"": [""rootfile.cs""],
                            ""includeFiles"": [""src/file1.cs"", ""src/file2.cs""],
                            ""excludeFiles"": [""src/file3.cs""]
                        }
                    },
                    ""configurations"": {
                        ""testconfig"": {
                            ""buildOptions"": {
                                ""copyToOutput"": {
                                    ""include"": [""root"", ""anothersource"", ""rootfile.cs""],
                                    ""exclude"": [""rootfile.cs"", ""someotherfile.cs""],
                                    ""includeFiles"": [""src/file1.cs"", ""src/file2.cs""],
                                    ""excludeFiles"": [""src/file2.cs""]
                                }
                            }
                        }
                    }
                }");

            var contentItems = mockProj.Items.Where(item => item.ItemType == "None");

            contentItems.Count().Should().Be(9);
            contentItems.Where(i => i.ConditionChain().Count() == 0).Should().HaveCount(4);
            contentItems.Where(i => i.ConditionChain().Count() == 1).Should().HaveCount(5);

            contentItems.Where(i =>
                i.ConditionChain().Count() == 0 &&
                i.Metadata.Any(m => m.Value == "PreserveNewest")).Should().HaveCount(2);
            contentItems.Where(i =>
                i.ConditionChain().Count() == 0 &&
                i.Metadata.Any(m => m.Value == "Never")).Should().HaveCount(2);

            contentItems.Where(i =>
                i.ConditionChain().Count() == 1 &&
                i.Metadata.Any(m => m.Value == "PreserveNewest")).Should().HaveCount(1);
            contentItems.Where(i =>
                i.ConditionChain().Count() == 1 &&
                i.Metadata.Any(m => m.Value == "Never")).Should().HaveCount(3);

            contentItems.First(i =>
                i.ConditionChain().Count() == 0 &&
                i.Metadata.Any(m => m.Value == "PreserveNewest") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Contains(@"src\file1.cs")).Update.Should().Be(@"src\file1.cs;src\file2.cs");

            contentItems.First(i =>
                i.ConditionChain().Count() == 0 &&
                i.Metadata.Any(m => m.Value == "PreserveNewest") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Equals(@"src")).Update.Should().Be(@"src");

            contentItems.First(i =>
                i.ConditionChain().Count() == 0 &&
                i.Metadata.Any(m => m.Value == "Never") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Equals(@"src\file3.cs")).Update.Should().Be(@"src\file3.cs");

            contentItems.First(i =>
                i.ConditionChain().Count() == 0 &&
                i.Metadata.Any(m => m.Value == "Never") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Equals(@"rootfile.cs")).Update.Should().Be(@"rootfile.cs");

            contentItems.First(i =>
                i.ConditionChain().Count() == 1 &&
                i.Metadata.Any(m => m.Value == "PreserveNewest") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Contains(@"root;anothersource")).Update.Should().Be(@"root;anothersource");

            contentItems.First(i =>
                i.ConditionChain().Count() == 1 &&
                i.Metadata.Any(m => m.Value == "Never") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Contains(@"src\file2.cs")).Update.Should().Be(@"src\file2.cs");

            contentItems.First(i =>
                i.ConditionChain().Count() == 1 &&
                i.Metadata.Any(m => m.Value == "Never") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Contains(@"rootfile.cs")).Update.Should().Be(@"rootfile.cs");

            contentItems.First(i =>
                i.ConditionChain().Count() == 1 &&
                i.Metadata.Any(m => m.Value == "Never") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Contains(@"someotherfile.cs")).Update.Should().Be(@"someotherfile.cs");
        }

        [Fact]
        public void ConfigurationBuildOptionsWhichHaveMappingsToDirectoryAddLinkMetadataWithItemMetadata()
        {
            var mockProj = RunConfigurationsAndBuildOptionsRuleOnPj(@"
                {
                    ""configurations"": {
                        ""testconfig"": {
                            ""buildOptions"": {
                                ""copyToOutput"": {
                                    ""mappings"": {
                                        ""/some/dir/"" : {
                                            ""include"": [""src"", ""root""],
                                            ""exclude"": [""src"", ""rootfile.cs""],
                                            ""includeFiles"": [""src/file1.cs"", ""src/file2.cs""],
                                            ""excludeFiles"": [""src/file2.cs""]
                                        }
                                    }
                                }
                            }
                        }
                    }
                }");

            var contentItems = mockProj.Items.Where(item => item.ItemType == "None");

            contentItems.Count().Should().Be(5);
            contentItems.Where(i => i.ConditionChain().Count() == 1).Should().HaveCount(5);

            var configIncludeContentItem = contentItems.First(item => item.Update.Contains("root"));
            var configIncludeContentItem2 = contentItems.First(item => item.Update.Contains(@"src\file1.cs"));
            var configIncludeContentItem3 = contentItems.First(item => item.Update.Contains(@"src\file2.cs"));
            var configIncludeContentItem4 = contentItems.First(item => item.Update.Equals(@"src"));
            var configIncludeContentItem5 = contentItems.First(item => item.Update.Contains(@"rootfile.cs"));

            configIncludeContentItem.Updates().Should().BeEquivalentTo("root");
            configIncludeContentItem.GetMetadataWithName("Link").Should().NotBeNull();
            configIncludeContentItem.GetMetadataWithName("Link").Value.Should().Be("/some/dir/%(FileName)%(Extension)");
            configIncludeContentItem.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
            configIncludeContentItem.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("PreserveNewest");

            configIncludeContentItem2.Updates().Should().BeEquivalentTo(@"src\file1.cs");
            configIncludeContentItem2.GetMetadataWithName("Link").Should().NotBeNull();
            configIncludeContentItem2.GetMetadataWithName("Link").Value.Should().Be("/some/dir/%(FileName)%(Extension)");
            configIncludeContentItem2.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
            configIncludeContentItem2.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("PreserveNewest");

            configIncludeContentItem3.Updates().Should().BeEquivalentTo(@"src\file2.cs");
            configIncludeContentItem3.GetMetadataWithName("Link").Should().NotBeNull();
            configIncludeContentItem3.GetMetadataWithName("Link").Value.Should().Be("/some/dir/%(FileName)%(Extension)");
            configIncludeContentItem3.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
            configIncludeContentItem3.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("Never");

            configIncludeContentItem4.Updates().Should().BeEquivalentTo(@"src");
            configIncludeContentItem4.GetMetadataWithName("Link").Should().NotBeNull();
            configIncludeContentItem4.GetMetadataWithName("Link").Value.Should().Be("/some/dir/%(FileName)%(Extension)");
            configIncludeContentItem4.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
            configIncludeContentItem4.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("Never");

            configIncludeContentItem5.Updates().Should().BeEquivalentTo(@"rootfile.cs");
            configIncludeContentItem5.GetMetadataWithName("Link").Should().NotBeNull();
            configIncludeContentItem5.GetMetadataWithName("Link").Value.Should().Be("/some/dir/%(FileName)%(Extension)");
            configIncludeContentItem5.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
            configIncludeContentItem5.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("Never");
        }

        [Fact]
        public void ConfigurationBuildOptionsWhichHaveMappingsOverlappingWithIncludesInSameConfigurationMergedItemsHaveLinkMetadata()
        {
            var mockProj = RunConfigurationsAndBuildOptionsRuleOnPj(@"
                {
                    ""configurations"": {
                        ""testconfig"": {
                            ""buildOptions"": {
                                ""copyToOutput"": {
                                    ""include"": [""src"", ""root""],
                                    ""exclude"": [""src"", ""rootfile.cs""],
                                    ""includeFiles"": [""src/file1.cs""],
                                    ""excludeFiles"": [""src/file2.cs""],
                                    ""mappings"": {
                                        ""/some/dir/"" : {
                                            ""include"": [""src""],
                                            ""exclude"": [""src"", ""src/rootfile.cs""]
                                        }
                                    }
                                }
                            }
                        }
                    }
                }");

            var contentItems = mockProj.Items.Where(item => item.ItemType == "None");

            contentItems.Count().Should().Be(6);
            contentItems.Where(i => i.ConditionChain().Count() == 1).Should().HaveCount(6);

            var configIncludeContentItem = contentItems.First(
                item => item.ConditionChain().Count() > 0
                    && item.Update == "root");
            
            var configIncludeContentItem2 = contentItems.First(
                item => item.ConditionChain().Count() > 0
                    && item.Update == "src");

            var configIncludeContentItem3 = contentItems.First(
                item => item.ConditionChain().Count() > 0
                    && item.Update.Contains(@"src\file1.cs"));

            var configIncludeContentItem4 = contentItems.First(
                item => item.ConditionChain().Count() > 0
                    && item.Update.Contains(@"src\file2.cs"));

            var configIncludeContentItem5 = contentItems.First(
                item => item.ConditionChain().Count() > 0
                    && item.Update.Equals(@"rootfile.cs"));

            var configIncludeContentItem6 = contentItems.First(
                item => item.ConditionChain().Count() > 0
                    && item.Update.Equals(@"src\rootfile.cs"));

            configIncludeContentItem.Updates().Should().BeEquivalentTo("root");
            configIncludeContentItem.GetMetadataWithName("Link").Should().BeNull();
            configIncludeContentItem.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
            configIncludeContentItem.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("PreserveNewest");

            configIncludeContentItem2.Update.Should().Be("src");
            configIncludeContentItem2.GetMetadataWithName("Link").Should().NotBeNull();
            configIncludeContentItem2.GetMetadataWithName("Link").Value.Should().Be("/some/dir/%(FileName)%(Extension)");
            configIncludeContentItem2.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
            configIncludeContentItem2.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("Never");

            configIncludeContentItem3.Updates().Should().BeEquivalentTo(@"src\file1.cs");
            configIncludeContentItem3.GetMetadataWithName("Link").Should().BeNull();
            configIncludeContentItem3.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
            configIncludeContentItem3.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("PreserveNewest");

            configIncludeContentItem4.Updates().Should().BeEquivalentTo(@"src\file2.cs");
            configIncludeContentItem4.GetMetadataWithName("Link").Should().BeNull();
            configIncludeContentItem4.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
            configIncludeContentItem4.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("Never");

            configIncludeContentItem5.Updates().Should().BeEquivalentTo(@"rootfile.cs");
            configIncludeContentItem5.GetMetadataWithName("Link").Should().BeNull();
            configIncludeContentItem5.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
            configIncludeContentItem5.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("Never");

            configIncludeContentItem6.Updates().Should().BeEquivalentTo(@"src\rootfile.cs");
            configIncludeContentItem6.GetMetadataWithName("Link").Value.Should().Be("/some/dir/%(FileName)%(Extension)");
            configIncludeContentItem6.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
            configIncludeContentItem6.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("Never");
        }

        [Fact]
        public void ConfigurationBuildOptionsWhichHaveMappingsOverlappingWithIncludesInRootBuildoptionsHasRemove()
        {
            var mockProj = RunConfigurationsAndBuildOptionsRuleOnPj(@"
                {
                    ""buildOptions"" : {
                        ""copyToOutput"": {
                            ""include"": [""src"", ""root""],
                            ""exclude"": [""src"", ""rootfile.cs""],
                            ""includeFiles"": [""src/file1.cs"", ""src/file2.cs""],
                            ""excludeFiles"": [""src/file2.cs""]
                        }
                    },
                    ""configurations"": {
                        ""testconfig"": {
                            ""buildOptions"": {
                                ""copyToOutput"": {
                                    ""mappings"": {
                                        ""/some/dir/"" : {
                                            ""include"": [""src""],
                                            ""exclude"": [""src"", ""rootfile.cs""]
                                        }
                                    }
                                }
                            }
                        }
                    }
                }");

            var contentItems = mockProj.Items.Where(item => item.ItemType == "None");

            contentItems.Count().Should().Be(8);
            
            var rootBuildOptionsContentItems = contentItems.Where(i => i.ConditionChain().Count() == 0).ToList();
            rootBuildOptionsContentItems.Count().Should().Be(5);
            foreach (var buildOptionContentItem in rootBuildOptionsContentItems)
            {
                buildOptionContentItem.GetMetadataWithName("Link").Should().BeNull();
            }

            contentItems.First(i =>
                i.ConditionChain().Count() == 0 &&
                i.Metadata.Any(m => m.Value == "PreserveNewest") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Equals(@"src\file1.cs")).Update.Should().Be(@"src\file1.cs");

            contentItems.First(i =>
                i.ConditionChain().Count() == 0 &&
                i.Metadata.Any(m => m.Value == "PreserveNewest") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Equals(@"root")).Update.Should().Be(@"root");

            contentItems.First(i =>
                i.ConditionChain().Count() == 0 &&
                i.Metadata.Any(m => m.Value == "Never") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Equals(@"src\file2.cs")).Update.Should().Be(@"src\file2.cs");

            contentItems.First(i =>
                i.ConditionChain().Count() == 0 &&
                i.Metadata.Any(m => m.Value == "Never") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Equals(@"src")).Update.Should().Be(@"src");

            contentItems.First(i =>
                i.ConditionChain().Count() == 0 &&
                i.Metadata.Any(m => m.Value == "Never") &&
                !string.IsNullOrEmpty(i.Update) &&
                i.Update.Equals(@"rootfile.cs")).Update.Should().Be(@"rootfile.cs");

            var configItems = contentItems.Where(i => i.ConditionChain().Count() == 1);
            configItems.Should().HaveCount(3);

            var configIncludeContentItem = contentItems.First(
                item => item.ConditionChain().Count() > 0
                    && item.Update.Contains("src"));

            var configRemoveContentItem = contentItems.First(
                item => item.ConditionChain().Count() > 0
                    && !string.IsNullOrEmpty(item.Remove));

            configIncludeContentItem.Update.Should().Be("src");

            configIncludeContentItem.GetMetadataWithName("Link").Should().NotBeNull();
            configIncludeContentItem.GetMetadataWithName("Link").Value.Should().Be("/some/dir/%(FileName)%(Extension)");

            configIncludeContentItem.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
            configIncludeContentItem.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("Never");

            configRemoveContentItem.Remove.Should().Be("src;rootfile.cs");
        }

        [Fact]
        public void ConfigurationBuildOptionsWhichHaveMappingsOverlappingWithIncludesInSameConfigurationAndRootBuildOptionsHaveRemovesAndLinkMetadataAndEncompassedItemsAreMerged()
        {
            var mockProj = RunConfigurationsAndBuildOptionsRuleOnPj(@"
                {
                    ""buildOptions"" : {
                        ""copyToOutput"": {
                            ""include"": [""src"", ""root""],
                            ""exclude"": [""src"", ""rootfile.cs""],
                            ""includeFiles"": [""src/file1.cs""],
                            ""excludeFiles"": [""src/file2.cs""]
                        }
                    },
                    ""configurations"": {
                        ""testconfig"": {
                            ""buildOptions"": {
                                ""copyToOutput"": {
                                    ""include"": [""src"", ""root""],
                                    ""exclude"": [""src"", ""rootfile.cs""],
                                    ""includeFiles"": [""src/file3.cs""],
                                    ""excludeFiles"": [""src/file2.cs""],
                                    ""mappings"": {
                                        ""/some/dir/"" : {
                                            ""include"": [""src""],
                                            ""exclude"": [""src"", ""src/rootfile.cs""]
                                        }
                                    }
                                }
                            }
                        }
                    }
                }");

            var contentItems = mockProj.Items.Where(item => item.ItemType == "None");

            contentItems.Count().Should().Be(9);
            contentItems.Where(i => i.ConditionChain().Count() == 1).Should().HaveCount(4);

            var rootBuildOptionsContentItems = contentItems.Where(i => i.ConditionChain().Count() == 0).ToList();
            rootBuildOptionsContentItems.Count().Should().Be(5);
            foreach (var buildOptionContentItem in rootBuildOptionsContentItems)
            {
                buildOptionContentItem.GetMetadataWithName("Link").Should().BeNull();
            }

            var configIncludeEncompassedItem = contentItems.FirstOrDefault(
                item => item.ConditionChain().Count() > 0
                    && item.Update == "root");
            configIncludeEncompassedItem.Should().BeNull();

            var configIncludeContentItem = contentItems.First(
                item => item.ConditionChain().Count() > 0
                    && item.Update == "src");

            var configIncludeContentItem2 = contentItems.First(
                item => item.ConditionChain().Count() > 0
                    && item.Update.Contains(@"src\file3.cs"));

            var configRemoveContentItem = contentItems.First(
                item => item.ConditionChain().Count() > 0
                    && !string.IsNullOrEmpty(item.Remove));

            configIncludeContentItem.Update.Should().Be("src");
            configIncludeContentItem.GetMetadataWithName("Link").Should().NotBeNull();
            configIncludeContentItem.GetMetadataWithName("Link").Value.Should().Be("/some/dir/%(FileName)%(Extension)");
            configIncludeContentItem.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
            configIncludeContentItem.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("Never");

            configIncludeContentItem2.Updates().Should().BeEquivalentTo(@"src\file3.cs");
            configIncludeContentItem2.GetMetadataWithName("Link").Should().BeNull();
            configIncludeContentItem2.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
            configIncludeContentItem2.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("PreserveNewest");

            configRemoveContentItem.Removes().Should().BeEquivalentTo("src");
        }

        private ProjectRootElement RunConfigurationsRuleOnPj(string s, string testDirectory = null)
        {
            testDirectory = testDirectory ?? Temp.CreateDirectory().Path;
            return RunRulesOnPj(s, new[] { new MigrateConfigurationsRule() }, testDirectory);
        }

        private ProjectRootElement RunConfigurationsAndBuildOptionsRuleOnPj(string s, string testDirectory = null)
        {
            return RunRulesOnPj(
                s,
                new IMigrationRule[]
                {
                    new MigrateBuildOptionsRule(),
                    new MigrateConfigurationsRule()
                },
                testDirectory);
        }

        private ProjectRootElement RunRulesOnPj(string s, IMigrationRule[] migrationRules, string testDirectory = null)
        {
            testDirectory = testDirectory ?? Temp.CreateDirectory().Path;
            return TemporaryProjectFileRuleRunner.RunRules(migrationRules, s, testDirectory);
        }
    }
}
