using MainShell.Common;
using MainShell.DeviceMaintance.Model;
using MainShell.Hardware;
using MainShell.Motion;
using MainShell.Parameter;
using MainShell.ParaSetting.Model;
using MaxwellFramework.Core.Attributes;
using MaxwellFramework.Core.Interfaces;
using MW.WorkFlow;
using MwFramework.ManagerService;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace MainShell.Process
{
    [Singleton]
    public class NeedleZCalibrationService
    {
        private const double Precision = 0.001;

        private readonly HardwareManager _hardwareManager;
        private readonly SafeAxisMotion _safeAxisMotion;
        private readonly IParamList _paramList;
        private readonly IDeviceIoMonitorService _deviceIoMonitorService;

        public NeedleZCalibrationService(HardwareManager hardwareManager, SafeAxisMotion safeAxisMotion, IParameterManager parameterManager, IDeviceIoMonitorService deviceIoMonitorService)
        {
            _hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
            _safeAxisMotion = safeAxisMotion ?? throw new ArgumentNullException(nameof(safeAxisMotion));
            _paramList = parameterManager as IParamList;
            _deviceIoMonitorService = deviceIoMonitorService ?? throw new ArgumentNullException(nameof(deviceIoMonitorService));
        }

        public NeedleZCalibrationExecutionContext GetExecutionContext(int? inputTouchCount, bool? inputAutoRaiseZ1)
        {
            NeedleCalibrationSetting needleCalibrationSetting = _paramList?.GetParameter<NeedleCalibrationSetting>();
            EquipmentParaSysSetting equipmentParaSysSetting = _paramList?.GetParameter<EquipmentParaSysSetting>();
            return CreateExecutionContext(needleCalibrationSetting, equipmentParaSysSetting, inputTouchCount, inputAutoRaiseZ1);
        }

        public NeedleZCalibrationExecutionContext CreateExecutionContext(NeedleCalibrationSetting needleCalibrationSetting, EquipmentParaSysSetting equipmentParaSysSetting, int? inputTouchCount, bool? inputAutoRaiseZ1)
        {
            ValidateCalibrationSetting(needleCalibrationSetting);

            NeedleZCalibrationItem item = needleCalibrationSetting.NeedleZCalibrationItem;
            ValidateCalibrationItem(item);

            int touchCount = Math.Max(1, inputTouchCount ?? item.NeedleTouchCount);
            bool autoRaiseZ1 = inputAutoRaiseZ1 ?? true;
            double safeHeight = ResolveSafeHeight(equipmentParaSysSetting);

            return new NeedleZCalibrationExecutionContext(needleCalibrationSetting, item, touchCount, autoRaiseZ1, safeHeight);
        }

        public async Task<double> ExecuteSingleTouchAsync(bool autoRaiseZ1, CancellationToken cancellationToken)
        {
            return await ExecuteSingleTouchAsync(autoRaiseZ1, cancellationToken, null);
        }

        public async Task<double> ExecuteSingleTouchAsync(bool autoRaiseZ1, CancellationToken cancellationToken, Action<double> onResult)
        {
            NeedleZCalibrationExecutionContext executionContext = GetExecutionContext(1, autoRaiseZ1);
            IReadOnlyList<double> results = await ExecuteCalibrationAsync(executionContext, null, cancellationToken, onResult);
            if (results == null || results.Count == 0)
            {
                throw new InvalidOperationException("单次对刀未返回结果。");
            }

            return results[0];
        }

        public async Task<IReadOnlyList<double>> ExecuteCalibrationAsync(NeedleZCalibrationExecutionContext executionContext, ActivityControl activityControl, CancellationToken cancellationToken, Action<double> onResult)
        {
            if (executionContext == null)
            {
                throw new ArgumentNullException(nameof(executionContext));
            }

            List<double> results = new List<double>();
            for (int i = 0; i < executionContext.TouchCount; i++)
            {
                await CheckControlAsync(activityControl, cancellationToken);
                double result = await ExecuteSingleCalibrationAsync(executionContext, activityControl, cancellationToken);
                results.Add(result);
                onResult?.Invoke(result);
            }

            if (executionContext.AutoRaiseZ1)
            {
                await CheckControlAsync(activityControl, cancellationToken);
                _safeAxisMotion.MoveAbs(_hardwareManager.Axis_Z1, executionContext.Item.Z1StartPosition, cancellationToken);
            }

            return results;
        }

        private async Task<double> ExecuteSingleCalibrationAsync(NeedleZCalibrationExecutionContext executionContext, ActivityControl activityControl, CancellationToken cancellationToken)
        {
            bool foundSignal = false;
            double start = executionContext.Item.Z1StartPosition;
            double end = start - executionContext.Item.Z1DropMaxPosition;

            await CheckControlAsync(activityControl, cancellationToken);
            _safeAxisMotion.MoveAbs(_hardwareManager.Axis_Z1, start, cancellationToken);

            while (Math.Abs(end - start) > Precision)
            {
                await CheckControlAsync(activityControl, cancellationToken);
                double mid = (start + end) / 2.0;
                if (mid < executionContext.SafeHeight)
                {
                    throw new InvalidOperationException(string.Format("目标位置[{0}]低于Z1对刀安全高度[{1}]，运动不安全。", mid, executionContext.SafeHeight));
                }

                _safeAxisMotion.MoveAbs(_hardwareManager.Axis_Z1, mid, cancellationToken);
                if (IsToolSetterTriggered())
                {
                    end = mid;
                    foundSignal = true;
                }
                else
                {
                    start = mid;
                }
            }

            if (!foundSignal)
            {
                throw new InvalidOperationException("在范围内未找到对刀信号，对刀失败。\r\n请检查对刀块位置与感测信号状态。");
            }

            return _hardwareManager.Axis_Z1.State != null ? _hardwareManager.Axis_Z1.State.ActualPos : _hardwareManager.Axis_Z1.GetPositionImmediate();
        }

        private static async Task CheckControlAsync(ActivityControl activityControl, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (activityControl == null)
            {
                return;
            }

            activityControl.CancellationToken.ThrowIfCancellationRequested();
            await activityControl.CheckPauseAsync();
            cancellationToken.ThrowIfCancellationRequested();
            activityControl.CancellationToken.ThrowIfCancellationRequested();
        }

        private bool IsToolSetterTriggered()
        {
            return _deviceIoMonitorService.IsPointOn(DeviceIoNames.Stage.ToolSetterSignal);
        }
        private static void ValidateCalibrationSetting(NeedleCalibrationSetting needleCalibrationSetting)
        {
            if (needleCalibrationSetting == null || needleCalibrationSetting.NeedleZCalibrationItem == null)
            {
                throw new InvalidOperationException("未获取到针头对刀参数。");
            }
        }

        private static void ValidateCalibrationItem(NeedleZCalibrationItem item)
        {
            if (item == null)
            {
                throw new ArgumentNullException(nameof(item));
            }

            if (item.Z1Speed <= 0)
            {
                throw new InvalidOperationException("Z1速度必须大于0。");
            }

            if (item.Z1DropMaxPosition <= 0)
            {
                throw new InvalidOperationException("Z1下降最大位置必须大于0。");
            }
        }

        private static double ResolveSafeHeight(EquipmentParaSysSetting equipmentParaSysSetting)
        {
            if (equipmentParaSysSetting == null || equipmentParaSysSetting.SafeParaSysItem == null)
            {
                return 0;
            }

            return equipmentParaSysSetting.SafeParaSysItem.NeedleCalibSafeHeight;
        }
    }

    public class NeedleZCalibrationExecutionContext
    {
        public NeedleZCalibrationExecutionContext(NeedleCalibrationSetting needleCalibrationSetting, NeedleZCalibrationItem item, int touchCount, bool autoRaiseZ1, double safeHeight)
        {
            NeedleCalibrationSetting = needleCalibrationSetting ?? throw new ArgumentNullException(nameof(needleCalibrationSetting));
            Item = item ?? throw new ArgumentNullException(nameof(item));
            TouchCount = touchCount;
            AutoRaiseZ1 = autoRaiseZ1;
            SafeHeight = safeHeight;
        }

        public NeedleCalibrationSetting NeedleCalibrationSetting { get; private set; }

        public NeedleZCalibrationItem Item { get; private set; }

        public int TouchCount { get; private set; }

        public bool AutoRaiseZ1 { get; private set; }

        public double SafeHeight { get; private set; }
    }
}
