#region Copyright notice and license

// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#endregion

using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using NUnit.Framework;

namespace Grpc.Tools.Tests
{
    public class DepFileUtilTest
    {

        [Test]
        public void HashString64Hex_IsSane()
        {
            string hashFoo1 = DepFileUtil.HashString64Hex("foo");
            string hashEmpty = DepFileUtil.HashString64Hex("");
            string hashFoo2 = DepFileUtil.HashString64Hex("foo");

            StringAssert.IsMatch("^[a-f0-9]{16}$", hashFoo1);
            Assert.AreEqual(hashFoo1, hashFoo2);
            Assert.AreNotEqual(hashFoo1, hashEmpty);
        }

        [Test]
        public void GetDepFilenameForProto_IsSane()
        {
            StringAssert.IsMatch(@"^out[\\/][a-f0-9]{16}_foo.protodep$",
                DepFileUtil.GetDepFilenameForProto("out", "foo.proto"));
            StringAssert.IsMatch(@"^[a-f0-9]{16}_foo.protodep$",
                DepFileUtil.GetDepFilenameForProto("", "foo.proto"));
        }

        [Test]
        public void GetDepFilenameForProto_HashesDir()
        {
            string PickHash(string fname) =>
                DepFileUtil.GetDepFilenameForProto("", fname).Substring(0, 16);

            string same1 = PickHash("dir1/dir2/foo.proto");
            string same2 = PickHash("dir1/dir2/proto.foo");
            string same3 = PickHash("dir1/dir2/proto");
            string same4 = PickHash("dir1/dir2/.proto");
            string unsame1 = PickHash("dir2/foo.proto");
            string unsame2 = PickHash("/dir2/foo.proto");

            Assert.AreEqual(same1, same2);
            Assert.AreEqual(same1, same3);
            Assert.AreEqual(same1, same4);
            Assert.AreNotEqual(same1, unsame1);
            Assert.AreNotEqual(unsame1, unsame2);
        }

        [Test]
        public void GetOutputDirWithHash_IsSane()
        {
            StringAssert.IsMatch(@"^out[\\/][a-f0-9]{16}$",
                DepFileUtil.GetOutputDirWithHash("out", "foo.proto"));
            StringAssert.IsMatch(@"^[a-f0-9]{16}$",
                DepFileUtil.GetOutputDirWithHash("", "foo.proto"));
        }

        [Test]
        public void GetOutputDirWithHash_HashesDir()
        {
            string PickHash(string fname) => DepFileUtil.GetOutputDirWithHash("", fname);

            string same1 = PickHash("dir1/dir2/foo.proto");
            string same2 = PickHash("dir1/dir2/proto.foo");
            string same3 = PickHash("dir1/dir2/proto");
            string same4 = PickHash("dir1/dir2/.proto");
            string unsame1 = PickHash("dir2/foo.proto");
            string unsame2 = PickHash("/dir2/foo.proto");

            Assert.AreEqual(same1, same2);
            Assert.AreEqual(same1, same3);
            Assert.AreEqual(same1, same4);
            Assert.AreNotEqual(same1, unsame1);
            Assert.AreNotEqual(unsame1, unsame2);
        }

        //////////////////////////////////////////////////////////////////////////
        // Full file reading tests

        // Generated by protoc on Windows. Slashes vary.
        const string depFile1 =
    @"C:\projects\foo\src\./foo.grpc.pb.cc \
C:\projects\foo\src\./foo.grpc.pb.h \
C:\projects\foo\src\./foo.pb.cc \
 C:\projects\foo\src\./foo.pb.h: C:/usr/include/google/protobuf/wrappers.proto\
   C:/usr/include/google/protobuf/any.proto\
C:/usr/include/google/protobuf/source_context.proto\
   C:/usr/include/google/protobuf/type.proto\
   foo.proto";

        // This has a nasty output directory with a space.
        const string depFile2 =
    @"obj\Release x64\net45\/Foo.cs \
obj\Release x64\net45\/FooGrpc.cs: C:/usr/include/google/protobuf/wrappers.proto\
 C:/projects/foo/src//foo.proto";

        [Test]
        public void ReadDependencyInput_FullFile1()
        {
            string[] deps = ReadDependencyInputFromFileData(depFile1, "foo.proto");

            Assert.NotNull(deps);
            Assert.That(deps, Has.Length.InRange(4, 5));  // foo.proto may or may not be listed.
            Assert.That(deps, Has.One.EndsWith("wrappers.proto"));
            Assert.That(deps, Has.One.EndsWith("type.proto"));
            Assert.That(deps, Has.None.StartWith(" "));
        }

        [Test]
        public void ReadDependencyInput_FullFile2()
        {
            string[] deps = ReadDependencyInputFromFileData(depFile2, "C:/projects/foo/src/foo.proto");

            Assert.NotNull(deps);
            Assert.That(deps, Has.Length.InRange(1, 2));
            Assert.That(deps, Has.One.EndsWith("wrappers.proto"));
            Assert.That(deps, Has.None.StartWith(" "));
        }

        [Test]
        public void ReadDependencyInput_FullFileUnparsable()
        {
            string[] deps = ReadDependencyInputFromFileData("a:/foo.proto", "/foo.proto");
            Assert.NotNull(deps);
            Assert.Zero(deps.Length);
        }

        // NB in our tests files are put into the temp directory but all have
        // different names. Avoid adding files with the same directory path and
        // name, or add reasonable handling for it if required. Tests are run in
        // parallel and will collide otherwise.
        private string[] ReadDependencyInputFromFileData(string fileData, string protoName)
        {
            string tempPath = Path.GetTempPath();
            string tempfile = DepFileUtil.GetDepFilenameForProto(tempPath, protoName);
            try
            {
                File.WriteAllText(tempfile, fileData);
                var mockEng = new Moq.Mock<IBuildEngine>();
                var log = new TaskLoggingHelper(mockEng.Object, "x");
                return DepFileUtil.ReadDependencyInputs(tempPath, protoName, log);
            }
            finally
            {
                try
                {
                    File.Delete(tempfile);
                }
                catch { }
            }
        }
    };
}
