﻿// 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 Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis
{
    internal partial class LineDirectiveMap<TDirective>
    {
        /// <summary>
        /// Enum that describes the state related to the #line or #externalsource directives at a position in source.
        /// </summary>
        public enum PositionState : byte
        {
            /// <summary>
            /// Used in VB when the position is not hidden, but it's not known yet that there is a (nonempty) <c>#ExternalSource</c>
            /// following.
            /// </summary>
            Unknown,

            /// <summary>
            /// Used in C# for spans preceding the first <c>#line</c> directive (if any) and for <c>#line default</c> spans
            /// </summary>
            Unmapped,

            /// <summary>
            /// Used in C# for spans inside of <c>#line linenumber</c> directive
            /// </summary>
            Remapped,

            /// <summary>
            /// Used in C# for spans inside of <c>#line (startLine, startChar) - (endLine, endChar) charOffset</c> directive
            /// </summary>
            RemappedSpan,

            /// <summary>
            /// Used in VB for spans inside of a <c>#ExternalSource</c> directive that followed an unknown span
            /// </summary>
            RemappedAfterUnknown,

            /// <summary>
            /// Used in VB for spans inside of a <c>#ExternalSource</c> directive that followed a hidden span
            /// </summary>
            RemappedAfterHidden,

            /// <summary>
            /// Used in C# and VB for spans that are inside of <c>#line hidden</c> (C#) or outside of <c>#ExternalSource</c> (VB) 
            /// directives
            /// </summary>
            Hidden
        }

        // Struct that represents an entry in the line mapping table. Entries sort by the unmapped
        // line.
        internal readonly struct LineMappingEntry : IComparable<LineMappingEntry>
        {
            // 0-based line in this tree
            public readonly int UnmappedLine;

            // 0-based line it maps to.
            public readonly int MappedLine;

            // 0-based mapped span from enhanced #line directive.
            public readonly LinePositionSpan MappedSpan;

            // optional 0-based character offset from enhanced #line directive.
            public readonly int? UnmappedCharacterOffset;

            // raw value from #line or #ExternalDirective, may be null
            public readonly string? MappedPathOpt;

            // the state of this line
            public readonly PositionState State;

            public LineMappingEntry(int unmappedLine)
            {
                this.UnmappedLine = unmappedLine;
                this.MappedLine = unmappedLine;
                this.MappedSpan = default;
                this.UnmappedCharacterOffset = null;
                this.MappedPathOpt = null;
                this.State = PositionState.Unmapped;
            }

            public LineMappingEntry(
                int unmappedLine,
                int mappedLine,
                string? mappedPathOpt,
                PositionState state)
            {
                Debug.Assert(state != PositionState.RemappedSpan);

                this.UnmappedLine = unmappedLine;
                this.MappedLine = mappedLine;
                this.MappedSpan = default;
                this.UnmappedCharacterOffset = null;
                this.MappedPathOpt = mappedPathOpt;
                this.State = state;
            }

            public LineMappingEntry(
                int unmappedLine,
                LinePositionSpan mappedSpan,
                int? unmappedCharacterOffset,
                string? mappedPathOpt)
            {
                this.UnmappedLine = unmappedLine;
                this.MappedLine = -1;
                this.MappedSpan = mappedSpan;
                this.UnmappedCharacterOffset = unmappedCharacterOffset;
                this.MappedPathOpt = mappedPathOpt;
                this.State = PositionState.RemappedSpan;
            }

            public int CompareTo(LineMappingEntry other)
                => UnmappedLine.CompareTo(other.UnmappedLine);

            public bool IsHidden
                => State == PositionState.Hidden;
        }
    }
}
