﻿// 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.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.CodeAnalysis.Syntax
{
    internal abstract class AbstractWarningStateMap<TWarningState>
        where TWarningState : struct
    {
        /// <summary>
        /// List of entries sorted in source order, each of which captures a
        /// position in the supplied syntax tree and the set of diagnostics (warnings)
        /// whose reporting should either be suppressed or enabled at this position.
        /// </summary>
        private readonly WarningStateMapEntry[] _warningStateMapEntries;

        protected AbstractWarningStateMap(SyntaxTree syntaxTree)
        {
            _warningStateMapEntries = CreateWarningStateMapEntries(syntaxTree);
        }

        /// <summary>
        /// Returns list of entries sorted in source order, each of which captures a
        /// position in the supplied syntax tree and the set of diagnostics (warnings)
        /// whose reporting should either be suppressed or enabled at this position.
        /// </summary>
        protected abstract WarningStateMapEntry[] CreateWarningStateMapEntries(SyntaxTree syntaxTree);

        /// <summary>
        /// Returns the reporting state for the supplied diagnostic id at the supplied position
        /// in the associated syntax tree.
        /// </summary>
        public TWarningState GetWarningState(string id, int position)
        {
            var entry = GetEntryAtOrBeforePosition(position);

            TWarningState state;
            if (entry.SpecificWarningOption.TryGetValue(id, out state))
            {
                return state;
            }

            return entry.GeneralWarningOption;
        }

        /// <summary>
        /// Gets the entry with the largest position less than or equal to supplied position.
        /// </summary>
        private WarningStateMapEntry GetEntryAtOrBeforePosition(int position)
        {
            Debug.Assert(_warningStateMapEntries != null && _warningStateMapEntries.Length > 0);
            int r = Array.BinarySearch(_warningStateMapEntries, new WarningStateMapEntry(position));
            return _warningStateMapEntries[r >= 0 ? r : ((~r) - 1)];
        }

        /// <summary>
        /// Struct that represents an entry in the warning state map. Sorts by position in the associated syntax tree.
        /// </summary>
        protected readonly struct WarningStateMapEntry : IComparable<WarningStateMapEntry>
        {
            // 0-based position in the associated syntax tree
            public readonly int Position;

            // the general option applicable to all warnings, accumulated of all #pragma up to the current Line.
            public readonly TWarningState GeneralWarningOption;

            // the mapping of the specific warning to the option, accumulated of all #pragma up to the current Line.
            public readonly ImmutableDictionary<string, TWarningState> SpecificWarningOption;

            public WarningStateMapEntry(int position)
            {
                this.Position = position;
                this.GeneralWarningOption = default;
                this.SpecificWarningOption = ImmutableDictionary.Create<string, TWarningState>();
            }

            public WarningStateMapEntry(int position, TWarningState general, ImmutableDictionary<string, TWarningState> specific)
            {
                this.Position = position;
                this.GeneralWarningOption = general;
                this.SpecificWarningOption = specific ?? ImmutableDictionary.Create<string, TWarningState>();
            }

            public int CompareTo(WarningStateMapEntry other)
            {
                return this.Position - other.Position;
            }
        }
    }
}
