﻿// 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;
using System.Diagnostics;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Syntax.InternalSyntax
{
    internal readonly partial struct SyntaxList<TNode> : IEquatable<SyntaxList<TNode>>
        where TNode : GreenNode
    {
        private readonly GreenNode? _node;

        internal SyntaxList(GreenNode? node)
        {
            _node = node;
        }

        internal GreenNode? Node => _node;

        public int Count
        {
            get
            {
                return _node == null ? 0 : (_node.IsList ? _node.SlotCount : 1);
            }
        }

        public TNode? this[int index]
        {
            get
            {
                if (_node == null)
                {
                    return null;
                }
                else if (_node.IsList)
                {
                    Debug.Assert(index >= 0);
                    Debug.Assert(index <= _node.SlotCount);

                    return (TNode?)_node.GetSlot(index);
                }
                else if (index == 0)
                {
                    return (TNode?)_node;
                }
                else
                {
                    throw ExceptionUtilities.Unreachable();
                }
            }
        }

        internal TNode GetRequiredItem(int index)
        {
            var node = this[index];
            RoslynDebug.Assert(node is object);
            return node;
        }

        internal GreenNode? ItemUntyped(int index)
        {
            RoslynDebug.Assert(_node is object);
            var node = this._node;
            if (node.IsList)
            {
                return node.GetSlot(index);
            }

            Debug.Assert(index == 0);
            return node;
        }

        public bool Any()
        {
            return _node != null;
        }

        public bool Any(int kind)
        {
            foreach (var element in this)
            {
                if (element.RawKind == kind)
                {
                    return true;
                }
            }

            return false;
        }

        internal TNode[] Nodes
        {
            get
            {
                var arr = new TNode[this.Count];
                for (int i = 0; i < this.Count; i++)
                {
                    arr[i] = GetRequiredItem(i);
                }
                return arr;
            }
        }

        public TNode? Last
        {
            get
            {
                RoslynDebug.Assert(_node is object);
                var node = this._node;
                if (node.IsList)
                {
                    return (TNode?)node.GetSlot(node.SlotCount - 1);
                }

                return (TNode?)node;
            }
        }

        public Enumerator GetEnumerator()
        {
            return new Enumerator(this);
        }

        internal void CopyTo(int offset, ArrayElement<GreenNode>[] array, int arrayOffset, int count)
        {
            for (int i = 0; i < count; i++)
            {
                array[arrayOffset + i].Value = GetRequiredItem(i + offset);
            }
        }

        public static bool operator ==(SyntaxList<TNode> left, SyntaxList<TNode> right)
        {
            return left._node == right._node;
        }

        public static bool operator !=(SyntaxList<TNode> left, SyntaxList<TNode> right)
        {
            return left._node != right._node;
        }

        public bool Equals(SyntaxList<TNode> other)
        {
            return _node == other._node;
        }

        public override bool Equals(object? obj)
        {
            return (obj is SyntaxList<TNode>) && Equals((SyntaxList<TNode>)obj);
        }

        public override int GetHashCode()
        {
            return _node != null ? _node.GetHashCode() : 0;
        }

        public SeparatedSyntaxList<TOther> AsSeparatedList<TOther>() where TOther : GreenNode
        {
            return new SeparatedSyntaxList<TOther>(this);
        }

        public static implicit operator SyntaxList<TNode>(TNode node)
        {
            return new SyntaxList<TNode>(node);
        }

        public static implicit operator SyntaxList<TNode>(SyntaxList<GreenNode> nodes)
        {
            return new SyntaxList<TNode>(nodes._node);
        }

        public static implicit operator SyntaxList<GreenNode>(SyntaxList<TNode> nodes)
        {
            return new SyntaxList<GreenNode>(nodes.Node);
        }
    }
}
