#region License

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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;
using System.Collections.Generic;
using System.Linq;
using Gremlin.Net.Structure;
using Xunit;

namespace Gremlin.Net.UnitTest.Structure
{
    public class PathTests
    {
        [Fact]
        public void ShouldAssignPropertiesCorrectly()
        {
            var labels = new List<ISet<string>>
            {
                new HashSet<string> {"a", "b"},
                new HashSet<string> {"c", "b"},
                new HashSet<string>()
            };
            var objects = new List<object?> {1, new Vertex(1), "hello"};

            var path = new Path(labels, objects);

            Assert.Equal(labels, path.Labels);
            Assert.Equal(objects, path.Objects);
        }

        [Fact]
        public void ShouldReturnTrueForContainsKeyWhenGivenKeyExists()
        {
            var labels = new List<ISet<string>>
            {
                new HashSet<string> {"a", "b"},
                new HashSet<string> {"c", "b"},
                new HashSet<string>()
            };
            var path = new Path(labels, new List<object?>());

            var containsKey = path.ContainsKey("c");

            Assert.True(containsKey);
        }

        [Fact]
        public void ShouldReturnFalseForContainsKeyWhenGivenKeyDoesNotExist()
        {
        var labels = new List<ISet<string>>
            {
                new HashSet<string> {"a", "b"},
                new HashSet<string> {"c", "b"},
                new HashSet<string>()
            };
            var path = new Path(labels, new List<object?>());

            var containsKey = path.ContainsKey("z");

            Assert.False(containsKey);
        }

        [Fact]
        public void ShouldReturnCountOfObjectsForCountProperty()
        {
            var objects = new List<object?> {1, new Vertex(1), "hello"};
            var path = new Path(new List<ISet<string>>(), objects);

            var count = path.Count;

            Assert.Equal(3, count);
        }

        [Fact]
        public void ShouldEnumeratorObjectsIntoListWhenToListIsCalled()
        {
            var objects = new List<object?> {1, new Vertex(1), "hello"};
            var path = new Path(new List<ISet<string>>(), objects);

            var enumeratedObj = path.ToList();

            Assert.Equal(objects, enumeratedObj);
        }

        [Fact]
        public void ShouldReturnTrueForEqualsOfTwoEqualPaths()
        {
            var firstPath =
                new Path(
                    new List<ISet<string>>
                    {
                        new HashSet<string> {"a", "b"},
                        new HashSet<string> {"c", "b"},
                        new HashSet<string>()
                    }, new List<object?> {1, new Vertex(1), "hello"});
            var secondPath =
                new Path(
                    new List<ISet<string>>
                    {
                        new HashSet<string> {"a", "b"},
                        new HashSet<string> {"c", "b"},
                        new HashSet<string>()
                    }, new List<object?> {1, new Vertex(1), "hello"});

            var equals = firstPath.Equals(secondPath);

            Assert.True(equals);
        }

        [Fact]
        public void ShouldReturnFalseForEqualsOfPathsWithDifferentLabels()
        {
            var firstPath =
                new Path(
                    new List<ISet<string>>
                    {
                        new HashSet<string> {"a", "b"},
                        new HashSet<string> {"c", "b"},
                        new HashSet<string>()
                    }, new List<object?> {1, new Vertex(1), "hello"});
            var secondPath =
                new Path(
                    new List<ISet<string>>
                    {
                        new HashSet<string> {"a"},
                        new HashSet<string> {"c", "b"},
                        new HashSet<string>()
                    }, new List<object?> {1, new Vertex(1), "hello"});

            var equals = firstPath.Equals(secondPath);

            Assert.False(equals);
        }

        [Fact]
        public void ShouldReturnFalseForEqualsOfPathsWithDifferentObjects()
        {
            var firstPath =
                new Path(
                    new List<ISet<string>>
                    {
                        new HashSet<string> {"a", "b"},
                        new HashSet<string> {"c", "b"},
                        new HashSet<string>()
                    }, new List<object?> {1, new Vertex(1), "hello"});
            var secondPath =
                new Path(
                    new List<ISet<string>>
                    {
                        new HashSet<string> {"a", "b"},
                        new HashSet<string> {"c", "b"},
                        new HashSet<string>()
                    }, new List<object?> {3, new Vertex(1), "hello"});

            var equals = firstPath.Equals(secondPath);

            Assert.False(equals);
        }

        [Fact]
        public void ShouldReturnTrueForEqualsObjectOfTwoEqualPaths()
        {
            var firstPath =
                new Path(
                    new List<ISet<string>>
                    {
                        new HashSet<string> {"a", "b"},
                        new HashSet<string> {"c", "b"},
                        new HashSet<string>()
                    }, new List<object?> { 1, new Vertex(1), "hello" });
            object secondPath =
                new Path(
                    new List<ISet<string>>
                    {
                        new HashSet<string> {"a", "b"},
                        new HashSet<string> {"c", "b"},
                        new HashSet<string>()
                    }, new List<object?> { 1, new Vertex(1), "hello" });

            var equals = firstPath.Equals(secondPath);

            Assert.True(equals);
        }

        [Fact]
        public void ShouldReturnFalseForEqualsObjectOfPathsWithDifferentLabels()
        {
            var firstPath =
                new Path(
                    new List<ISet<string>>
                    {
                        new HashSet<string> {"a", "b"},
                        new HashSet<string> {"c", "b"},
                        new HashSet<string>()
                    }, new List<object?> { 1, new Vertex(1), "hello" });
            object secondPath =
                new Path(
                    new List<ISet<string>>
                    {
                        new HashSet<string> {"a"},
                        new HashSet<string> {"c", "b"},
                        new HashSet<string>()
                    }, new List<object?> { 1, new Vertex(1), "hello" });

            var equals = firstPath.Equals(secondPath);

            Assert.False(equals);
        }

        [Fact]
        public void ShouldReturnFalseForEqualsObjectOfPathsWithDifferentObjects()
        {
            var firstPath =
                new Path(
                    new List<ISet<string>>
                    {
                        new HashSet<string> {"a", "b"},
                        new HashSet<string> {"c", "b"},
                        new HashSet<string>()
                    }, new List<object?> { 1, new Vertex(1), "hello" });
            object secondPath =
                new Path(
                    new List<ISet<string>>
                    {
                        new HashSet<string> {"a", "b"},
                        new HashSet<string> {"c", "b"},
                        new HashSet<string>()
                    }, new List<object?> { 3, new Vertex(1), "hello" });

            var equals = firstPath.Equals(secondPath);

            Assert.False(equals);
        }

        [Fact]
        public void ShouldReturnFalseForEqualsWhereOtherIsNull()
        {
            var path = new Path(new List<ISet<string>> { new HashSet<string> { "a", "b" }, }, new List<object?> { 1 });

            var equals = path.Equals(null);

            Assert.False(equals);
        }

        [Fact]
        public void ShouldReturnEqualHashcodesForEqualPaths()
        {
            var firstPath =
                new Path(
                    new List<ISet<string>>
                    {
                        new HashSet<string> {"a", "b"},
                        new HashSet<string> {"c", "b"},
                        new HashSet<string>()
                    }, new List<object?> { 1, new Vertex(1), "hello" });
            var secondPath =
                new Path(
                    new List<ISet<string>>
                    {
                        new HashSet<string> {"a", "b"},
                        new HashSet<string> {"c", "b"},
                        new HashSet<string>()
                    }, new List<object?> { 1, new Vertex(1), "hello" });

            var firstHashCode = firstPath.GetHashCode();
            var secondHashCode = secondPath.GetHashCode();

            Assert.Equal(firstHashCode, secondHashCode);
        }

        [Fact]
        public void ShouldThrowWhenInvalidIndexIsAccessed()
        {
            var objects = new List<object?> {1, new Vertex(1), "hello"};
            var path = new Path(new List<ISet<string>>(), objects);

            Assert.Throws<ArgumentOutOfRangeException>(() => path[3]);
        }

        [Fact]
        public void ShouldReturnObjectsByTheirIndex()
        {
            var objects = new List<object?> {1, new Vertex(1), "hello"};
            var path = new Path(new List<ISet<string>>(), objects);

            Assert.Equal(1, path[0]);
            Assert.Equal(new Vertex(1), path[1]);
            Assert.Equal("hello", path[2]);
        }

        [Fact]
        public void ShouldReturnAllObjectsWhenTheirKeyIsAccessed()
        {
            var labels = new List<ISet<string>>
            {
                new HashSet<string> {"a", "b"},
                new HashSet<string> {"c", "b"},
                new HashSet<string>()
            };
            var objects = new List<object?> {1, new Vertex(1), "hello"};
            var path = new Path(labels, objects);

            var bObjects = path["b"];

            Assert.Equal(new List<object> {1, new Vertex(1)}, bObjects);
        }

        [Fact]
        public void ShouldReturnObjectsByTheirKey()
        {
            var labels = new List<ISet<string>>
            {
                new HashSet<string> {"a"},
                new HashSet<string> {"c", "b"},
                new HashSet<string>()
            };
            var objects = new List<object?> {1, new Vertex(1), "hello"};
            var path = new Path(labels, objects);

            Assert.Equal(1, path["a"]);
            Assert.Equal(new Vertex(1), path["c"]);
            Assert.Equal(new Vertex(1), path["b"]);
        }

        [Fact]
        public void ShouldThrowWhenUnknownKeyIsAccessed()
        {
            var path = new Path(new List<ISet<string>>(), new List<object?>());

            Assert.Throws<KeyNotFoundException>(() => path["unknownKey"]);
        }

        [Fact]
        public void ShouldReturnCommonStringRepresentationForToString()
        {
            var labels = new List<ISet<string>>
            {
                new HashSet<string> {"a", "b"},
                new HashSet<string> {"c", "b"},
                new HashSet<string>()
            };
            var objects = new List<object?> {1, new Vertex(1), "hello"};
            var path = new Path(labels, objects);

            var pathStr = path.ToString();

            Assert.Equal("path[1, v[1], hello]", pathStr);
        }

        [Fact]
        public void ShouldReturnTrueAndObjectsForTryGetWhenKeyWithMultipleObjectsIsProvided()
        {
            var labels = new List<ISet<string>>
            {
                new HashSet<string> {"a", "b"},
                new HashSet<string> {"c", "b"},
                new HashSet<string>()
            };
            var objects = new List<object?> {1, new Vertex(1), "hello"};
            var path = new Path(labels, objects);

            var success = path.TryGetValue("b", out object? actualObj);

            Assert.True(success);
            Assert.Equal(new List<object> {1, new Vertex(1)}, actualObj);
        }

        [Fact]
        public void ShouldReturnTrueAndCorrectObjectForTryGet()
        {
            var labels = new List<ISet<string>>
            {
                new HashSet<string> {"a"},
                new HashSet<string> {"c", "b"},
                new HashSet<string>()
            };
            var objects = new List<object?> {1, new Vertex(1), "hello"};
            var path = new Path(labels, objects);

            var success = path.TryGetValue("b", out var actualObj);

            Assert.True(success);
            Assert.Equal(new Vertex(1), actualObj);
        }

        [Fact]
        public void ShouldReturnFalseForTryGetWhenUnknownKeyIsProvided()
        {
            var path = new Path(new List<ISet<string>>(), new List<object?>());

            var success = path.TryGetValue("unknownKey", out _);

            Assert.False(success);
        }
    }
}