using MaxwellFramework.Core.Attributes;
using MainShell.Alarm;
using MainShell.Hardware;
using MainShell.Log;
using MainShell.Motion.Safety;
using MwFramework.Device;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace MainShell.Motion
{
    [Singleton]
    public sealed class MotionAlarmReporter
    {
        private readonly AlarmOperate _alarmOperate;

        public MotionAlarmReporter(AlarmOperate alarmOperate)
        {
            _alarmOperate = alarmOperate ?? throw new ArgumentNullException(nameof(alarmOperate));
        }

        public async Task<bool> ReportAlarmAsync(int? alarmId)
        {
            if (!alarmId.HasValue)
            {
                return false;
            }

            await _alarmOperate.AlertAsync(alarmId.Value).ConfigureAwait(false);
            return true;
        }
    }

    [Singleton]
    public sealed class StagePlatformSafetyOptionsProvider
    {
        private const double DefaultMaxPlaneSpread = 5.0d;
        private const double DefaultMaxTravelPerStep = 10.0d;

        public StagePlatformSafetyOptions GetOptions()
        {
            return new StagePlatformSafetyOptions(DefaultMaxPlaneSpread, DefaultMaxTravelPerStep);
        }
    }

    [Singleton]
    public sealed class MotionPrecheckService
    {
        private readonly MotionAlarmReporter _motionAlarmReporter;
        private readonly IReadOnlyList<IMotionSafetyCheck> _motionSafetyChecks;

        public MotionPrecheckService(HardwareManager hardware, MotionSafetyStateProvider motionSafetyStateProvider, MotionAlarmReporter motionAlarmReporter, StagePlatformSafetyOptionsProvider stagePlatformSafetyOptionsProvider)
        {
            if (hardware == null)
            {
                throw new ArgumentNullException(nameof(hardware));
            }

            if (motionSafetyStateProvider == null)
            {
                throw new ArgumentNullException(nameof(motionSafetyStateProvider));
            }

            _motionAlarmReporter = motionAlarmReporter ?? throw new ArgumentNullException(nameof(motionAlarmReporter));
            var stagePlatformSafetyOptions = (stagePlatformSafetyOptionsProvider ?? throw new ArgumentNullException(nameof(stagePlatformSafetyOptionsProvider))).GetOptions();
            _motionSafetyChecks = new IMotionSafetyCheck[]
            {
                new MotionRequestDuplicateAxisCheck(),
                new MotionBatchSameSourceCheck(),
                new EmergencyStopReleasedCheck(motionSafetyStateProvider),
                new SafetyDoorClosedCheck(motionSafetyStateProvider),
                new AutoFlowModeCheck(motionSafetyStateProvider),
                new StageVacuumReadyCheck(motionSafetyStateProvider),
                new BondHeadSafePositionCheck(motionSafetyStateProvider),
                new StagePlatformPlaneMoveSafetyCheck(hardware, stagePlatformSafetyOptions)
            };
        }

        public async Task ValidateAsync(MotionSafetyContext context, CancellationToken cancellationToken, int? fallbackAlarmId)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            foreach (var check in _motionSafetyChecks)
            {
                var result = check.Check(context);
                if (result == null || result.IsPassed)
                {
                    continue;
                }

                var failureMessage = string.Format("Motion precheck failed. Rule:{0} Kind:{1} Source:{2} Message:{3}", result.RuleName, context.RequestKind, context.Source ?? "N/A", result.Message);
                failureMessage.LogSysError();
                var alarmId = result.AlarmId ?? fallbackAlarmId;
                if (alarmId.HasValue)
                {
                    await _motionAlarmReporter.ReportAlarmAsync(alarmId).ConfigureAwait(false);
                }

                throw new InvalidOperationException(failureMessage);
            }
        }
    }

    [Singleton]
    public sealed class MotionExecutionLogger
    {
        public void HandleMotionStarted(object sender, MotionStartedEventArgs e)
        {
            try
            {
                string.Format("Axis:{0} Motion:{1} Started StartPos:{2:F3} Target:{3}", e.AxisName, e.Operation, e.StartPosition, e.TargetPosition.HasValue ? e.TargetPosition.Value.ToString("F3") : "N/A").LogInfo();
            }
            catch (Exception ex)
            {
                string.Format("Axis:{0} motion start log failed. Error:{1}", e.AxisName, ex.Message).LogSysError();
            }
        }

        public void HandleMotionFinished(object sender, MotionFinishedEventArgs e)
        {
            try
            {
                var result = e.Result;
                var message = string.Format("Axis:{0} Motion:{1} Finished Success:{2} Cancelled:{3} EndPos:{4:F3} Duration:{5}ms FailureStage:{6}", result.AxisName, result.Operation, result.Succeeded, result.Cancelled, result.EndPosition, result.DurationMilliseconds, result.FailureStage ?? "N/A");
                if (result.Succeeded)
                {
                    message.LogInfo();
                    return;
                }

                var error = string.Format("{0} Error:{1}", message, result.Exception);
                error.LogSysError();
            }
            catch (Exception ex)
            {
                string.Format("Axis:{0} motion finish log failed. Error:{1}", e.AxisName, ex.Message).LogSysError();
            }
        }
    }

    [Singleton]
    public sealed class MotionControllerRegistry
    {
        private readonly Dictionary<string, ControllerEntry> _controllers = new Dictionary<string, ControllerEntry>(StringComparer.OrdinalIgnoreCase);
        private readonly object _syncRoot = new object();
        private readonly MotionExecutionLogger _motionExecutionLogger;

        private sealed class ControllerEntry
        {
            public ControllerEntry(IAxis axis, MotionController controller)
            {
                Axis = axis ?? throw new ArgumentNullException(nameof(axis));
                Controller = controller ?? throw new ArgumentNullException(nameof(controller));
            }

            public IAxis Axis { get; }
            public MotionController Controller { get; }
        }

        public MotionControllerRegistry(MotionExecutionLogger motionExecutionLogger)
        {
            _motionExecutionLogger = motionExecutionLogger ?? throw new ArgumentNullException(nameof(motionExecutionLogger));
        }

        public MotionController GetController(IAxis axis)
        {
            if (axis == null)
            {
                throw new ArgumentNullException(nameof(axis));
            }

            var axisName = axis.Name;
            if (string.IsNullOrWhiteSpace(axisName))
            {
                return CreateController(axis);
            }

            lock (_syncRoot)
            {
                ControllerEntry entry;
                if (_controllers.TryGetValue(axisName, out entry) && ReferenceEquals(entry.Axis, axis))
                {
                    return entry.Controller;
                }

                var controller = CreateController(axis);
                _controllers[axisName] = new ControllerEntry(axis, controller);
                return controller;
            }
        }

        private MotionController CreateController(IAxis axis)
        {
            var controller = new MotionController(axis);
            controller.MotionStarted += _motionExecutionLogger.HandleMotionStarted;
            controller.MotionFinished += _motionExecutionLogger.HandleMotionFinished;
            return controller;
        }
    }
}
