using MainShell.EventArgsFolder;
using MainShell.Hardware;
using MainShell.Manual.Model;
using MainShell.DeviceMaintance.ViewModel.State;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MainShell.DeviceMaintance.ViewModel
{
    public class DeviceIoViewModel : OperateViewModelBase
    {
        private readonly IDeviceIoMonitorService _ioMonitorService;
        private readonly Dictionary<string, DeviceIoPointItemState> _pointStateMap = new Dictionary<string, DeviceIoPointItemState>(StringComparer.OrdinalIgnoreCase);

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

        public DeviceIoViewModel(IDeviceIoMonitorService ioMonitorService)
        {
            _ioMonitorService = ioMonitorService;
            PageState = BuildState();
            _ioMonitorService.IoChanged += OnIoChanged;
            _ioMonitorService.Start();
        }

        protected override void OnViewLoaded()
        {
            base.OnViewLoaded();
            RefreshFromCurrentPoints();
            InitializeLatestPointsAsync();
        }

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

        private DeviceIoPageState BuildState()
        {
            var state = new DeviceIoPageState();
            _pointStateMap.Clear();

            foreach (var moduleGroup in _ioMonitorService.Definitions.GroupBy(x => string.IsNullOrWhiteSpace(x.Module) ? "Default" : x.Module))
            {
                var moduleState = new DeviceIoModuleState
                {
                    Name = moduleGroup.Key
                };

                foreach (var definition in moduleGroup.OrderBy(x => x.StationNo).ThenBy(x => x.Id).ThenBy(x => x.LineNo))
                {
                    var item = new DeviceIoPointItemState
                    {
                        Id = definition.Id,
                        PointKey = definition.PointKey,
                        Module = definition.Module,
                        Name = definition.Name,
                        StationNo = definition.StationNo,
                        LineNo = definition.LineNo,
                        Description = definition.Description,
                        IsOutput = definition.Type == IoPointType.Output,
                        IsOn = definition.DefaultValue,
                        IsInverse = definition.IsInverse,
                        CanWrite = definition.Type == IoPointType.Output && definition.Enabled,
                    };

                    if (item.IsOutput)
                    {
                        var command = new ActionCommand(() => ToggleOutput(item), () => item.CanWrite);
                        item.ToggleCommand = command;
                    }

                    _pointStateMap[item.PointKey] = item;

                    if (item.IsOutput)
                    {
                        moduleState.OutputPoints.Add(item);
                    }
                    else
                    {
                        moduleState.InputPoints.Add(item);
                    }
                }

                state.Modules.Add(moduleState);
            }

            state.IsOnline = _ioMonitorService.IsOnline;
            return state;
        }

        private async void InitializeLatestPointsAsync()
        {
            _ioMonitorService.RequestRefresh();
            await Task.Delay(150);
            RefreshFromCurrentPoints();
        }

        private void RefreshFromCurrentPoints()
        {
            var points = _ioMonitorService.LatestPoints.Values.ToList();
            ApplyPoints(points);
        }

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

            ApplyPoints(args.ChangedPoints);
            PageState.LastRefreshTime = DateTime.Now;
        }

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

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

                hasPoint = true;
                DeviceIoPointItemState item;
                if (!_pointStateMap.TryGetValue(point.PointKey, out item))
                {
                    continue;
                }

                item.IsOn = point.Value;

                var outputPoint = point as DeviceOutputPointState;
                item.CanWrite = outputPoint != null && outputPoint.CanWrite;

                var command = item.ToggleCommand as ActionCommand;
                if (command != null)
                {
                    command.RaiseCanExecuteChanged();
                }
            }

            if (hasPoint)
            {
                PageState.LastRefreshTime = DateTime.Now;
            }

            PageState.IsOnline = hasPoint && _ioMonitorService.IsOnline;
        }

        private void ToggleOutput(DeviceIoPointItemState item)
        {
            DeviceIoPointState point;
            if (!_ioMonitorService.TryGetPoint(item.PointKey, out point))
            {
                UpdateWriteResult(item.Name + " 写入失败：未找到输出点。", false);
                return;
            }

            var outputPoint = point as DeviceOutputPointState;
            if (outputPoint == null || !outputPoint.CanWrite)
            {
                UpdateWriteResult(item.Name + " 写入失败：输出点不可写。", false);
                return;
            }

            var targetState = !item.IsOn;
            var result = outputPoint.TrySetOutput(targetState);
            UpdateWriteResult(item.Name + (result ? (targetState ? " 已切换为 ON。" : " 已切换为 OFF。") : " 写入失败。"), result);
        }

        private void UpdateWriteResult(string message, bool success)
        {
            PageState.LastWriteMessage = message;
            PageState.IsWriteSuccess = success;
            PageState.LastWriteTime = DateTime.Now;
        }
    }
}
