using MainShell.Common;
using MainShell.DeviceMaintance.Model;
using MainShell.EventArgsFolder;
using MainShell.Hardware;
using MainShell.Models;
using MainShell.Motion;
using MainShell.Parameter;
using MainShell.Process;
using MainShell.Recipe.Models;
using MaxwellControl.Tools;
using MaxwellFramework.Core.Interfaces;
using MW.WorkFlow;
using MwFramework.Device;
using MwFramework.ManagerService;
using MXJM.Parameter.Maintance;
using Stylet;
using StyletIoC;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace MainShell.DeviceMaintance.ViewModel
{
    public class NeedleCaliViewModel : DeviceMaintanceBaseViewModel, IPage, IHandle<NeedleZCalibrationResultEventArgs>
    {
        private const string CalibrationWorkflowName = "NeedleZCalibration";

        private readonly IParameterManager _parameterManager;
        private readonly IParamList _paramList;
        private readonly SafeAxisMotion _safeAxisMotion;
        private readonly StagePlatformMotionService _stagePlatformMotionService;
        private readonly HardwareManager _hardwareManager;
        private readonly IDeviceIoMonitorService _deviceIoMonitorService;
        private readonly IEventAggregator _eventAggregator;
        private readonly WorkflowRunner _workflowRunner;
        private readonly NeedleZCalibrationService _needleZCalibrationService;
        private ParameterHelper _parameterHelper = new ParameterHelper();
        private NeedleCalibrationSetting _needleCalibrationSetting;
        private NeedleZCalibrationItem _needleZCalibrationItem;
        private CancellationTokenSource _cancellationTokenSource;
        private bool _uiEnable = true;
        private bool _autoRaiseZ1 = true;
        private bool _isEnabledNeedleCalibrationControl = true;
        private NeedleTouchRecord _selectedRecord;

        public NeedleCaliViewModel(IParameterManager parameterManager, SafeAxisMotion safeAxisMotion,
            StagePlatformMotionService stagePlatformMotionService, HardwareManager hardwareManager,
            IDeviceIoMonitorService deviceIoMonitorService, IEventAggregator eventAggregator,
            WorkflowRunner workflowRunner, NeedleZCalibrationService needleZCalibrationService)
        {
            _parameterManager = parameterManager;
            _paramList = parameterManager as IParamList;
            _safeAxisMotion = safeAxisMotion;
            _stagePlatformMotionService = stagePlatformMotionService;
            _hardwareManager = hardwareManager;
            _deviceIoMonitorService = deviceIoMonitorService;
            _eventAggregator = eventAggregator;
            _workflowRunner = workflowRunner;
            _needleZCalibrationService = needleZCalibrationService;
            _eventAggregator?.Subscribe(this);
        }

        public string Name { get; set; } = "NeedleCaliMaint";

        public ParameterHelper ParameterHelper
        {
            get => _parameterHelper;
            set => SetAndNotify(ref _parameterHelper, value);
        }

        public NeedleCalibrationSetting NeedleCalibrationSetting
        {
            get => _needleCalibrationSetting;
            set => SetAndNotify(ref _needleCalibrationSetting, value);
        }

        public NeedleZCalibrationItem NeedleZCalibrationItem
        {
            get => _needleZCalibrationItem;
            set => SetAndNotify(ref _needleZCalibrationItem, value);
        }

        public bool UiEnable
        {
            get => _uiEnable;
            set
            {
                if (SetAndNotify(ref _uiEnable, value))
                {
                    OnUiStateChanged(value);
                }
            }
        }

        public bool AutoRaiseZ1
        {
            get => _autoRaiseZ1;
            set => SetAndNotify(ref _autoRaiseZ1, value);
        }

        public bool IsEnabledNeedleCalibrationControl
        {
            get => _isEnabledNeedleCalibrationControl;
            set => SetAndNotify(ref _isEnabledNeedleCalibrationControl, value);
        }

        public NeedleTouchRecord SelectedRecord
        {
            get => _selectedRecord;
            set => SetAndNotify(ref _selectedRecord, value);
        }

        protected override void OnViewLoaded()
        {
            base.OnViewLoaded();
            LoadParameters();
        }

        protected override void OnClose()
        {
            _eventAggregator?.Unsubscribe(this);
            base.OnClose();
        }

        public void Handle(NeedleZCalibrationResultEventArgs message)
        {
            if (message == null || NeedleZCalibrationItem == null)
            {
                return;
            }

            CommonUti.RunOnUi(() =>
            {
                EnsureRecordCollection();
                int index = NeedleZCalibrationItem.NeedleTouchRecords.Count + 1;
                NeedleZCalibrationItem.NeedleTouchHeight = message.Result;
                NeedleZCalibrationItem.NeedleTouchRecords.Add(new NeedleTouchRecord
                {
                    CurrentCount = index,
                    Height = message.Result,
                });
                SelectedRecord = NeedleZCalibrationItem.NeedleTouchRecords.LastOrDefault();
                RefreshStatistics();
            });
        }

        public void MoveCzCurrentLocation()
        {
            ExecuteMotion(() =>
            {
                if (NeedleZCalibrationItem == null)
                {
                    return;
                }

                _safeAxisMotion.MoveAbs(_hardwareManager.Axis_SZ, NeedleZCalibrationItem.CZHeight);
                _stagePlatformMotionService.MoveFlat(NeedleZCalibrationItem.StageHeight);
            });
        }

        public void ReadGantry2CurrentLocation()
        {
            CommonUti.RunOnUi(() =>
            {
                if (NeedleZCalibrationItem == null)
                {
                    return;
                }

                NeedleZCalibrationItem.Gantry2AvoidPositionX = GetAxisPosition(_hardwareManager.Axis_X2);
                NeedleZCalibrationItem.Gantry2AvoidPositionY = GetAxisPosition(_hardwareManager.Axis_Y2);
            });
        }

        public void MoveGantry2ToCurrentLocation()
        {
            ExecuteMotion(() =>
            {
                if (NeedleZCalibrationItem == null)
                {
                    return;
                }

                _safeAxisMotion.MoveAbs(_hardwareManager.Axis_X2, NeedleZCalibrationItem.Gantry2AvoidPositionX);
                _safeAxisMotion.MoveAbs(_hardwareManager.Axis_Y2, NeedleZCalibrationItem.Gantry2AvoidPositionY);
            });
        }

        public void ReadSupportPinCurrentLocation()
        {
            CommonUti.RunOnUi(() =>
            {
                if (NeedleZCalibrationItem == null)
                {
                    return;
                }

                NeedleZCalibrationItem.SupportPinPositionX = GetAxisPosition(_hardwareManager.Axis_X1);
                NeedleZCalibrationItem.SupportPinPositionY = GetAxisPosition(_hardwareManager.Axis_Y1);
            });
        }

        public void MoveSupportPinToCurrentLocation()
        {
            ExecuteMotion(() =>
            {
                if (NeedleZCalibrationItem == null)
                {
                    return;
                }

                _safeAxisMotion.MoveAbs(_hardwareManager.Axis_X1, NeedleZCalibrationItem.SupportPinPositionX);
                _safeAxisMotion.MoveAbs(_hardwareManager.Axis_Y1, NeedleZCalibrationItem.SupportPinPositionY);
            });
        }

        public void SetBaseHeight()
        {
            CommonUti.RunOnUi(() =>
            {
                if (NeedleZCalibrationItem == null)
                {
                    return;
                }

                NeedleZCalibrationItem.NeedleTouchHeightBase = NeedleZCalibrationItem.CurrentNeedleTouchHeight;
                RefreshStatistics();
            });
        }

        public async void StartSingleZ1CalibrationAsync()
        {
            await ExecuteCalibrationAsync(1, false);
        }

        public void UseCurrentZ1Height()
        {
            CommonUti.RunOnUi(() =>
            {
                if (NeedleZCalibrationItem == null)
                {
                    return;
                }

                NeedleZCalibrationItem.CurrentNeedleTouchHeight = NeedleZCalibrationItem.NeedleTouchHeightAve + NeedleZCalibrationItem.KnifeOffset;
                RefreshStatistics();
                SaveParameterInternal(true);
            });
        }

        public async void StartCalibrationAsync()
        {
            await ExecuteCalibrationAsync(Math.Max(1, NeedleZCalibrationItem != null ? NeedleZCalibrationItem.NeedleTouchCount : 1), true);
        }

        public async void StopCalibrationAsync()
        {
            _cancellationTokenSource?.Cancel();
            if (_workflowRunner != null && _workflowRunner.IsRunning)
            {
                await _workflowRunner.StopAsync();
            }

            _safeAxisMotion.Stop(
                _hardwareManager.Axis_X1,
                _hardwareManager.Axis_X2,
                _hardwareManager.Axis_Y1,
                _hardwareManager.Axis_Y2,
                _hardwareManager.Axis_Z1,
                _hardwareManager.Axis_SZ,
                _hardwareManager.Axis_Stage_Z7,
                _hardwareManager.Axis_Stage_Z8,
                _hardwareManager.Axis_Stage_Z9);

            UiEnable = true;
        }

        public void SaveCalibration()
        {
            CommonUti.RunOnUi(() => SaveParameterInternal(true));
        }

        private async Task ExecuteCalibrationAsync(int touchCount, bool clearRecords)
        {
            if (NeedleZCalibrationItem == null)
            {
                LoadParameters();
            }

            if (NeedleZCalibrationItem == null)
            {
                LocalizedMessageBox.Show(MessageKey.NeedleCalibrationLoadFailed, MessageKey.TitleError);
                return;
            }

            if (touchCount <= 0)
            {
                LocalizedMessageBox.Show(MessageKey.NeedleCalibrationTouchCountMustGreaterThanZero, MessageKey.TitleWarning);
                return;
            }

            InitCancelToken();
            if (clearRecords)
            {
                CommonUti.RunOnUi(() =>
                {
                    EnsureRecordCollection();
                    NeedleZCalibrationItem.NeedleTouchRecords.Clear();
                    NeedleZCalibrationItem.NeedleTouchHeight = 0;
                    NeedleZCalibrationItem.NeedleTouchHeightAve = 0;
                    NeedleZCalibrationItem.CurrentNeedleTouchHeight = 0;
                    NeedleZCalibrationItem.NeedleTouchOffset = 0;
                });
            }

            UiEnable = false;
            try
            {
                WorkflowContext context = new WorkflowContext();
                NeedleZCalibrationWorkflowData workflowData = new NeedleZCalibrationWorkflowData(
                    CalibrationWorkflowName,
                    touchCount,
                    AutoRaiseZ1,
                    _cancellationTokenSource.Token,
                    _eventAggregator,
                    _hardwareManager,
                    _safeAxisMotion,
                    _parameterManager,
                    _deviceIoMonitorService);
                context.SetData(WorkflowContextKeys.WorkflowName, workflowData.WorkflowName);
                context.SetData(WorkflowContextKeys.NeedleZCalibrationWorkflowData, workflowData);

                NeedleZCalibrationActivity activity = new NeedleZCalibrationActivity(_needleZCalibrationService);
                WorkflowRunCompletedEventArgs result = await _workflowRunner.RunActivityWithResultAsync(activity, context);
                switch (result != null ? result.FinalState : WorkflowState.Faulted)
                {
                    case WorkflowState.Completed:
                        RefreshStatistics();
                        LocalizedMessageBox.Show(MessageKey.NeedleCalibrationCompleted, MessageKey.TitleInfo);
                        break;
                    case WorkflowState.Canceled:
                        LocalizedMessageBox.Show(MessageKey.NeedleCalibrationStopped, MessageKey.TitleWarning);
                        break;
                    case WorkflowState.Faulted:
                        string failureMessage = string.IsNullOrWhiteSpace(result != null ? result.FailureMessage : null)
                            ? (result != null ? result.Error : null)
                            : result.FailureMessage;
                        if (string.IsNullOrWhiteSpace(failureMessage))
                        {
                            LocalizedMessageBox.Show(MessageKey.ProcessFailed, MessageKey.TitleError);
                        }
                        else
                        {
                            LocalizedMessageBox.ShowFormat(MessageKey.ProcessFailedWithReason, MessageKey.TitleError, MessageBoxButton.OK, MessageBoxImage.Error, failureMessage);
                        }
                        break;
                }
            }
            catch (OperationCanceledException)
            {
                LocalizedMessageBox.Show(MessageKey.NeedleCalibrationCanceled, MessageKey.TitleWarning);
            }
            catch (Exception ex)
            {
                LocalizedMessageBox.ShowFormat(MessageKey.ProcessFailedWithReason, MessageKey.TitleError, MessageBoxButton.OK, MessageBoxImage.Error, ex.Message);
            }
            finally
            {
                UiEnable = true;
            }
        }

        private void RefreshStatistics()
        {
            if (NeedleZCalibrationItem == null)
            {
                return;
            }

            ObservableCollection<NeedleTouchRecord> records = NeedleZCalibrationItem.NeedleTouchRecords;
            if (records == null || records.Count == 0)
            {
                NeedleZCalibrationItem.NeedleTouchHeightAve = 0;
                NeedleZCalibrationItem.CurrentNeedleTouchHeight = 0;
                NeedleZCalibrationItem.NeedleTouchOffset = 0;
                return;
            }

            NeedleZCalibrationItem.NeedleTouchHeightAve = records.Average(x => x.Height);
            NeedleZCalibrationItem.CurrentNeedleTouchHeight = NeedleZCalibrationItem.NeedleTouchHeightAve + NeedleZCalibrationItem.KnifeOffset;
            NeedleZCalibrationItem.NeedleTouchOffset = NeedleZCalibrationItem.CurrentNeedleTouchHeight - NeedleZCalibrationItem.NeedleTouchHeightBase;
        }

        private void SaveParameterInternal(bool showSuccessMessage)
        {
            NeedleCalibrationSetting?.Write();
            ParameterHelper?.RaiseValueAccept();
            if (showSuccessMessage)
            {
                LocalizedMessageBox.Show(MessageKey.CommonSaveSucceeded, MessageKey.TitleInfo);
            }
        }

        private void LoadParameters()
        {
            NeedleCalibrationSetting = _paramList?.GetParameter<NeedleCalibrationSetting>();
            NeedleZCalibrationItem = NeedleCalibrationSetting != null ? NeedleCalibrationSetting.NeedleZCalibrationItem : null;
            EnsureRecordCollection();
        }

        private void EnsureRecordCollection()
        {
            if (NeedleZCalibrationItem != null && NeedleZCalibrationItem.NeedleTouchRecords == null)
            {
                NeedleZCalibrationItem.NeedleTouchRecords = new ObservableCollection<NeedleTouchRecord>();
            }
        }

        private void InitCancelToken()
        {
            _cancellationTokenSource?.Cancel();
            _cancellationTokenSource?.Dispose();
            _cancellationTokenSource = new CancellationTokenSource();
        }

        private double GetAxisPosition(IAxis axis)
        {
            if (axis == null)
            {
                return 0;
            }

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

        private void ExecuteMotion(Action action)
        {
            CommonUti.RunOnUi(() =>
            {
                try
                {
                    action?.Invoke();
                }
                catch (Exception ex)
                {
                    LocalizedMessageBox.ShowFormat(MessageKey.ProcessFailedWithReason, MessageKey.TitleError, MessageBoxButton.OK, MessageBoxImage.Error, ex.Message);
                }
            });
        }
    }
}
