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

using System;

#nullable disable

namespace Microsoft.Build.BackEnd
{
    /// <summary>
    /// The result of executing the task or target.
    /// </summary>
    internal enum WorkUnitResultCode
    {
        /// <summary>
        /// The work unit was skipped.
        /// </summary>
        Skipped,

        /// <summary>
        /// The work unit succeeded.
        /// </summary>
        Success,

        /// <summary>
        /// The work unit failed.
        /// </summary>
        Failed,

        /// <summary>
        /// The work unit was cancelled.
        /// </summary>
        Canceled,
    }

    /// <summary>
    /// Indicates whether further work should be done.
    /// </summary>
    internal enum WorkUnitActionCode
    {
        /// <summary>
        /// Work should proceed with the next work unit.
        /// </summary>
        Continue,

        /// <summary>
        /// No further work units should be executed.
        /// </summary>
        Stop,
    }

    /// <summary>
    /// A result of executing a target or task.
    /// </summary>
    internal class WorkUnitResult : ITranslatable
    {
        /// <summary>
        /// The result.
        /// </summary>
        private WorkUnitResultCode _resultCode;

        /// <summary>
        /// The next action to take.
        /// </summary>
        private WorkUnitActionCode _actionCode;

        /// <summary>
        /// The exception from the failure, if any.
        /// </summary>
        private Exception _exception;

        /// <summary>
        /// Creates a new work result ready for aggregation during batches.
        /// </summary>
        internal WorkUnitResult()
        {
            _resultCode = WorkUnitResultCode.Skipped;
            _actionCode = WorkUnitActionCode.Continue;
            _exception = null;
        }

        /// <summary>
        /// Creates a work result with the specified result codes.
        /// </summary>
        internal WorkUnitResult(WorkUnitResultCode resultCode, WorkUnitActionCode actionCode, Exception e)
        {
            _resultCode = resultCode;
            _actionCode = actionCode;
            _exception = e;
        }

        /// <summary>
        /// Translator constructor
        /// </summary>
        private WorkUnitResult(ITranslator translator)
        {
            ((ITranslatable)this).Translate(translator);
        }

        /// <summary>
        /// Get the result code.
        /// </summary>
        internal WorkUnitResultCode ResultCode => _resultCode;

        /// <summary>
        /// Get the action code.
        /// </summary>
        internal WorkUnitActionCode ActionCode
        {
            get => _actionCode;
            set => _actionCode = value;
        }

        /// <summary>
        /// Get the exception
        /// </summary>
        internal Exception Exception => _exception;

        #region INodePacketTranslatable Members

        /// <summary>
        /// Translator.
        /// </summary>
        public void Translate(ITranslator translator)
        {
            translator.TranslateEnum(ref _resultCode, (int)_resultCode);
            translator.TranslateEnum(ref _actionCode, (int)_actionCode);
            translator.TranslateException(ref _exception);
        }

        #endregion

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

        /// <summary>
        /// Aggregates the specified result with this result and returns the aggregation.
        /// </summary>
        /// <remarks>
        /// The rules are:
        /// 1. Errors take precedence over success.
        /// 2. Success takes precedence over skipped.
        /// 3. Stop takes precedence over continue.
        /// 4. The first exception in the result wins.
        /// </remarks>
        internal WorkUnitResult AggregateResult(WorkUnitResult result)
        {
            WorkUnitResultCode aggregateResult = _resultCode;
            WorkUnitActionCode aggregateAction = _actionCode;
            Exception aggregateException = _exception;

            if (result._resultCode == WorkUnitResultCode.Canceled || result.ResultCode == WorkUnitResultCode.Failed)
            {
                // Failed and canceled take priority
                aggregateResult = result._resultCode;
            }
            else if (result._resultCode == WorkUnitResultCode.Success && aggregateResult == WorkUnitResultCode.Skipped)
            {
                // Success only counts if we were previously in the skipped category.
                aggregateResult = result._resultCode;
            }

            if (result._actionCode == WorkUnitActionCode.Stop)
            {
                aggregateAction = result.ActionCode;
            }

            if (aggregateException == null)
            {
                aggregateException = result._exception;
            }

            return new WorkUnitResult(aggregateResult, aggregateAction, aggregateException);
        }
    }
}
