// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using Microsoft.Build.Execution;

#nullable disable

namespace Microsoft.Build.BackEnd
{
    /// <summary>
    /// Indicates what the action is for requests which are yielding.
    /// </summary>
    internal enum YieldAction : byte
    {
        /// <summary>
        /// The request is yielding its control of the node.
        /// </summary>
        Yield,

        /// <summary>
        /// The request is ready to reacquire control of the node.
        /// </summary>
        Reacquire,

        /// <summary>
        /// There is no yield action
        /// </summary>
        None,
    }

    /// <summary>
    /// This class is used to inform the Scheduler that a request on a node is being blocked from further progress.  There
    /// are two cases for this:
    /// 1) The request may be blocked waiting for a target to complete in the same project but which is assigned to
    ///    another request.
    /// 2) The request may be blocked because it has child requests which need to be satisfied to proceed.
    /// </summary>
    internal class BuildRequestBlocker : INodePacket
    {
        /// <summary>
        /// The yield action, if any.
        /// </summary>
        private YieldAction _yieldAction;

        /// <summary>
        /// The global request id of the request which is being blocked from continuing.
        /// </summary>
        private int _blockedGlobalRequestId;

        /// <summary>
        /// The set of targets which are currently in progress for the blocked global request ID.
        /// </summary>
        private string[] _targetsInProgress;

        /// <summary>
        /// The request on which we are blocked, if any.
        /// </summary>
        private int _blockingGlobalRequestId;

        /// <summary>
        /// The name of the blocking target, if any.
        /// </summary>
        private string _blockingTarget;

        private BuildResult _partialBuildResult;

        /// <summary>
        /// The requests which need to be built to unblock the request, if any.
        /// </summary>
        private BuildRequest[] _buildRequests;

        /// <summary>
        /// Constructor for deserialization.
        /// </summary>
        internal BuildRequestBlocker(ITranslator translator)
        {
            Translate(translator);
        }

        /// <summary>
        /// Constructor for the blocker where we are blocked waiting for a target.
        /// </summary>
        internal BuildRequestBlocker(int blockedGlobalRequestId, string[] targetsInProgress, int blockingGlobalRequestId, string blockingTarget)
            : this(blockedGlobalRequestId, targetsInProgress)
        {
            _blockingGlobalRequestId = blockingGlobalRequestId;
            _blockingTarget = blockingTarget;
        }

        /// <summary>
        /// Constructor for the blocker where we are blocked waiting for requests to be satisfied.
        /// </summary>
        internal BuildRequestBlocker(int blockedGlobalRequestId, string[] targetsInProgress, BuildRequest[] buildRequests)
            : this(blockedGlobalRequestId, targetsInProgress)
        {
            _buildRequests = buildRequests;
        }

        /// <summary>
        /// Constructor for a blocker used by yielding requests.
        /// </summary>
        internal BuildRequestBlocker(int blockedGlobalRequestId, string[] targetsInProgress, YieldAction action)
            : this(blockedGlobalRequestId, targetsInProgress)
        {
            _yieldAction = action;
            _blockingGlobalRequestId = blockedGlobalRequestId;
        }

        /// <summary>
        /// Constructor for a blocker used by results-transfer requests
        /// </summary>
        /// <param name="blockedGlobalRequestId">The request needing results transferred</param>
        internal BuildRequestBlocker(int blockedGlobalRequestId)
        {
            _blockedGlobalRequestId = blockedGlobalRequestId;
            _blockingGlobalRequestId = blockedGlobalRequestId;
            _targetsInProgress = null;
            _yieldAction = YieldAction.None;
        }

        /// <summary>
        /// Constructor for common values.
        /// </summary>
        private BuildRequestBlocker(int blockedGlobalRequestId, string[] targetsInProgress)
        {
            _blockedGlobalRequestId = blockedGlobalRequestId;
            _blockingGlobalRequestId = BuildRequest.InvalidGlobalRequestId;
            _targetsInProgress = targetsInProgress;
            _yieldAction = YieldAction.None;
        }

        public BuildRequestBlocker(int requestGlobalRequestId, string[] targetsInProgress, int unsubmittedRequestBlockingGlobalRequestId, string unsubmittedRequestBlockingTarget, BuildResult partialBuildResult)
        : this(requestGlobalRequestId, targetsInProgress, unsubmittedRequestBlockingGlobalRequestId, unsubmittedRequestBlockingTarget)
        {
            _partialBuildResult = partialBuildResult;
        }

        /// <summary>
        /// Returns the type of packet.
        /// </summary>
        public NodePacketType Type
        {
            [DebuggerStepThrough]
            get
            { return NodePacketType.BuildRequestBlocker; }
        }

        /// <summary>
        /// Accessor for the blocked request id.
        /// </summary>
        public int BlockedRequestId
        {
            [DebuggerStepThrough]
            get
            {
                return _blockedGlobalRequestId;
            }
        }

        /// <summary>
        /// Accessor for the set of targets currently in progress.
        /// </summary>
        public string[] TargetsInProgress
        {
            [DebuggerStepThrough]
            get
            {
                return _targetsInProgress;
            }
        }

        /// <summary>
        /// Accessor for the blocking request id, if any.
        /// </summary>
        public int BlockingRequestId
        {
            [DebuggerStepThrough]
            get
            {
                return _blockingGlobalRequestId;
            }
        }

        /// <summary>
        /// Accessor for the blocking request id, if any.
        /// </summary>
        public string BlockingTarget
        {
            [DebuggerStepThrough]
            get
            {
                return _blockingTarget;
            }
        }

        /// <summary>
        /// Accessor for the blocking build requests, if any.
        /// </summary>
        public BuildRequest[] BuildRequests
        {
            [DebuggerStepThrough]
            get
            {
                return _buildRequests;
            }
        }

        /// <summary>
        /// Accessor for the yield action.
        /// </summary>
        public YieldAction YieldAction
        {
            [DebuggerStepThrough]
            get
            {
                return _yieldAction;
            }
        }

        public BuildResult PartialBuildResult => _partialBuildResult;

        #region INodePacketTranslatable Members

        /// <summary>
        /// Serialization method.
        /// </summary>
        public void Translate(ITranslator translator)
        {
            translator.Translate(ref _blockedGlobalRequestId);
            translator.Translate(ref _targetsInProgress);
            translator.Translate(ref _blockingGlobalRequestId);
            translator.Translate(ref _blockingTarget);
            translator.TranslateEnum(ref _yieldAction, (int)_yieldAction);
            translator.TranslateArray(ref _buildRequests);
            translator.Translate(ref _partialBuildResult, packetTranslator => BuildResult.FactoryForDeserialization(packetTranslator));
        }

        #endregion

        /// <summary>
        /// Factory for serialization.
        /// </summary>
        internal static INodePacket FactoryForDeserialization(ITranslator translator)
        {
            return new BuildRequestBlocker(translator);
        }
    }
}
