﻿// 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.IO;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.UnitTests.FileSystem
{
    public class PathUtilitiesTests
    {
        private void TestGetDirectoryNameAndCompareToDotnet(string expectedDirectoryName, string fullPath)
        {
            var roslynName = PathUtilities.TestAccessor.GetDirectoryName(fullPath, isUnixLike: false);
            Assert.Equal(expectedDirectoryName, roslynName);

            var dotnetName = Path.GetDirectoryName(fullPath);
            Assert.Equal(dotnetName, roslynName);
        }

        [ConditionalFact(typeof(WindowsOnly))]
        public void TestGetDirectoryName_WindowsPaths_Absolute()
        {
            TestGetDirectoryNameAndCompareToDotnet(@"C:\temp", @"C:\temp\goo.txt");
            TestGetDirectoryNameAndCompareToDotnet(@"C:\temp", @"C:\temp\goo");
            TestGetDirectoryNameAndCompareToDotnet(@"C:\temp", @"C:\temp\");
            TestGetDirectoryNameAndCompareToDotnet(@"C:\", @"C:\temp");
            TestGetDirectoryNameAndCompareToDotnet(null, @"C:\");
            TestGetDirectoryNameAndCompareToDotnet(null, @"C:");

            // dotnet throws on empty argument.  But not on null... go figure.
            Assert.Null(PathUtilities.TestAccessor.GetDirectoryName(@"", isUnixLike: false));

            TestGetDirectoryNameAndCompareToDotnet(null, null);
        }

        [ConditionalFact(typeof(WindowsOnly))]
        public void TestGetDirectoryName_WindowsPaths_Relative()
        {
            TestGetDirectoryNameAndCompareToDotnet(@"goo\temp", @"goo\temp\goo.txt");
            TestGetDirectoryNameAndCompareToDotnet(@"goo\temp", @"goo\temp\goo");
            TestGetDirectoryNameAndCompareToDotnet(@"goo\temp", @"goo\temp\");
            TestGetDirectoryNameAndCompareToDotnet(@"goo", @"goo\temp");
            TestGetDirectoryNameAndCompareToDotnet(@"goo", @"goo\");
            TestGetDirectoryNameAndCompareToDotnet("", @"goo");
        }

        [Fact]
        public void TestGetDirectoryName_UnixPaths_Absolute()
        {
            Assert.Equal(
                @"/temp",
                PathUtilities.TestAccessor.GetDirectoryName(@"/temp/goo.txt", isUnixLike: true));

            Assert.Equal(
                @"/temp",
                PathUtilities.TestAccessor.GetDirectoryName(@"/temp/goo", isUnixLike: true));

            Assert.Equal(
                @"/temp",
                PathUtilities.TestAccessor.GetDirectoryName(@"/temp/", isUnixLike: true));

            Assert.Equal(
                @"/",
                PathUtilities.TestAccessor.GetDirectoryName(@"/temp", isUnixLike: true));

            Assert.Null(
                PathUtilities.TestAccessor.GetDirectoryName(@"/", isUnixLike: true));

            Assert.Null(
                PathUtilities.TestAccessor.GetDirectoryName(@"", isUnixLike: true));

            Assert.Null(
                PathUtilities.TestAccessor.GetDirectoryName(null, isUnixLike: true));
        }

        [Fact]
        public void TestGetDirectoryName_UnixPaths_Relative()
        {
            Assert.Equal(
                @"goo/temp",
                PathUtilities.TestAccessor.GetDirectoryName(@"goo/temp/goo.txt", isUnixLike: true));

            Assert.Equal(
                @"goo/temp",
                PathUtilities.TestAccessor.GetDirectoryName(@"goo/temp/goo", isUnixLike: true));

            Assert.Equal(
                @"goo/temp",
                PathUtilities.TestAccessor.GetDirectoryName(@"goo/temp/", isUnixLike: true));

            Assert.Equal(
                @"goo",
                PathUtilities.TestAccessor.GetDirectoryName(@"goo/temp", isUnixLike: true));

            Assert.Equal(
                @"goo",
                PathUtilities.TestAccessor.GetDirectoryName(@"goo/", isUnixLike: true));

            Assert.Equal(
                "",
                PathUtilities.TestAccessor.GetDirectoryName(@"goo", isUnixLike: true));

            Assert.Null(
                PathUtilities.TestAccessor.GetDirectoryName(@"", isUnixLike: true));

            Assert.Null(
                PathUtilities.TestAccessor.GetDirectoryName(null, isUnixLike: true));
        }

        [ConditionalFact(typeof(WindowsOnly))]
        public void TestGetDirectoryName_WindowsSharePaths()
        {
            TestGetDirectoryNameAndCompareToDotnet(@"\\server\temp", @"\\server\temp\goo.txt");
            TestGetDirectoryNameAndCompareToDotnet(@"\\server\temp", @"\\server\temp\goo");
            TestGetDirectoryNameAndCompareToDotnet(@"\\server\temp", @"\\server\temp\");
            TestGetDirectoryNameAndCompareToDotnet(null, @"\\server\temp");
            TestGetDirectoryNameAndCompareToDotnet(null, @"\\server\");
            TestGetDirectoryNameAndCompareToDotnet(null, @"\\server");
            TestGetDirectoryNameAndCompareToDotnet(null, @"\\");
            TestGetDirectoryNameAndCompareToDotnet(null, @"\");
        }

        [ConditionalFact(typeof(WindowsOnly))]
        public void TestGetDirectoryName_EsotericCases()
        {
            TestGetDirectoryNameAndCompareToDotnet(@"C:\temp", @"C:\temp\\goo.txt");
            TestGetDirectoryNameAndCompareToDotnet(@"C:\temp", @"C:\temp\\\goo.txt");

            // Dotnet does normalization of dots, so we can't compare against it here.
            Assert.Equal(
                @"C:\temp\..",
                PathUtilities.TestAccessor.GetDirectoryName(@"C:\temp\..\goo.txt", isUnixLike: false));

            Assert.Equal(
                @"C:\temp",
                PathUtilities.TestAccessor.GetDirectoryName(@"C:\temp\..", isUnixLike: false));

            Assert.Equal(
                @"C:\temp\.",
                PathUtilities.TestAccessor.GetDirectoryName(@"C:\temp\.\goo.txt", isUnixLike: false));

            Assert.Equal(
                @"C:\temp",
                PathUtilities.TestAccessor.GetDirectoryName(@"C:\temp\.", isUnixLike: false));

            TestGetDirectoryNameAndCompareToDotnet(@"C:temp", @"C:temp\\goo.txt");
            TestGetDirectoryNameAndCompareToDotnet(@"C:temp", @"C:temp\\\goo.txt");

            Assert.Equal(
                @"C:temp\..",
                PathUtilities.TestAccessor.GetDirectoryName(@"C:temp\..\goo.txt", isUnixLike: false));

            Assert.Equal(
                @"C:temp",
                PathUtilities.TestAccessor.GetDirectoryName(@"C:temp\..", isUnixLike: false));

            Assert.Equal(
                @"C:temp\.",
                PathUtilities.TestAccessor.GetDirectoryName(@"C:temp\.\goo.txt", isUnixLike: false));

            Assert.Equal(
                @"C:temp",
                PathUtilities.TestAccessor.GetDirectoryName(@"C:temp\.", isUnixLike: false));

            TestGetDirectoryNameAndCompareToDotnet(@"C:temp", @"C:temp\");
            TestGetDirectoryNameAndCompareToDotnet(@"C:", @"C:temp");
            TestGetDirectoryNameAndCompareToDotnet(null, @"C:");
        }

        [ConditionalFact(typeof(WindowsOnly))]
        public void TestContainsPathComponent()
        {
            Assert.True(
                PathUtilities.ContainsPathComponent(@"c:\packages\temp", "packages", ignoreCase: true));
            Assert.True(
                PathUtilities.ContainsPathComponent(@"\\server\packages\temp", "packages", ignoreCase: true));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"\\packages\temp", "packages", ignoreCase: true));
            Assert.True(
                PathUtilities.ContainsPathComponent(@"c:\packages", "packages", ignoreCase: true));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"c:\packages1\temp", "packages", ignoreCase: true));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"c:\package\temp", "packages", ignoreCase: true));

            Assert.True(
                PathUtilities.ContainsPathComponent(@"c:\packages\temp", "packages", ignoreCase: false));
            Assert.True(
                PathUtilities.ContainsPathComponent(@"\\server\packages\temp", "packages", ignoreCase: false));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"\\packages\temp", "packages", ignoreCase: false));
            Assert.True(
                PathUtilities.ContainsPathComponent(@"c:\packages", "packages", ignoreCase: false));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"c:\packages1\temp", "packages", ignoreCase: false));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"c:\package\temp", "packages", ignoreCase: false));

            Assert.True(
                PathUtilities.ContainsPathComponent(@"c:\packages\temp", "Packages", ignoreCase: true));
            Assert.True(
                PathUtilities.ContainsPathComponent(@"\\server\packages\temp", "Packages", ignoreCase: true));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"\\packages\temp", "Packages", ignoreCase: true));
            Assert.True(
                PathUtilities.ContainsPathComponent(@"c:\packages", "Packages", ignoreCase: true));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"c:\packages1\temp", "Packages", ignoreCase: true));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"c:\package\temp", "Packages", ignoreCase: true));

            Assert.False(
                PathUtilities.ContainsPathComponent(@"c:\packages\temp", "Packages", ignoreCase: false));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"\\server\packages\temp", "Packages", ignoreCase: false));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"\\packages\temp", "Packages", ignoreCase: false));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"c:\packages", "Packages", ignoreCase: false));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"c:\packages1\temp", "Packages", ignoreCase: false));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"c:\package\temp", "Packages", ignoreCase: false));

            Assert.True(
                PathUtilities.ContainsPathComponent(@"c:\Packages\temp", "packages", ignoreCase: true));
            Assert.True(
                PathUtilities.ContainsPathComponent(@"\\server\Packages\temp", "packages", ignoreCase: true));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"\\Packages\temp", "packages", ignoreCase: true));
            Assert.True(
                PathUtilities.ContainsPathComponent(@"c:\Packages", "packages", ignoreCase: true));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"c:\Packages1\temp", "packages", ignoreCase: true));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"c:\Package\temp", "packages", ignoreCase: true));

            Assert.False(
                PathUtilities.ContainsPathComponent(@"c:\Packages\temp", "packages", ignoreCase: false));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"\\server\Packages\temp", "packages", ignoreCase: false));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"\\Packages\temp", "packages", ignoreCase: false));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"c:\Packages", "packages", ignoreCase: false));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"c:\Packages1\temp", "packages", ignoreCase: false));
            Assert.False(
                PathUtilities.ContainsPathComponent(@"c:\Package\temp", "packages", ignoreCase: false));
        }

        [ConditionalFact(typeof(WindowsOnly))]
        public void IsSameDirectoryOrChildOfHandlesDifferentSlashes()
        {
            Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\", @"C:"));
            Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\", @"C:\"));
            Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:", @"C:"));
            Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:", @"C:\"));

            Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\ABCD\EFGH", @"C:"));
            Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\ABCD\EFGH", @"C:\"));

            Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\ABCD\EFGH\", @"C:"));
            Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\ABCD\EFGH\", @"C:\"));

            Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\ABCD\EFGH", @"C:\ABCD"));
            Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\ABCD\EFGH", @"C:\ABCD\"));

            Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\ABCD\EFGH\", @"C:\ABCD"));
            Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\ABCD\EFGH\", @"C:\ABCD\"));

            Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\ABCD\EFGH", @"C:\ABCD\EFGH"));
            Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\ABCD\EFGH", @"C:\ABCD\EFGH\"));

            Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\ABCD\EFGH\", @"C:\ABCD\EFGH"));
            Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\ABCD\EFGH\", @"C:\ABCD\EFGH\"));
        }

        [Fact]
        public void IsSameDirectoryOrChildOfNegativeTests()
        {
            Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"C:\", @"C:\ABCD"));
            Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"C:\ABC", @"C:\ABCD"));
            Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"C:\ABCDE", @"C:\ABCD"));

            Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"C:\A\B\C", @"C:\A\B\C\D"));
        }

        [Fact]
        public void IsValidFilePath()
        {
            var cases = new[] {
                ("test/data1.txt", true),
                ("test\\data1.txt", true),
                ("data1.txt", true),
                ("data1", true),
                ("data1\\", PathUtilities.IsUnixLikePlatform),
                ("data1//", false),
                (null, false),
                ("", false),
                ("  ", ExecutionConditionUtil.IsCoreClrUnix),
                ("path/?.txt", !ExecutionConditionUtil.IsWindowsDesktop),
                ("path/*.txt", !ExecutionConditionUtil.IsWindowsDesktop),
                ("path/:.txt", !ExecutionConditionUtil.IsWindowsDesktop),
                ("path/\".txt", !ExecutionConditionUtil.IsWindowsDesktop),
                ("IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII" +
            "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII" +
            "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII" +
            "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII" +
            "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII.txt", !ExecutionConditionUtil.IsWindowsDesktop)
            };

            foreach (var (path, isValid) in cases)
            {
                Assert.True(isValid == PathUtilities.IsValidFilePath(path), $"Expected {isValid} for \"{path}\"");
            }
        }

        [ConditionalTheory(typeof(WindowsOnly))]
        [InlineData(@"C:\", "B")]
        [InlineData(@"C:\A", "B")]
        [InlineData(@"C:\A\", "B")]
        [InlineData(@"C:A\", "B")]
        [InlineData(@"\A", "B")]
        [InlineData(@"\\A\B\C", "B")]
        [InlineData(@"C", @"B:\")]
        [InlineData(@"C:", @"B:\")]
        [InlineData(@"C:\", @"B:\")]
        [InlineData(@"C:\A", @"B:\")]
        [InlineData(@"C:\A\", @"B:\")]
        [InlineData(@"C:A\", @"B:\")]
        [InlineData(@"\A", @"B:\")]
        [InlineData(@"\\A\B\C", @"B:\")]
        [InlineData("", @"B:\")]
        [InlineData(" ", @"B:\")]
        public void CombinePaths_SameAsPathCombine_Windows(string path1, string path2)
        {
            Assert.Equal(Path.Combine(path1, path2), PathUtilities.CombinePaths(path1, path2));
        }

        [ConditionalTheory(typeof(UnixLikeOnly))]
        [InlineData("C", "B")]
        [InlineData("C/", "\t")]
        [InlineData("C/", "B")]
        [InlineData("/C", "B")]
        [InlineData("/C/", "B")]
        [InlineData("C", "/B")]
        [InlineData("C/", "/B")]
        [InlineData("/C", "/B")]
        [InlineData("/C/", "/B")]
        [InlineData("", "/B/")]
        [InlineData(" ", "/B/")]
        public void CombinePaths_SameAsPathCombine_Linux(string path1, string path2)
        {
            Assert.Equal(Path.Combine(path1, path2), PathUtilities.CombinePaths(path1, path2));
        }

        [Theory]
        [InlineData("C", " ")]
        [InlineData("C", "B")]
        [InlineData("", "")]
        [InlineData(" ", " ")]
        [InlineData("", "B")]
        [InlineData(" ", "B")]
        public void CombinePaths_SameAsPathCombine_Common(string path1, string path2)
        {
            Assert.Equal(Path.Combine(path1, path2), PathUtilities.CombinePaths(path1, path2));
        }

        [ConditionalTheory(typeof(WindowsOnly))]
        [InlineData(@"C:\|\<>", @"C:\|", "<>")]
        [InlineData("C:\\\t", @"C:\", "\t")]
        [InlineData("C", "C", null)]
        [InlineData("C:B", "C:", "B")]
        [InlineData(null, null, null)]
        [InlineData("B", null, "B")]
        public void CombinePaths_DifferentFromPathCombine(string expected, string path1, string path2)
        {
            Assert.Equal(expected, PathUtilities.CombinePaths(path1, path2));
        }

        [ConditionalFact(typeof(WindowsOnly)), WorkItem(51602, @"https://github.com/dotnet/roslyn/issues/51602")]
        public void GetRelativePath_EnsureNo_IndexOutOfRangeException_Windows()
        {
            var expected = "";
            var result = PathUtilities.GetRelativePath(@"C:\A\B\", @"C:\A\B");
            Assert.Equal(expected, result);
        }

        [ConditionalFact(typeof(UnixLikeOnly)), WorkItem(51602, @"https://github.com/dotnet/roslyn/issues/51602")]
        public void GetRelativePath_EnsureNo_IndexOutOfRangeException_Unix()
        {
            var expected = "";
            var result = PathUtilities.GetRelativePath(@"/A/B/", @"/A/B");
            Assert.Equal(expected, result);
        }
    }
}
