﻿// 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.CodeGen
{
    /// <summary>
    /// Id that associates an emitted user-defined or long-lived synthesized local variable 
    /// with a syntax node that defined it. If a syntax node defines multiple variables it 
    /// provides information necessary to identify which one of these variables is it.
    /// </summary>
    internal readonly struct LocalDebugId : IEquatable<LocalDebugId>
    {
        /// <summary>
        /// We calculate a "syntax offset" for each user-defined and long-lived synthesized variable. 
        /// Every such variable symbol has to be associated with a syntax node (its declarator). 
        /// In usual cases this is the textual distance of the declarator from the start of the method body. 
        /// It gets a bit complicated when the containing method body is not contiguous (constructors). 
        /// If the variable is in the body of the constructor the definition of syntax offset is the same. 
        /// If the variable is defined in a constructor  initializer or in a member initializer 
        /// (this is only possible when declaration expressions or closures in primary constructors are involved) 
        /// then the distance is a negative sum of the widths of all the initializers that succeed the declarator 
        /// of the variable in the emitted constructor body plus the relative offset of the declarator from 
        /// the start of the containing initializer.
        /// </summary>
        public readonly int SyntaxOffset;

        /// <summary>
        /// If a single node is a declarator for multiple variables of the same synthesized kind (it can only happen for synthesized variables) 
        /// we calculate additional number "ordinal" for such variable. We assign the ordinals to the synthesized variables with the same kind
        /// and syntax offset in the order as they appear in the lowered bound tree. It is important that a valid EnC edit can't change 
        /// the ordinal of a synthesized variable. If it could it would need to be assigned a different kind or associated with a different declarator node.
        /// </summary>
        public readonly int Ordinal;

        public static readonly LocalDebugId None = new LocalDebugId(isNone: true);

        private LocalDebugId(bool isNone)
        {
            Debug.Assert(isNone);

            this.SyntaxOffset = -1;
            this.Ordinal = -1;
        }

        public LocalDebugId(int syntaxOffset, int ordinal = 0)
        {
            Debug.Assert(ordinal >= 0);

            this.SyntaxOffset = syntaxOffset;
            this.Ordinal = ordinal;
        }

        public bool IsNone
        {
            get
            {
                return Ordinal == -1;
            }
        }

        public bool Equals(LocalDebugId other)
        {
            return SyntaxOffset == other.SyntaxOffset
                && Ordinal == other.Ordinal;
        }

        public override int GetHashCode()
        {
            return Hash.Combine(SyntaxOffset, Ordinal);
        }

        public override bool Equals(object? obj)
        {
            return obj is LocalDebugId && Equals((LocalDebugId)obj);
        }

        public override string ToString()
        {
            return SyntaxOffset + ":" + Ordinal;
        }
    }
}
