using System;
using System.Collections.Generic;
using System.Linq;

namespace MainShell.Motion
{
    public sealed class MotionResult
    {
        public MotionResult(string axisName, MotionOperation operation, double? targetPosition, double startPosition, double endPosition, bool succeeded, bool cancelled, Exception exception, string message, bool alarmReported, DateTime startedAtUtc, DateTime finishedAtUtc, string correlationId = null, string batchId = null, string failureStage = null, bool timedOut = false, bool stoppedByCoordinator = false)
        {
            AxisName = axisName;
            Operation = operation;
            TargetPosition = targetPosition;
            StartPosition = startPosition;
            EndPosition = endPosition;
            Succeeded = succeeded;
            Cancelled = cancelled;
            Exception = exception;
            Message = message;
            AlarmReported = alarmReported;
            StartedAtUtc = startedAtUtc;
            FinishedAtUtc = finishedAtUtc;
            CorrelationId = correlationId;
            BatchId = batchId;
            FailureStage = failureStage;
            TimedOut = timedOut;
            StoppedByCoordinator = stoppedByCoordinator;
        }

        public string AxisName { get; }
        public MotionOperation Operation { get; }
        public double? TargetPosition { get; }
        public double StartPosition { get; }
        public double EndPosition { get; }
        public bool Succeeded { get; }
        public bool Cancelled { get; }
        public Exception Exception { get; }
        public string Message { get; }
        public bool AlarmReported { get; }
        public DateTime StartedAtUtc { get; }
        public DateTime FinishedAtUtc { get; }
        public string CorrelationId { get; }
        public string BatchId { get; }
        public string FailureStage { get; }
        public bool TimedOut { get; }
        public bool StoppedByCoordinator { get; }
        public long DurationMilliseconds => (long)(FinishedAtUtc - StartedAtUtc).TotalMilliseconds;

        public MotionResult WithAlarmReported(bool alarmReported)
        {
            if (AlarmReported == alarmReported)
            {
                return this;
            }

            return new MotionResult(AxisName, Operation, TargetPosition, StartPosition, EndPosition, Succeeded, Cancelled, Exception, Message, alarmReported, StartedAtUtc, FinishedAtUtc, CorrelationId, BatchId, FailureStage, TimedOut, StoppedByCoordinator);
        }

        public MotionResult WithFailureDetails(string failureStage, bool timedOut = false, bool stoppedByCoordinator = false, string correlationId = null, string batchId = null)
        {
            return new MotionResult(AxisName, Operation, TargetPosition, StartPosition, EndPosition, Succeeded, Cancelled, Exception, Message, AlarmReported, StartedAtUtc, FinishedAtUtc, correlationId ?? CorrelationId, batchId ?? BatchId, failureStage ?? FailureStage, timedOut, stoppedByCoordinator);
        }

        public void EnsureSuccess()
        {
            if (Succeeded)
            {
                return;
            }

            if (Exception != null)
            {
                throw Exception;
            }

            throw new InvalidOperationException(Message ?? string.Format("Motion failed. Axis: {0}", AxisName));
        }
    }

    public sealed class MotionBatchResult
    {
        public MotionBatchResult(IEnumerable<MotionResult> results)
        {
            Results = (results ?? Enumerable.Empty<MotionResult>()).ToList().AsReadOnly();
        }

        public IReadOnlyList<MotionResult> Results { get; private set; }
        public bool Succeeded => Results.All(x => x != null && x.Succeeded);
        public bool Cancelled => Results.Any(x => x != null && x.Cancelled);

        public void EnsureSuccess()
        {
            var failedResults = Results.Where(x => x != null && !x.Succeeded).ToList();
            if (failedResults.Count == 0)
            {
                return;
            }

            if (failedResults.Count == 1)
            {
                failedResults[0].EnsureSuccess();
                return;
            }

            throw new AggregateException(failedResults.Select(CreateException));
        }

        private static Exception CreateException(MotionResult result)
        {
            if (result == null)
            {
                return new InvalidOperationException("Motion result is null.");
            }

            return result.Exception ?? new InvalidOperationException(result.Message ?? string.Format("Motion failed. Axis: {0}", result.AxisName));
        }
    }
}
