﻿// 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.

using System.Collections.Generic;
using Newtonsoft.Json;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.Graph
{
    /// <summary>
    /// Represents an edge from one vertex to another.
    /// </summary>
    internal class Edge : Element
    {
        [JsonProperty("outV")]
        public Id<Vertex> OutVertex { get; }

        // LSIF edges can have either an inV for a one-to-one edge, or an inVs for a one-to-many edge. We'll represent
        // this by having two properties here, and exactly one of them will always be non-null.

        [JsonProperty("inV", NullValueHandling = NullValueHandling.Ignore)]
        public Id<Vertex>? InVertex { get; }

        [JsonProperty("inVs", NullValueHandling = NullValueHandling.Ignore)]
        public Id<Vertex>[]? InVertices { get; }

        public IEnumerable<Id<Vertex>> GetInVerticies() => InVertices ?? SpecializedCollections.SingletonEnumerable(InVertex!.Value);

        public Edge(string label, Id<Vertex> outVertex, Id<Vertex> inVertex, IdFactory idFactory)
            : base(type: "edge", label: label, idFactory)
        {
            // We'll be strict and assert that label types that are one-to-many must always use inVs
            Contract.ThrowIfTrue(IsEdgeLabelOneToMany(label));
            OutVertex = outVertex;
            InVertex = inVertex;
        }

        public Edge(string label, Id<Vertex> outVertex, Id<Vertex>[] inVertices, IdFactory idFactory)
            : base(type: "edge", label: label, idFactory)
        {
            Contract.ThrowIfFalse(IsEdgeLabelOneToMany(label));
            OutVertex = outVertex;
            InVertices = inVertices;
        }

        private static bool IsEdgeLabelOneToMany(string label) => label is "contains" or "item";

        public static Edge Create<TOutVertex, TInVertex>(string label, Id<TOutVertex> outVertex, Id<TInVertex> inVertex, IdFactory idFactory) where TOutVertex : Vertex where TInVertex : Vertex
        {
            return new Edge(label, outVertex.As<TOutVertex, Vertex>(), inVertex.As<TInVertex, Vertex>(), idFactory);
        }

        public static Edge Create<TOutVertex, TInVertex>(string label, Id<TOutVertex> outVertex, IList<Id<TInVertex>> inVertices, IdFactory idFactory) where TOutVertex : Vertex where TInVertex : Vertex
        {
            var inVerticesArray = new Id<Vertex>[inVertices.Count];

            // Note: this is ultimately just an array copy, but in a strongly-typed way. The JIT might see through this as a memory copy,
            // but might require some more explicit code if not.
            for (var i = 0; i < inVertices.Count; i++)
            {
                inVerticesArray[i] = inVertices[i].As<TInVertex, Vertex>();
            }

            return new Edge(label, outVertex.As<TOutVertex, Vertex>(), inVerticesArray, idFactory);
        }

        public override string ToString()
        {
            return $"{Label} edge from {OutVertex} to {string.Join(", ", GetInVerticies())}";
        }
    }
}
