using MwFramework.Device;
using System;
using System.Collections.Generic;
using System.Linq;

namespace MainShell.Motion
{
    public enum MotionRequestKind
    {
        MoveAbs,
        MoveRel,
        Home,
        Jog
    }

    public sealed class MotionMoveRequest
    {
        private MotionMoveRequest(string axisName, IAxis axis, double targetPosition, int timeoutMilliseconds, int? alarmId, MotionRequestKind requestKind, string source, IReadOnlyCollection<string> tags, string correlationId, string batchId, double? positionTolerance, bool stopOnFailure)
        {
            AxisName = axisName;
            Axis = axis;
            TargetPosition = targetPosition;
            TimeoutMilliseconds = timeoutMilliseconds;
            AlarmId = alarmId;
            RequestKind = requestKind;
            Source = source;
            Tags = tags ?? Array.Empty<string>();
            CorrelationId = correlationId;
            BatchId = batchId;
            PositionTolerance = positionTolerance;
            StopOnFailure = stopOnFailure;
        }

        public string AxisName { get; }
        public IAxis Axis { get; }
        public double TargetPosition { get; }
        public int TimeoutMilliseconds { get; }
        public int? AlarmId { get; }
        public MotionRequestKind RequestKind { get; }
        public string Source { get; }
        public IReadOnlyCollection<string> Tags { get; }
        public string CorrelationId { get; }
        public string BatchId { get; }
        public double? PositionTolerance { get; }
        public bool StopOnFailure { get; }

        public MotionMoveRequest WithMetadata(string source = null, IEnumerable<string> tags = null, string correlationId = null, string batchId = null, double? positionTolerance = null, bool? stopOnFailure = null)
        {
            return new MotionMoveRequest(
                AxisName,
                Axis,
                TargetPosition,
                TimeoutMilliseconds,
                AlarmId,
                RequestKind,
                string.IsNullOrWhiteSpace(source) ? Source : source,
                MergeTags(Tags, tags),
                string.IsNullOrWhiteSpace(correlationId) ? CorrelationId : correlationId,
                string.IsNullOrWhiteSpace(batchId) ? BatchId : batchId,
                positionTolerance ?? PositionTolerance,
                stopOnFailure ?? StopOnFailure);
        }

        public static MotionMoveRequest ForAxisName(string axisName, double targetPosition, int timeoutMilliseconds = MotionController.DefaultTimeoutMilliseconds, int? alarmId = null, string source = null, IEnumerable<string> tags = null, string correlationId = null, string batchId = null, double? positionTolerance = null, bool stopOnFailure = true)
        {
            if (string.IsNullOrWhiteSpace(axisName))
            {
                throw new ArgumentNullException(nameof(axisName));
            }

            return new MotionMoveRequest(axisName, null, targetPosition, timeoutMilliseconds, alarmId, MotionRequestKind.MoveAbs, source, NormalizeTags(tags), correlationId, batchId, positionTolerance, stopOnFailure);
        }

        public static MotionMoveRequest ForAxis(IAxis axis, double targetPosition, int timeoutMilliseconds = MotionController.DefaultTimeoutMilliseconds, int? alarmId = null, string source = null, IEnumerable<string> tags = null, string correlationId = null, string batchId = null, double? positionTolerance = null, bool stopOnFailure = true)
        {
            if (axis == null)
            {
                throw new ArgumentNullException(nameof(axis));
            }

            return new MotionMoveRequest(axis.Name, axis, targetPosition, timeoutMilliseconds, alarmId, MotionRequestKind.MoveAbs, source, NormalizeTags(tags), correlationId, batchId, positionTolerance, stopOnFailure);
        }

        public static MotionMoveRequest ForRelativeAxisName(string axisName, double distance, int timeoutMilliseconds = MotionController.DefaultTimeoutMilliseconds, int? alarmId = null, string source = null, IEnumerable<string> tags = null, string correlationId = null, string batchId = null, double? positionTolerance = null, bool stopOnFailure = true)
        {
            if (string.IsNullOrWhiteSpace(axisName))
            {
                throw new ArgumentNullException(nameof(axisName));
            }

            return new MotionMoveRequest(axisName, null, distance, timeoutMilliseconds, alarmId, MotionRequestKind.MoveRel, source, NormalizeTags(tags), correlationId, batchId, positionTolerance, stopOnFailure);
        }

        public static MotionMoveRequest ForRelativeAxis(IAxis axis, double distance, int timeoutMilliseconds = MotionController.DefaultTimeoutMilliseconds, int? alarmId = null, string source = null, IEnumerable<string> tags = null, string correlationId = null, string batchId = null, double? positionTolerance = null, bool stopOnFailure = true)
        {
            if (axis == null)
            {
                throw new ArgumentNullException(nameof(axis));
            }

            return new MotionMoveRequest(axis.Name, axis, distance, timeoutMilliseconds, alarmId, MotionRequestKind.MoveRel, source, NormalizeTags(tags), correlationId, batchId, positionTolerance, stopOnFailure);
        }

        private static IReadOnlyCollection<string> NormalizeTags(IEnumerable<string> tags)
        {
            return (tags ?? Enumerable.Empty<string>())
                .Where(x => !string.IsNullOrWhiteSpace(x))
                .Distinct(StringComparer.OrdinalIgnoreCase)
                .ToArray();
        }

        private static IReadOnlyCollection<string> MergeTags(IEnumerable<string> currentTags, IEnumerable<string> newTags)
        {
            return NormalizeTags((currentTags ?? Enumerable.Empty<string>()).Concat(newTags ?? Enumerable.Empty<string>()));
        }
    }
}
