﻿// 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.Linq;
using Microsoft.CodeAnalysis.Emit;

namespace Microsoft.CodeAnalysis.CodeGen;

internal readonly struct StateMachineStateDebugInfo
{
    public readonly int SyntaxOffset;
    public readonly StateMachineState StateNumber;

    public StateMachineStateDebugInfo(int syntaxOffset, StateMachineState stateNumber)
    {
        SyntaxOffset = syntaxOffset;
        StateNumber = stateNumber;
    }
}

/// <summary>
/// Debug information maintained for each state machine.
/// Facilitates mapping of state machine states from a compilation to the previous one (or to a metadata baseline) during EnC.
/// </summary>
internal readonly struct StateMachineStatesDebugInfo
{
    public readonly ImmutableArray<StateMachineStateDebugInfo> States;

    /// <summary>
    /// The number of the first state that has not been used in any of the previous versions of the state machine,
    /// or null if we are not generating EnC delta.
    /// 
    /// For 1st generation EnC delta, this is calculated by examining the <see cref="EditAndContinueMethodDebugInformation.StateMachineStates"/> stored in the baseline metadata.
    /// For subsequent generations, the number is updated to account for newly generated states in that generation.
    /// </summary>
    public readonly StateMachineState? FirstUnusedIncreasingStateMachineState;

    /// <summary>
    /// The number of the first state that has not been used in any of the previous versions of the state machine,
    /// or null if we are not generating EnC delta, or the state machine has no decreasing states.
    /// 
    /// For 1st generation EnC delta, this is calculated by examining the <see cref="EditAndContinueMethodDebugInformation.StateMachineStates"/> stored in the baseline metadata.
    /// For subsequent generations, the number is updated to account for newly generated states in that generation.
    /// </summary>
    public readonly StateMachineState? FirstUnusedDecreasingStateMachineState;

    private StateMachineStatesDebugInfo(ImmutableArray<StateMachineStateDebugInfo> states, StateMachineState? firstUnusedIncreasingStateMachineState, StateMachineState? firstUnusedDecreasingStateMachineState)
    {
        States = states;
        FirstUnusedIncreasingStateMachineState = firstUnusedIncreasingStateMachineState;
        FirstUnusedDecreasingStateMachineState = firstUnusedDecreasingStateMachineState;
    }

    public static StateMachineStatesDebugInfo Create(VariableSlotAllocator? variableSlotAllocator, ImmutableArray<StateMachineStateDebugInfo> stateInfos)
    {
        StateMachineState? firstUnusedIncreasingStateMachineState = null, firstUnusedDecreasingStateMachineState = null;

        if (variableSlotAllocator != null)
        {
            // We start with first unused state numbers from the previous generation and update them based on states generated in the current one.
            firstUnusedIncreasingStateMachineState = variableSlotAllocator.GetFirstUnusedStateMachineState(increasing: true);
            firstUnusedDecreasingStateMachineState = variableSlotAllocator.GetFirstUnusedStateMachineState(increasing: false);

            if (!stateInfos.IsDefaultOrEmpty)
            {
                // The current method is a state machine and has some resumable/finalization states.
                // Update the first unused states based on the highest resumable (positive numbers) and lowest finalization (negative numbers) of this method.

                var maxState = stateInfos.Max(info => info.StateNumber) + 1;
                var minState = stateInfos.Min(info => info.StateNumber) - 1;

                firstUnusedIncreasingStateMachineState = (firstUnusedIncreasingStateMachineState != null) ? (StateMachineState)Math.Max((int)firstUnusedIncreasingStateMachineState.Value, (int)maxState) : maxState;

                if (minState < 0)
                {
                    firstUnusedDecreasingStateMachineState = (firstUnusedDecreasingStateMachineState != null) ? (StateMachineState)Math.Min((int)firstUnusedDecreasingStateMachineState.Value, (int)minState) : minState;
                }
            }
        }

        return new StateMachineStatesDebugInfo(
            stateInfos,
            firstUnusedIncreasingStateMachineState,
            firstUnusedDecreasingStateMachineState);
    }
}
