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

using Microsoft.Build.Execution;

#nullable disable

namespace Microsoft.Build.BackEnd
{
    /// <summary>
    /// The type of action to take in response to a scheduling request.
    /// </summary>
    internal enum ScheduleActionType
    {
        /// <summary>
        /// The response indicates that no action should be taken.
        /// </summary>
        NoAction,

        /// <summary>
        /// The response indicates that the request should be sent to the specified node.
        /// </summary>
        Schedule,

        /// <summary>
        /// The response indicates that the request should be send to the specified node,
        /// along with the configuration for the request.
        /// </summary>
        ScheduleWithConfiguration,

        /// <summary>
        /// The response has results for a particular blocked request
        /// </summary>
        ReportResults,

        /// <summary>
        /// The specified request id should now resume execution
        /// </summary>
        ResumeExecution,

        /// <summary>
        /// The response indicates that a new node should be created rather than scheduling this request.
        /// The request may be scheduled at a later time.
        /// </summary>
        CreateNode,

        /// <summary>
        /// The response indicates that the submission is complete.
        /// </summary>
        SubmissionComplete,

        /// <summary>
        /// The last action caused a circular dependency which cannot be resolved.
        /// </summary>
        CircularDependency
    }

    /// <summary>
    /// A response from the scheduler indicating where a build request should be handled.
    /// </summary>
    internal class ScheduleResponse
    {
        /// <summary>
        /// The type of action to take on this response.
        /// </summary>
        internal readonly ScheduleActionType Action;

        /// <summary>
        /// The node ID to which the request should be sent.
        /// </summary>
        internal readonly int NodeId;

        /// <summary>
        /// The results for a completed submission.
        /// </summary>
        internal readonly BuildResult BuildResult;

        /// <summary>
        /// The build request to send.
        /// </summary>
        internal readonly BuildRequest BuildRequest;

        /// <summary>
        /// The unblocking information.
        /// </summary>
        internal readonly BuildRequestUnblocker Unblocker;

        /// <summary>
        /// The type of node we must create.
        /// </summary>
        internal readonly NodeAffinity RequiredNodeType;

        /// <summary>
        /// The number of nodes of the requested affinity to create.
        /// </summary>
        internal readonly int NumberOfNodesToCreate;

        /// <summary>
        /// Constructs a response where no action should be taken.
        /// </summary>
        internal ScheduleResponse(ScheduleActionType type)
        {
            Action = type;
        }

        /// <summary>
        /// Constructs a response indicating what type of node we need to create.
        /// </summary>
        private ScheduleResponse(NodeAffinity affinity, int count)
        {
            Action = ScheduleActionType.CreateNode;
            RequiredNodeType = affinity;
            NumberOfNodesToCreate = count;
        }

        /// <summary>
        /// Constructs a response indicating that a specific submission has completed.
        /// </summary>
        private ScheduleResponse(BuildResult result)
        {
            Action = ScheduleActionType.SubmissionComplete;
            BuildResult = result;
        }

        /// <summary>
        /// Constructs a response indicating there is a circular dependency caused by the specified request.
        /// </summary>
        private ScheduleResponse(int nodeId, BuildRequest parentRequest, BuildRequest requestCausingCircularDependency)
        {
            Action = ScheduleActionType.CircularDependency;
            BuildRequest = requestCausingCircularDependency;
            NodeId = nodeId;
            Unblocker = new BuildRequestUnblocker(parentRequest, new BuildResult(requestCausingCircularDependency, true /* circularDependency */));
        }

        /// <summary>
        /// Constructs a response where a request should be scheduled.
        /// </summary>
        /// <param name="node">The node ID to which the request should be sent.</param>
        /// <param name="request">The request to send.</param>
        /// <param name="sendConfiguration"><code>true</code> to send the configuration, otherwise <code>false</code>.</param>
        private ScheduleResponse(int node, BuildRequest request, bool sendConfiguration)
        {
            Action = sendConfiguration ? ScheduleActionType.ScheduleWithConfiguration : ScheduleActionType.Schedule;
            NodeId = node;
            BuildRequest = request;
        }

        /// <summary>
        /// Constructs a response where a result should be sent or execution should be resumed.
        /// </summary>
        /// <param name="node">The node ID to which the result should be sent.</param>
        /// <param name="unblocker">The result to send.</param>
        private ScheduleResponse(int node, BuildRequestUnblocker unblocker)
        {
            Action = (unblocker.Result == null) ? ScheduleActionType.ResumeExecution : ScheduleActionType.ReportResults;
            NodeId = node;
            Unblocker = unblocker;
            BuildResult = unblocker.Result;
        }

        /// <summary>
        /// Creates a Schedule or ScheduleWithConfiguration response
        /// </summary>
        /// <param name="node">The node to which the response should be sent.</param>
        /// <param name="requestToSchedule">The request to be scheduled.</param>
        /// <param name="sendConfiguration">Flag indicating whether or not the configuration for the request must be sent to the node as well.</param>
        /// <returns>The ScheduleResponse.</returns>
        public static ScheduleResponse CreateScheduleResponse(int node, BuildRequest requestToSchedule, bool sendConfiguration)
        {
            return new ScheduleResponse(node, requestToSchedule, sendConfiguration);
        }

        /// <summary>
        /// Creates a ReportResult response.
        /// </summary>
        /// <param name="node">The node to which the response should be sent.</param>
        /// <param name="resultToReport">The result to be reported.</param>
        /// <returns>The ScheduleResponse.</returns>
        public static ScheduleResponse CreateReportResultResponse(int node, BuildResult resultToReport)
        {
            return new ScheduleResponse(node, new BuildRequestUnblocker(resultToReport));
        }

        /// <summary>
        /// Creates a ResumeExecution response.
        /// </summary>
        /// <param name="node">The node to which the response should be sent.</param>
        /// <param name="globalRequestIdToResume">The request which should resume executing.</param>
        /// <returns>The ScheduleResponse.</returns>
        public static ScheduleResponse CreateResumeExecutionResponse(int node, int globalRequestIdToResume)
        {
            return new ScheduleResponse(node, new BuildRequestUnblocker(globalRequestIdToResume));
        }

        /// <summary>
        /// Creates a CircularDependency response.
        /// </summary>
        /// <param name="node">The node to which the response should be sent.</param>
        /// <param name="parentRequest">The request which attempted to invoke the request causing the circular dependency.</param>
        /// <param name="requestCausingCircularDependency">The request which caused the circular dependency.</param>
        /// <returns>The ScheduleResponse.</returns>
        public static ScheduleResponse CreateCircularDependencyResponse(int node, BuildRequest parentRequest, BuildRequest requestCausingCircularDependency)
        {
            return new ScheduleResponse(node, parentRequest, requestCausingCircularDependency);
        }

        /// <summary>
        /// Creates a SubmissionComplete response.
        /// </summary>
        /// <param name="rootRequestResult">The result for the submission's root request.</param>
        /// <returns>The ScheduleResponse.</returns>
        public static ScheduleResponse CreateSubmissionCompleteResponse(BuildResult rootRequestResult)
        {
            return new ScheduleResponse(rootRequestResult);
        }

        /// <summary>
        /// Create a CreateNode response
        /// </summary>
        /// <param name="typeOfNodeToCreate">The type of node to create.</param>
        /// <param name="count">The number of new nodes of that particular affinity to create.</param>
        /// <returns>The ScheduleResponse.</returns>
        public static ScheduleResponse CreateNewNodeResponse(NodeAffinity typeOfNodeToCreate, int count)
        {
            return new ScheduleResponse(typeOfNodeToCreate, count);
        }

        /// <summary>
        /// Returns the schedule response as a descriptive string.
        /// </summary>
        public override string ToString()
        {
            switch (Action)
            {
                case ScheduleActionType.ReportResults:
                case ScheduleActionType.ResumeExecution:
                    return $"Act: {Action} Node: {NodeId} Request: {Unblocker.BlockedRequestId}";

                case ScheduleActionType.Schedule:
                    return $"Act: {Action} Node: {NodeId} Request: {BuildRequest.GlobalRequestId} Parent {BuildRequest.ParentGlobalRequestId}";

                case ScheduleActionType.ScheduleWithConfiguration:
                    return $"Act: {Action} Node: {NodeId} Request: {BuildRequest.GlobalRequestId} Parent {BuildRequest.ParentGlobalRequestId} Configuration: {BuildRequest.ConfigurationId}";

                case ScheduleActionType.CircularDependency:
                    return $"Act: {Action} Node: {NodeId} Request: {BuildRequest.GlobalRequestId} Parent {BuildRequest.ParentGlobalRequestId} Configuration: {BuildRequest.ConfigurationId}";

                case ScheduleActionType.SubmissionComplete:
                    return $"Act: {Action} Submission: {BuildResult.SubmissionId}";

                case ScheduleActionType.CreateNode:
                    return $"Act: {Action} Count: {NumberOfNodesToCreate}";

                case ScheduleActionType.NoAction:
                default:
                    return $"Act: {Action}";
            }
        }
    }
}
