// 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.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
    internal sealed class SourceLabelSymbol : LabelSymbol
    {
        private readonly MethodSymbol _containingMethod;
        private readonly SyntaxNodeOrToken _identifierNodeOrToken;

        /// <summary>
        /// Switch case labels have a constant expression associated with them.
        /// </summary>
        private readonly ConstantValue? _switchCaseLabelConstant;

        // PERF: Often we do not need this, so we make this lazy
        private string? _lazyName;

        public SourceLabelSymbol(
            MethodSymbol containingMethod,
            SyntaxNodeOrToken identifierNodeOrToken,
            ConstantValue? switchCaseLabelConstant = null)
        {
            Debug.Assert(identifierNodeOrToken.IsToken || identifierNodeOrToken.IsNode);
            _containingMethod = containingMethod;
            _identifierNodeOrToken = identifierNodeOrToken;
            _switchCaseLabelConstant = switchCaseLabelConstant;
        }

        public override string Name
        {
            get
            {
                return _lazyName ??
                    (_lazyName = MakeLabelName());
            }
        }

        private string MakeLabelName()
        {
            var node = _identifierNodeOrToken.AsNode();
            if (node != null)
            {
                if (node.Kind() == SyntaxKind.DefaultSwitchLabel)
                {
                    return ((DefaultSwitchLabelSyntax)node).Keyword.ToString();
                }

                return node.ToString();
            }

            var tk = _identifierNodeOrToken.AsToken();
            if (tk.Kind() != SyntaxKind.None)
            {
                return tk.ValueText;
            }

            return _switchCaseLabelConstant?.ToString() ?? "";
        }

        public SourceLabelSymbol(
            MethodSymbol containingMethod,
            ConstantValue switchCaseLabelConstant)
        {
            _containingMethod = containingMethod;
            _identifierNodeOrToken = default(SyntaxToken);
            _switchCaseLabelConstant = switchCaseLabelConstant;
        }

        public override ImmutableArray<Location> Locations
        {
            get
            {
                return _identifierNodeOrToken.IsToken && _identifierNodeOrToken.Parent == null
                    ? ImmutableArray<Location>.Empty
                    : ImmutableArray.Create<Location>(_identifierNodeOrToken.GetLocation()!);
            }
        }

        public override ImmutableArray<SyntaxReference> DeclaringSyntaxReferences
        {
            get
            {
                CSharpSyntaxNode? node = null;

                if (_identifierNodeOrToken.IsToken)
                {
                    if (_identifierNodeOrToken.Parent != null)
                        node = _identifierNodeOrToken.Parent.FirstAncestorOrSelf<LabeledStatementSyntax>();
                }
                else
                {
                    node = _identifierNodeOrToken.AsNode()!.FirstAncestorOrSelf<SwitchLabelSyntax>();
                }

                return node == null ? ImmutableArray<SyntaxReference>.Empty : ImmutableArray.Create<SyntaxReference>(node.GetReference());
            }
        }

        public override MethodSymbol ContainingMethod
        {
            get
            {
                return _containingMethod;
            }
        }

        public override Symbol ContainingSymbol
        {
            get
            {
                return _containingMethod;
            }
        }

        // Get the identifier node or token that defined this label symbol. This is useful for robustly
        // checking if a label symbol actually matches a particular definition, even in the presence
        // of duplicates.
        internal override SyntaxNodeOrToken IdentifierNodeOrToken
        {
            get
            {
                return _identifierNodeOrToken;
            }
        }

        /// <summary>
        /// If the label is a switch case label, returns the associated constant value with
        /// case expression, otherwise returns null.
        /// </summary>
        public ConstantValue? SwitchCaseLabelConstant
        {
            get
            {
                return _switchCaseLabelConstant;
            }
        }

        public override bool Equals(Symbol? obj, TypeCompareKind compareKind)
        {
            if (obj == (object)this)
            {
                return true;
            }

            var symbol = obj as SourceLabelSymbol;
            return (object?)symbol != null
                && symbol._identifierNodeOrToken.Kind() != SyntaxKind.None
                && symbol._identifierNodeOrToken.Equals(_identifierNodeOrToken)
                && symbol._containingMethod.Equals(_containingMethod, compareKind);
        }

        public override int GetHashCode()
        {
            return _identifierNodeOrToken.GetHashCode();
        }
    }
}
