using MainShell.Hardware;
using MainShell.Log;
using MainShell.Motion;
using MainShell.PageCalib.OriginCalib.Model;
using MaxwellFramework.Core.Attributes;
using MwFramework.Device;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace MainShell.PageCalib.OriginCalib.Service
{
    public sealed class OriginCalibrationExecutionResult
    {
        public OriginCalibrationExecutionResult(IDictionary<string, double> actualPositions)
        {
            var positions = actualPositions ?? new Dictionary<string, double>(StringComparer.Ordinal);
            ActualPositions = new ReadOnlyDictionary<string, double>(new Dictionary<string, double>(positions, StringComparer.Ordinal));
        }

        public IReadOnlyDictionary<string, double> ActualPositions { get; private set; }
    }

    [Singleton]
    public class OriginCalibrationMotionService
    {
        private const int PositionSettleDelayMilliseconds = 200;

        private readonly HardwareManager _hardware;
        private readonly SafeAxisMotion _safeAxisMotion;

        public OriginCalibrationMotionService(HardwareManager hardware, SafeAxisMotion safeAxisMotion)
        {
            _hardware = hardware ?? throw new ArgumentNullException(nameof(hardware));
            _safeAxisMotion = safeAxisMotion ?? throw new ArgumentNullException(nameof(safeAxisMotion));
        }

        public async Task MoveAxisAsync(string axisName, double position, CancellationToken cancellationToken = default(CancellationToken))
        {
            string.Format("原点标定正在移动轴“{0}”到位置 {1:F3}。", axisName, position).LogInfo();

            try
            {
                var result = await _safeAxisMotion.MoveAbsAsync(axisName, position, MotionController.DefaultTimeoutMilliseconds, cancellationToken).ConfigureAwait(false);
                result.EnsureSuccess();
                string.Format("原点标定轴“{0}”已到达目标位置 {1:F3}。", axisName, position).LogInfo();
            }
            catch (OperationCanceledException)
            {
                string.Format("原点标定轴“{0}”移动已取消。", axisName).LogInfo();
                throw;
            }
            catch (Exception ex)
            {
                string.Format("原点标定轴“{0}”移动失败：{1}", axisName, ex.Message).LogSysError();
                throw;
            }
        }

        public async Task<OriginCalibrationExecutionResult> ExecuteAsync(IEnumerable<AvoidanceAxisItem> avoidanceAxes, IEnumerable<CalibrationAxisItem> calibrationAxes, CancellationToken cancellationToken = default(CancellationToken))
        {
            var avoidanceAxisList = avoidanceAxes == null ? new List<AvoidanceAxisItem>() : avoidanceAxes.Where(item => item != null).ToList();
            var calibrationAxisList = calibrationAxes == null ? new List<CalibrationAxisItem>() : calibrationAxes.Where(item => item != null).ToList();

            string.Format(
                "原点标定执行开始，避让轴数量：{0}，标定轴数量：{1}。",
                avoidanceAxisList.Count,
                calibrationAxisList.Count).LogInfo();

            try
            {
                await ExecuteBatchAsync(CreateRequests(avoidanceAxisList, item => item.AxisName, item => item.Position), cancellationToken).ConfigureAwait(false);
                await ExecuteBatchAsync(CreateRequests(calibrationAxisList, item => item.AxisName, item => item.TargetPosition), cancellationToken).ConfigureAwait(false);

                string.Format(
                    "原点标定等待 {0} ms 以稳定轴反馈，再读取实际位置。",
                    PositionSettleDelayMilliseconds).LogInfo();
                await Task.Delay(PositionSettleDelayMilliseconds, cancellationToken).ConfigureAwait(false);

                var actualPositions = new Dictionary<string, double>(StringComparer.Ordinal);
                foreach (var calibrationAxis in calibrationAxisList)
                {
                    if (calibrationAxis == null || string.IsNullOrWhiteSpace(calibrationAxis.AxisName))
                    {
                        continue;
                    }

                    var axis = _hardware.GetAxisByName(calibrationAxis.AxisName);
                    if (axis == null)
                    {
                        continue;
                    }

                    actualPositions[calibrationAxis.AxisName] = Math.Round(GetActualPosition(axis), 3);
                    string.Format(
                        "原点标定已采集轴“{0}”的实际位置：{1:F3}。",
                        calibrationAxis.AxisName,
                        actualPositions[calibrationAxis.AxisName]).LogInfo();
                }

                string.Format("原点标定执行完成，共采集 {0} 个标定轴的实际位置。", actualPositions.Count).LogInfo();
                return new OriginCalibrationExecutionResult(actualPositions);
            }
            catch (OperationCanceledException)
            {
                "原点标定执行已取消。".LogInfo();
                throw;
            }
            catch (Exception ex)
            {
                string.Format("原点标定执行失败：{0}", ex.Message).LogSysError();
                throw;
            }
        }

        private async Task ExecuteBatchAsync(MotionMoveRequest[] requests, CancellationToken cancellationToken)
        {
            if (requests.Length == 0)
            {
                return;
            }

            var result = await _safeAxisMotion.SafeMoveAsync(cancellationToken, requests).ConfigureAwait(false);
            result.EnsureSuccess();
        }

        private MotionMoveRequest[] CreateRequests<TItem>(IEnumerable<TItem> items, Func<TItem, string> axisNameAccessor, Func<TItem, double> positionAccessor)
        {
            if (items == null)
            {
                return new MotionMoveRequest[0];
            }

            var requests = new List<MotionMoveRequest>();
            foreach (var item in items)
            {
                if (item == null)
                {
                    continue;
                }

                var axisName = axisNameAccessor(item);
                if (string.IsNullOrWhiteSpace(axisName))
                {
                    continue;
                }

                if (_hardware.GetAxisByName(axisName) == null)
                {
                    string.Format("原点标定轴“{0}”在硬件管理器中不存在。", axisName).LogSysError();
                    continue;
                }

                requests.Add(MotionMoveRequest.ForAxisName(axisName, positionAccessor(item)));
            }

            return requests.ToArray();
        }

        private static double GetActualPosition(IAxis axis)
        {
            return axis.State != null ? axis.State.ActualPos : axis.GetPositionImmediate();
        }
    }
}