using MainShell.Common;
using MainShell.Filewritable;
using MainShell.Hardware;
using MainShell.Log;
using MainShell.Models;
using MainShell.Recipe.Models;
using MainShell.Recipe.Models.PID;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using StyletIoC;

namespace MainShell.Recipe.ViewModel
{
    public class ProcessRecipeViewModel : RecipeViewModelBase<ProcessRecipe>
    {
        private readonly HardwareManager _hardwareManager;
        private readonly PIDOperater _pidOperater;

        private static readonly string[] _fixedProfileNames = new string[]
        {
            "默认PID组"
        };

        private static readonly string[] _fixedCompensationAxisNames = new string[]
        {
            "PHS-X1",
            "PHS-Y1",
            "WS-X",
            "Stage-Y"
        };

        private ProcessRecipe _processRecipe;
        public ProcessRecipe ProcessRecipe
        {
            get { return _processRecipe; }
            set
            {
                if (SetAndNotify(ref _processRecipe, value))
                {
                    if (ProcessCompensationViewModel != null)
                    {
                        ProcessCompensationViewModel.ProcessRecipe = value;
                    }

                    EnsureProcessRecipeState();
                    SyncProfileSelection();
                    NotifyOfPropertyChange(() => AxisPIDParameters);
                    NotifyOfPropertyChange(() => AxisOptions);
                    NotifyOfPropertyChange(() => SelectedAxes);
                    NotifyOfPropertyChange(() => SelectedFilteringParameters);
                }
            }
        }

        protected override ProcessRecipe CurrentRecipe
        {
            get { return ProcessRecipe; }
            set { ProcessRecipe = value; }
        }

        protected override string RecipeFolderPath => Paths.ProcessRecipe;
        protected override string EmptyRecipeDisplayName => "当前工艺配方";

        protected override string GetActiveRecipeName()
        {
            return RecipeManager.CurrentProcessRecipe != null ? RecipeManager.CurrentProcessRecipe.RecipeName : null;
        }

        public ObservableCollection<PIDProfile> AxisPIDParameters => ProcessRecipe != null ? ProcessRecipe.AxisPIDParameters : null;

        private PIDProfile _selectedPIDProfile;
        public PIDProfile SelectedPIDProfile
        {
            get { return _selectedPIDProfile; }
            set
            {
                if (SetAndNotify(ref _selectedPIDProfile, value))
                {
                    EnsureProfileState(_selectedPIDProfile);
                    NotifyOfPropertyChange(() => SelectedAxes);
                    NotifyOfPropertyChange(() => SelectedFilteringParameters);
                    SyncFilteringSelection();
                }
            }
        }

        public ObservableCollection<AxisPIDParameter> SelectedAxes => SelectedPIDProfile != null ? SelectedPIDProfile.Axes : null;

        public ObservableCollection<AxisFilteringParameter> SelectedFilteringParameters => SelectedPIDProfile != null ? SelectedPIDProfile.FilteringParameters : null;

        private ObservableCollection<PidAxisOption> _axisOptions = new ObservableCollection<PidAxisOption>();
        public ObservableCollection<PidAxisOption> AxisOptions
        {
            get { return _axisOptions; }
            set { SetAndNotify(ref _axisOptions, value); }
        }

        private AxisPIDParameter _selectedAxisParameter;
        public AxisPIDParameter SelectedAxisParameter
        {
            get { return _selectedAxisParameter; }
            set { SetAndNotify(ref _selectedAxisParameter, value); }
        }

        private AxisFilteringParameter _selectedFilteringParameter;
        public AxisFilteringParameter SelectedFilteringParameter
        {
            get { return _selectedFilteringParameter; }
            set { SetAndNotify(ref _selectedFilteringParameter, value); }
        }

        private AxisFilteringParameter _selectedNotchFilteringParameter;
        public AxisFilteringParameter SelectedNotchFilteringParameter
        {
            get { return _selectedNotchFilteringParameter; }
            set
            {
                if (SetAndNotify(ref _selectedNotchFilteringParameter, value) && value != null)
                {
                    SelectedFilteringParameter = value;
                }
            }
        }

        private AxisFilteringParameter _selectedLowPassFilteringParameter;
        public AxisFilteringParameter SelectedLowPassFilteringParameter
        {
            get { return _selectedLowPassFilteringParameter; }
            set
            {
                if (SetAndNotify(ref _selectedLowPassFilteringParameter, value) && value != null)
                {
                    SelectedFilteringParameter = value;
                }
            }
        }

        private int _selectedParameterTabIndex;
        public int SelectedParameterTabIndex
        {
            get { return _selectedParameterTabIndex; }
            set { SetAndNotify(ref _selectedParameterTabIndex, value); }
        }

        private ProcessCompensationViewModel _processCompensationViewModel;
        [Inject]
        public ProcessCompensationViewModel ProcessCompensationViewModel
        {
            get { return _processCompensationViewModel; }
            set
            {
                if (SetAndNotify(ref _processCompensationViewModel, value) && value != null)
                {
                    value.ProcessRecipe = ProcessRecipe;
                }
            }
        }

        public ProcessRecipeViewModel(RecipeWrapManager wrapManager, RecipeManager recipeManager, HardwareManager hardwareManager, PIDOperater pidOperater)
            : base(recipeManager)
        {
            _hardwareManager = hardwareManager;
            _pidOperater = pidOperater;
            RecipeWraps = wrapManager.ProcessRecipeWraps;
            RegisterButtonGroupEvents();
            AxisOptions = BuildAxisOptions();
            PidAxisCatalog.SetAxisOptions(AxisOptions);
        }

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

            AxisOptions = BuildAxisOptions();
            PidAxisCatalog.SetAxisOptions(AxisOptions);
            InitializeSelection(RecipeManager.CurrentProcessRecipe != null ? RecipeManager.CurrentProcessRecipe.RecipeName : null);
        }

        protected override void SwitchActiveRecipe(string recipeName)
        {
            RecipeManager.SwitchProcessRecipe(recipeName);
        }

        protected override void ClearActiveRecipe()
        {
            RecipeManager.ClearProcessRecipe();
        }

        protected override void OnRecipeLoaded(ProcessRecipe recipe, RecipeWrap recipeWrap)
        {
            EnsureProcessRecipeState();
            SyncProfileSelection();
        }

        public override void OnCopyRecipe(object sender, EventArgs eventArgs)
        {
            base.OnCopyRecipe(sender, eventArgs);
        }

        public override void OnImportRecipe(object sender, EventArgs eventArgs)
        {
        }

        public override void OnExportRecipe(object sender, EventArgs eventArgs)
        {
        }

        public override void OnReNameRecipe(object sender, EventArgs eventArgs)
        {
            RecipeRenameEventArgs args = eventArgs as RecipeRenameEventArgs;
            if (args != null)
            {
                TryRenameRecipe(args);
            }
        }

        public void AddAxis()
        {
            if (SelectedPIDProfile == null)
            {
                MwMessageBox.Show("请先选择一个 PID 配置组");
                return;
            }

            EnsureProfileState(SelectedPIDProfile);

            PidAxisOption axisOption = GetNextAxisOption(SelectedPIDProfile.Axes.Select(item => item.AxisIndex));
            AxisPIDParameter axis = new AxisPIDParameter();
            if (axisOption != null)
            {
                axis.ModuleName = axisOption.AxisName;
            }
            else
            {
                axis.ModuleName = string.Format("Axis{0}", SelectedPIDProfile.Axes.Count + 1);
                axis.AxisIndex = SelectedPIDProfile.Axes.Count;
            }

            SelectedPIDProfile.Axes.Add(axis);
            SelectedAxisParameter = axis;
            NotifyOfPropertyChange(() => SelectedAxes);
            NotifyOfPropertyChange(() => HasUnsavedChanges);
        }

        public void DeleteAxis()
        {
            if (SelectedPIDProfile == null || SelectedAxisParameter == null)
            {
                return;
            }

            EnsureProfileState(SelectedPIDProfile);

            int index = SelectedPIDProfile.Axes.IndexOf(SelectedAxisParameter);
            SelectedPIDProfile.Axes.Remove(SelectedAxisParameter);
            SelectedAxisParameter = SelectedPIDProfile.Axes.Count == 0
                ? null
                : SelectedPIDProfile.Axes[Math.Min(index, SelectedPIDProfile.Axes.Count - 1)];
            NotifyOfPropertyChange(() => SelectedAxes);
            NotifyOfPropertyChange(() => HasUnsavedChanges);
        }

        public void AddFilteringParameter()
        {
            if (SelectedPIDProfile == null)
            {
                MwMessageBox.Show("请先选择一个 PID 配置组");
                return;
            }

            EnsureProfileState(SelectedPIDProfile);

            PidAxisOption axisOption = GetNextAxisOption(SelectedPIDProfile.FilteringParameters.Select(item => item.AxisIndex));
            AxisFilteringParameter filteringParameter = new AxisFilteringParameter
            {
                AxisIndex = axisOption != null ? axisOption.AxisIndex : GetDefaultAxisIndex()
            };

            SelectedPIDProfile.FilteringParameters.Add(filteringParameter);
            SelectedFilteringParameter = filteringParameter;
            NotifyOfPropertyChange(() => SelectedFilteringParameters);
            NotifyOfPropertyChange(() => HasUnsavedChanges);
        }

        public void ReadParameters()
        {
            if (SelectedPIDProfile == null)
            {
                LocalizedMessageBox.Show(MessageKey.PidNoProfileSelected, MessageKey.TitleWarning);
                return;
            }

            try
            {
                if (SelectedParameterTabIndex == 1)
                {
                    ReadFilteringParameters();
                }
                else
                {
                    ReadPidAxisParameters();
                }

                NotifyOfPropertyChange(() => SelectedAxes);
                NotifyOfPropertyChange(() => SelectedFilteringParameters);
                NotifyOfPropertyChange(() => HasUnsavedChanges);
                LocalizedMessageBox.Show(MessageKey.PidReadSucceeded, MessageKey.TitleInfo);
            }
            catch (Exception exception)
            {
                $"PID参数读取失败: {exception}".LogSysError();
                LocalizedMessageBox.Show(MessageKey.PidReadFailed, MessageKey.TitleError);
            }
        }

        public void WriteParameters()
        {
            if (SelectedPIDProfile == null)
            {
                LocalizedMessageBox.Show(MessageKey.PidNoProfileSelected, MessageKey.TitleWarning);
                return;
            }

            try
            {
                if (SelectedParameterTabIndex == 1)
                {
                    WriteFilteringParameters();
                }
                else
                {
                    WritePidAxisParameters();
                }

                LocalizedMessageBox.Show(MessageKey.PidWriteSucceeded, MessageKey.TitleInfo);
            }
            catch (Exception exception)
            {
                $"PID参数写入失败: {exception}".LogSysError();
                LocalizedMessageBox.Show(MessageKey.PidWriteFailed, MessageKey.TitleError);
            }
        }

        public void DeleteFilteringParameter()
        {
            if (SelectedPIDProfile == null || SelectedFilteringParameter == null)
            {
                return;
            }

            EnsureProfileState(SelectedPIDProfile);

            int index = SelectedPIDProfile.FilteringParameters.IndexOf(SelectedFilteringParameter);
            SelectedPIDProfile.FilteringParameters.Remove(SelectedFilteringParameter);
            SelectedFilteringParameter = SelectedPIDProfile.FilteringParameters.Count == 0
                ? null
                : SelectedPIDProfile.FilteringParameters[Math.Min(index, SelectedPIDProfile.FilteringParameters.Count - 1)];
            NotifyOfPropertyChange(() => SelectedFilteringParameters);
            NotifyOfPropertyChange(() => HasUnsavedChanges);
        }

        private void EnsureProcessRecipeState()
        {
            if (ProcessRecipe == null)
            {
                return;
            }

            if (ProcessRecipe.AxisPIDParameters == null)
            {
                ProcessRecipe.AxisPIDParameters = new ObservableCollection<PIDProfile>();
            }

            if (ProcessRecipe.ProcessCompensationParameters == null)
            {
                ProcessRecipe.ProcessCompensationParameters = new ObservableCollection<ProcessAxisCompensationParameter>();
            }

            EnsureFixedProfiles();
            EnsureFixedCompensationParameters();

            foreach (PIDProfile profile in ProcessRecipe.AxisPIDParameters)
            {
                EnsureProfileState(profile);
            }
        }

        private void SyncProfileSelection()
        {
            if (AxisPIDParameters == null || AxisPIDParameters.Count == 0)
            {
                SelectedPIDProfile = null;
                SelectedAxisParameter = null;
                SelectedFilteringParameter = null;
                return;
            }

            if (SelectedPIDProfile == null || !AxisPIDParameters.Contains(SelectedPIDProfile))
            {
                SelectedPIDProfile = AxisPIDParameters[0];
            }

            EnsureProfileState(SelectedPIDProfile);

            if (SelectedAxes == null || SelectedAxes.Count == 0)
            {
                SelectedAxisParameter = null;
            }
            else if (SelectedAxisParameter == null || !SelectedAxes.Contains(SelectedAxisParameter))
            {
                SelectedAxisParameter = SelectedAxes[0];
            }

            SyncFilteringSelection();
        }

        private void EnsureProfileState(PIDProfile profile)
        {
            if (profile == null)
            {
                return;
            }

            profile.EnsureAxes();

            if (profile.FilteringParameters == null)
            {
                profile.FilteringParameters = new ObservableCollection<AxisFilteringParameter>();
            }

            if (profile.FilteringParameters.Count == 0)
            {
                AxisFilteringParameter filteringParameter = new AxisFilteringParameter();
                filteringParameter.AxisIndex = GetDefaultAxisIndex();
                profile.FilteringParameters.Add(filteringParameter);
            }
        }

        private void SyncFilteringSelection()
        {
            if (SelectedFilteringParameters == null || SelectedFilteringParameters.Count == 0)
            {
                SelectedFilteringParameter = null;
                SelectedNotchFilteringParameter = null;
                SelectedLowPassFilteringParameter = null;
                return;
            }

            if (SelectedFilteringParameter == null || !SelectedFilteringParameters.Contains(SelectedFilteringParameter))
            {
                SelectedFilteringParameter = SelectedFilteringParameters[0];
            }

            if (SelectedNotchFilteringParameter == null || !SelectedFilteringParameters.Contains(SelectedNotchFilteringParameter))
            {
                SelectedNotchFilteringParameter = SelectedFilteringParameter;
            }

            if (SelectedLowPassFilteringParameter == null || !SelectedFilteringParameters.Contains(SelectedLowPassFilteringParameter))
            {
                SelectedLowPassFilteringParameter = SelectedFilteringParameter;
            }
        }

        private void EnsureFixedProfiles()
        {
            foreach (string profileName in _fixedProfileNames)
            {
                PIDProfile profile = AxisPIDParameters.FirstOrDefault(item => string.Equals(item.Name, profileName, StringComparison.OrdinalIgnoreCase));
                if (profile == null)
                {
                    profile = new PIDProfile(profileName);
                    profile.Description = "程序初始化固定配置组";
                    AxisPIDParameters.Add(profile);
                }
            }
        }

        private void EnsureFixedCompensationParameters()
        {
            foreach (string axisName in _fixedCompensationAxisNames)
            {
                ProcessAxisCompensationParameter parameter = ProcessRecipe.ProcessCompensationParameters
                    .FirstOrDefault(item => string.Equals(item.AxisName, axisName, StringComparison.OrdinalIgnoreCase));

                if (parameter == null)
                {
                    parameter = new ProcessAxisCompensationParameter();
                    parameter.AxisName = axisName;
                    parameter.CompensationValue = 0d;
                    ProcessRecipe.ProcessCompensationParameters.Add(parameter);
                }
            }
        }

        private void ReadPidAxisParameters()
        {
            if (SelectedAxes == null)
            {
                return;
            }

            foreach (AxisPIDParameter axisParameter in SelectedAxes)
            {
                AxisPIDParameter deviceParameter = _pidOperater.ReadPIDParametersFromDevice(axisParameter.ModuleName);
                CopyAxisParameterValues(deviceParameter, axisParameter);
            }
        }

        private void WritePidAxisParameters()
        {
            if (SelectedAxes == null)
            {
                return;
            }

            foreach (AxisPIDParameter axisParameter in SelectedAxes)
            {
                _pidOperater.SendPIDParameters(axisParameter);
            }
        }

        private void ReadFilteringParameters()
        {
            if (SelectedFilteringParameter == null)
            {
                LocalizedMessageBox.Show(MessageKey.PidNoFilteringParameterSelected, MessageKey.TitleWarning);
                return;
            }

            AxisFilteringParameter deviceParameter = _pidOperater.ReadPIDFilteringParametersFromDevice();
            CopyFilteringParameterValues(deviceParameter, SelectedFilteringParameter);
        }

        private void WriteFilteringParameters()
        {
            if (SelectedFilteringParameter == null)
            {
                LocalizedMessageBox.Show(MessageKey.PidNoFilteringParameterSelected, MessageKey.TitleWarning);
                return;
            }

            _pidOperater.SendPIDFilteringParameters(SelectedFilteringParameter);
        }

        private void CopyAxisParameterValues(AxisPIDParameter source, AxisPIDParameter target)
        {
            if (source == null || target == null)
            {
                return;
            }

            target.PA_SLVKP.Value = source.PA_SLVKP.Value;
            target.PA_SLVKI.Value = source.PA_SLVKI.Value;
            target.PA_SLVKPSF.Value = source.PA_SLVKPSF.Value;
            target.PA_SLVKPIF.Value = source.PA_SLVKPIF.Value;
            target.PA_SLVKISF.Value = source.PA_SLVKISF.Value;
            target.PA_SLVKIIF.Value = source.PA_SLVKIIF.Value;
            target.PA_SLPKP.Value = source.PA_SLPKP.Value;
            target.PA_SLPKPIF.Value = source.PA_SLPKPIF.Value;
            target.PA_SLPKPSF.Value = source.PA_SLPKPSF.Value;
            target.PA_SLAFF.Value = source.PA_SLAFF.Value;
            target.PA_SLJFF.Value = source.PA_SLJFF.Value;
        }

        private void CopyFilteringParameterValues(AxisFilteringParameter source, AxisFilteringParameter target)
        {
            if (source == null || target == null)
            {
                return;
            }

            target.IsUseNotch.Value = source.IsUseNotch.Value;
            target.NotchFrequency.Value = source.NotchFrequency.Value;
            target.NotchWidth.Value = source.NotchWidth.Value;
            target.NotchFalloff.Value = source.NotchFalloff.Value;
            target.IsUseFiltering.Value = source.IsUseFiltering.Value;
            target.FilteringFrequency.Value = source.FilteringFrequency.Value;
            target.FilteringDamping.Value = source.FilteringDamping.Value;
        }

        private PidAxisOption GetNextAxisOption(IEnumerable<int> usedAxisIndices)
        {
            if (AxisOptions == null || AxisOptions.Count == 0)
            {
                return null;
            }

            HashSet<int> usedAxisSet = new HashSet<int>();
            if (usedAxisIndices != null)
            {
                foreach (int axisIndex in usedAxisIndices)
                {
                    usedAxisSet.Add(axisIndex);
                }
            }

            foreach (PidAxisOption axisOption in AxisOptions)
            {
                if (!usedAxisSet.Contains(axisOption.AxisIndex))
                {
                    return axisOption;
                }
            }

            return AxisOptions[0];
        }

        private int GetDefaultAxisIndex()
        {
            if (AxisOptions == null || AxisOptions.Count == 0)
            {
                return 0;
            }

            return AxisOptions[0].AxisIndex;
        }

        private ObservableCollection<PidAxisOption> BuildAxisOptions()
        {
            ObservableCollection<PidAxisOption> axisOptions = new ObservableCollection<PidAxisOption>();

            if (_hardwareManager != null && _hardwareManager.AxesDic != null && _hardwareManager.AxesDic.Count > 0)
            {
                int order = 0;
                foreach (string axisName in _hardwareManager.AxesDic.Keys.OrderBy(item => item))
                {
                    axisOptions.Add(new PidAxisOption
                    {
                        AxisName = axisName,
                        AxisIndex = ExtractAxisIndex(axisName, order)
                    });
                    order++;
                }
            }

            if (axisOptions.Count > 0)
            {
                return axisOptions;
            }

            FieldInfo[] fields = typeof(AxisName).GetFields(BindingFlags.Public | BindingFlags.Static);
            int fallbackOrder = 0;
            foreach (FieldInfo fieldInfo in fields.OrderBy(item => item.Name))
            {
                object value = fieldInfo.GetValue(null);
                string axisName = value as string;
                if (string.IsNullOrWhiteSpace(axisName))
                {
                    continue;
                }

                axisOptions.Add(new PidAxisOption
                {
                    AxisName = axisName,
                    AxisIndex = ExtractAxisIndex(axisName, fallbackOrder)
                });
                fallbackOrder++;
            }

            return axisOptions;
        }

        private int ExtractAxisIndex(string axisName, int fallbackValue)
        {
            if (string.IsNullOrWhiteSpace(axisName))
            {
                return fallbackValue;
            }

            string digits = new string(axisName.Reverse().TakeWhile(char.IsDigit).Reverse().ToArray());
            int axisIndex;
            if (int.TryParse(digits, out axisIndex))
            {
                return axisIndex;
            }

            return fallbackValue;
        }
    }
}
