using MainShell.DeviceMaintance.Model;
using MainShell.DeviceMaintance.ViewModel.State;
using MainShell.Hardware;
using MainShell.Manual.ViewModel.State;
using MaxwellFramework.Core.Interfaces;
using Stylet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MainShell.DeviceMaintance.ViewModel
{
    public class CylinderViewModel : Screen, IPage
    {
        private readonly IDeviceIoMonitorService _ioMonitorService;
        private readonly IDeviceCylinderService _cylinderService;
        private readonly Dictionary<string, List<CylinderSignalState>> _signalMap = new Dictionary<string, List<CylinderSignalState>>(StringComparer.OrdinalIgnoreCase);
        private readonly Dictionary<string, DeviceIoPointDefinition> _ioDefinitionsByName;

        private CylinderPageState _pageState;
        public CylinderPageState PageState
        {
            get { return _pageState; }
            set { SetAndNotify(ref _pageState, value); }
        }

        public CylinderViewModel(IDeviceIoMonitorService ioMonitorService, IDeviceCylinderService cylinderService)
        {
            _ioMonitorService = ioMonitorService;
            _cylinderService = cylinderService;
            _ioDefinitionsByName = _ioMonitorService.Definitions.ToDictionary(x => x.Name, x => x, StringComparer.OrdinalIgnoreCase);
            PageState = BuildState();
            _ioMonitorService.IoChanged += OnIoChanged;
            _ioMonitorService.Start();
        }

        protected override void OnViewLoaded()
        {
            base.OnViewLoaded();
            ApplyIoPoints(_ioMonitorService.LatestPoints.Values);
            InitializePointsAsync();
        }

        protected override void OnDeactivate()
        {
            _ioMonitorService.IoChanged -= OnIoChanged;
            base.OnDeactivate();
        }

        private CylinderPageState BuildState()
        {
            var state = new CylinderPageState();
            _signalMap.Clear();

            foreach (var definition in _cylinderService.Definitions)
            {
                var item = new CylinderItemState
                {
                    Name = definition.Name,
                    Description = definition.Description,
                    Module = definition.Module,
                    ControlTypeText = GetControlTypeText(definition.ControlType)
                };

                item.ExtendCommand = new ActionCommand(() => ExecuteCylinderAction(definition, item, true), () => !item.IsBusy);
                item.RetractCommand = new ActionCommand(() => ExecuteCylinderAction(definition, item, false), () => !item.IsBusy);

                for (var i = 0; i < definition.ExtendOutputPoints.Count; i++)
                {
                    AddOutputSignal(item, definition.ExtendOutputPoints[i], definition.ExtendOutputPoints.Count > 1 ? string.Format("{0}-伸出输出{1}", definition.Name, i + 1) : definition.Name + "-伸出输出");
                }

                for (var i = 0; i < definition.RetractOutputPoints.Count; i++)
                {
                    AddOutputSignal(item, definition.RetractOutputPoints[i], definition.RetractOutputPoints.Count > 1 ? string.Format("{0}-缩回输出{1}", definition.Name, i + 1) : definition.Name + "-缩回输出");
                }

                foreach (var pointReference in definition.ExtendedFeedbackPoints)
                {
                    AddFeedbackSignal(item, pointReference, definition.Name + "-伸出到位");
                }

                foreach (var pointReference in definition.RetractedFeedbackPoints)
                {
                    AddFeedbackSignal(item, pointReference, definition.Name + "-缩回到位");
                }

                state.Cylinders.Add(item);
            }

            return state;
        }

        private void AddOutputSignal(CylinderItemState item, string pointReference, string fallbackName)
        {
            var definition = ResolveDefinition(pointReference);
            var signal = new CylinderSignalState
            {
                PointReference = pointReference,
                PointKey = definition?.PointKey,
                Name = definition != null ? definition.Name : fallbackName,
                LineNo = definition != null ? definition.LineNo : string.Empty,
                IsOutput = true
            };
            item.OutputSignals.Add(signal);
            MapSignal(signal, pointReference);
        }

        private void AddFeedbackSignal(CylinderItemState item, string pointReference, string fallbackName)
        {
            var definition = ResolveDefinition(pointReference);
            var signal = new CylinderSignalState
            {
                PointReference = pointReference,
                PointKey = definition?.PointKey,
                Name = definition != null ? definition.Name : fallbackName,
                LineNo = definition != null ? definition.LineNo : string.Empty,
                IsOutput = false
            };
            item.FeedbackSignals.Add(signal);
            MapSignal(signal, pointReference);
        }

        private DeviceIoPointDefinition ResolveDefinition(string pointReference)
        {
            if (string.IsNullOrWhiteSpace(pointReference))
            {
                return null;
            }

            DeviceIoPointDefinition definition;
            if (_ioDefinitionsByName.TryGetValue(pointReference, out definition))
            {
                return definition;
            }

            int id;
            if (!int.TryParse(pointReference, out id))
            {
                return null;
            }

            var matches = _ioMonitorService.Definitions.Where(x => x.Id == id).Take(2).ToList();
            return matches.Count == 1 ? matches[0] : null;
        }

        private void MapSignal(CylinderSignalState signal, string pointReference)
        {
            var key = !string.IsNullOrWhiteSpace(signal.PointKey) ? signal.PointKey : pointReference;
            if (string.IsNullOrWhiteSpace(key))
            {
                return;
            }

            List<CylinderSignalState> signals;
            if (!_signalMap.TryGetValue(key, out signals))
            {
                signals = new List<CylinderSignalState>();
                _signalMap[key] = signals;
            }

            signals.Add(signal);
        }

        private void OnIoChanged(object sender, EventArgs e)
        {
            var args = e as MainShell.EventArgsFolder.DeviceIoChangedEventArgs;
            if (args == null)
            {
                return;
            }

            ApplyIoPoints(args.ChangedPoints);
        }

        private async void InitializePointsAsync()
        {
            _ioMonitorService.RequestRefresh();
            await Task.Delay(150);
            ApplyIoPoints(_ioMonitorService.LatestPoints.Values);
        }

        private void ApplyIoPoints(IEnumerable<DeviceIoPointState> points)
        {
            if (points == null)
            {
                return;
            }

            foreach (var point in points)
            {
                if (point == null)
                {
                    continue;
                }

                List<CylinderSignalState> signals;
                if (!_signalMap.TryGetValue(point.PointKey, out signals))
                {
                    continue;
                }

                foreach (var signal in signals)
                {
                    signal.IsOn = point.Value;
                }
            }
        }

        private void ExecuteCylinderAction(CylinderDefinition definition, CylinderItemState item, bool extend)
        {
            item.IsBusy = true;
            RaiseCommands(item);

            string failureReason;
            var result = extend ? _cylinderService.TryExtend(definition.Name, out failureReason) : _cylinderService.TryRetract(definition.Name, out failureReason);

            if (result)
            {
                SimulateCylinderFeedback(definition, extend);
                PageState.LastActionMessage = definition.Name + (extend ? " 执行伸出成功。" : " 执行缩回成功。");
            }
            else
            {
                PageState.LastActionMessage = string.IsNullOrWhiteSpace(failureReason)
                    ? definition.Name + (extend ? " 伸出失败。" : " 缩回失败。")
                    : failureReason;
            }

            PageState.LastActionTime = DateTime.Now;
            item.IsBusy = false;
            RaiseCommands(item);
        }

        private void SimulateCylinderFeedback(CylinderDefinition definition, bool extend)
        {
            foreach (var output in definition.ExtendOutputPoints.SelectMany(GetSignals))
            {
                output.IsOn = extend;
            }

            foreach (var output in definition.RetractOutputPoints.SelectMany(GetSignals))
            {
                output.IsOn = !extend;
            }

            foreach (var feedback in definition.ExtendedFeedbackPoints.SelectMany(GetSignals))
            {
                feedback.IsOn = extend;
            }

            foreach (var feedback in definition.RetractedFeedbackPoints.SelectMany(GetSignals))
            {
                feedback.IsOn = !extend;
            }
        }

        private IEnumerable<CylinderSignalState> GetSignals(string pointReference)
        {
            var definition = ResolveDefinition(pointReference);
            var key = definition != null ? definition.PointKey : pointReference;
            if (string.IsNullOrWhiteSpace(key))
            {
                return Enumerable.Empty<CylinderSignalState>();
            }

            List<CylinderSignalState> signals;
            if (_signalMap.TryGetValue(key, out signals))
            {
                return signals;
            }

            return Enumerable.Empty<CylinderSignalState>();
        }

        private static void RaiseCommands(CylinderItemState item)
        {
            var extend = item.ExtendCommand as ActionCommand;
            if (extend != null)
            {
                extend.RaiseCanExecuteChanged();
            }

            var retract = item.RetractCommand as ActionCommand;
            if (retract != null)
            {
                retract.RaiseCanExecuteChanged();
            }
        }

        private static string GetControlTypeText(CylinderControlType controlType)
        {
            switch (controlType)
            {
                case CylinderControlType.DualOutput:
                    return "双输出控制";
                case CylinderControlType.MultiOutput:
                    return "多输出单控";
                default:
                    return "单输出控制";
            }
        }
    }
}
