using MainShell.Common;
using MainShell.PageCalib.OriginCalib.Model;
using MaxwellFramework.Core.Attributes;
using MwFramework.Device;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Xml.Serialization;

namespace MainShell.Hardware.Acs
{
    public interface IAcsStartupInitializationService
    {
        bool IsInitialized { get; }

        void Initialize();

        void WriteOriginCalibrationHomeOffsets();
    }

    public sealed class AcsStartupInitializationService : IAcsStartupInitializationService
    {
        private const int CorrectionArrayLength = 100;
        private const int StartupPollingIntervalMilliseconds = 100;
        private const int StartupTimeoutMilliseconds = 10000;
        private const string InterferometerConfigRelativePath = @"Configuration\Card\Acs\Interferometer.xml";
        private const string WorkspaceInterferometerConfigRelativePath = @"Bin\Configuration\Card\Acs\Interferometer.xml";
        private readonly object _syncRoot = new object();
        private readonly IAcsAddressAccessService _acsAddressAccessService;
        private readonly AcsAddressRepository _acsAddressRepository;
        private readonly HardwareManager _hardwareManager;
        private bool _isInitialized;

        public AcsStartupInitializationService(
            IAcsAddressAccessService acsAddressAccessService,
            AcsAddressRepository acsAddressRepository,
            HardwareManager hardwareManager)
        {
            _acsAddressAccessService = acsAddressAccessService ?? throw new ArgumentNullException(nameof(acsAddressAccessService));
            _acsAddressRepository = acsAddressRepository ?? throw new ArgumentNullException(nameof(acsAddressRepository));
            _hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
        }

        public bool IsInitialized
        {
            get
            {
                return _isInitialized;
            }
        }

        public void Initialize()
        {
            lock (_syncRoot)
            {
                WriteInterferometerStartupParameters();
                WriteOriginCalibrationHomeOffsetsCore();
                _isInitialized = true;
            }
        }

        public void WriteOriginCalibrationHomeOffsets()
        {
            WriteOriginCalibrationHomeOffsetsCore();
        }

        private static void ValidateAxisIndex(int axisIndex, int axisCount)
        {
            if (axisIndex < 0 || axisIndex >= axisCount)
            {
                throw new ArgumentOutOfRangeException(nameof(axisIndex), axisIndex, "Axis index is outside the loaded ACS startup parameter range.");
            }
        }

        private void WriteInterferometerStartupParameters()
        {
            ObservableCollection<CorrectionItem> corrections = LoadInterferometerCorrections();
            AcsCommonAdress commonData = _acsAddressRepository.GetCommonData();
            AcsInterferometerCompensationAdress interferometerData = _acsAddressRepository.GetInterferometerCompensationData();
            AcsHomeSettingAdress homeSettingData = _acsAddressRepository.GetHomeSettingData();

            int axisCount = homeSettingData.PA_HomeMode.Length;
            double[] startPositions = new double[axisCount];
            double[] stepLengths = new double[axisCount];
            int[] correctionOpens = new int[axisCount];
            int[] homeModes = new int[axisCount];
            double[] homeVelocities = new double[axisCount];
            int[] homeOrders = new int[axisCount];

            foreach (CorrectionItem correctionItem in corrections)
            {
                if (correctionItem == null)
                {
                    continue;
                }

                ValidateAxisIndex(correctionItem.AxisIndex, axisCount);

                double[] correctionValues = BuildCorrectionArray(correctionItem);
                startPositions[correctionItem.AxisIndex] = correctionItem.StartPos;
                stepLengths[correctionItem.AxisIndex] = correctionItem.StepLength;
                correctionOpens[correctionItem.AxisIndex] = correctionItem.Enable ? 1 : 0;
                homeModes[correctionItem.AxisIndex] = correctionItem.HomeMode;
                homeVelocities[correctionItem.AxisIndex] = correctionItem.HomeSpeed;
                homeOrders[correctionItem.AxisIndex] = correctionItem.HomeOrder;

                AcsAddressDefinition correctionBufferAddress;
                if (TryGetCorrectionBufferAddress(correctionItem.AxisName, interferometerData, out correctionBufferAddress))
                {
                    _acsAddressAccessService.Write(correctionBufferAddress, correctionValues);
                }
            }

            _acsAddressAccessService.Write(interferometerData.PA_StartPos, startPositions);
            _acsAddressAccessService.Write(interferometerData.PA_Step_len, stepLengths);
            _acsAddressAccessService.Write(interferometerData.PA_CorrectionOpen, correctionOpens);
            _acsAddressAccessService.Write(homeSettingData.PA_HomeMode, homeModes);
            _acsAddressAccessService.Write(homeSettingData.PA_HomeVel, homeVelocities);
            _acsAddressAccessService.Write(homeSettingData.PA_HomeOrder, homeOrders);
            _acsAddressAccessService.Write(commonData.PA_ComSupMotionType, (int)ComSupMotionType.Interfere);

            WaitForStartupWriteResult(commonData);
        }
        
        private ObservableCollection<CorrectionItem> LoadInterferometerCorrections()
        {
            string configPath = ResolveInterferometerConfigPath();
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(ObservableCollection<CorrectionItem>));

            using (FileStream fileStream = File.OpenRead(configPath))
            {
                ObservableCollection<CorrectionItem> corrections = xmlSerializer.Deserialize(fileStream) as ObservableCollection<CorrectionItem>;
                if (corrections == null)
                {
                    throw new InvalidOperationException("The interferometer configuration could not be deserialized.");
                }

                return corrections;
            }
        }

        private static double[] BuildCorrectionArray(CorrectionItem correctionItem)
        {
            double[] correctionValues = new double[CorrectionArrayLength];
            if (correctionItem.CorrectionDatas == null)
            {
                return correctionValues;
            }

            int count = Math.Min(correctionItem.CorrectionDatas.Count, correctionValues.Length);
            for (int index = 0; index < count; index++)
            {
                CorrectionData correctionData = correctionItem.CorrectionDatas[index];
                if (correctionData == null)
                {
                    continue;
                }

                correctionValues[index] = correctionData.ScaleData;
            }

            return correctionValues;
        }

        private void WriteOriginCalibrationHomeOffsetsCore()
        {
            OriginCalibSetting setting = OriginCalibSetting.LoadOrCreate();
            AcsHomeOffsetAdress homeOffsetData = _acsAddressRepository.GetHomeOffsetData();
            double[] homeOffsets = new double[homeOffsetData.PA_HomeOffset.Length];

            WriteHomeOffset(homeOffsets, _hardwareManager.Axis_X1, GetCalibrationOffset(setting, OriginCalibModuleNames.GantryX1Y1, AxisName.Axis_PHS_X1));
            WriteHomeOffset(homeOffsets, _hardwareManager.Axis_Y1, GetCalibrationOffset(setting, OriginCalibModuleNames.GantryX1Y1, AxisName.Axis_PHS_Y1));
            WriteHomeOffset(homeOffsets, _hardwareManager.Axis_X2, GetCalibrationOffset(setting, OriginCalibModuleNames.GantryX2, AxisName.Axis_PHS_X2));
            WriteHomeOffset(homeOffsets, _hardwareManager.Axis_Stage_Y3, GetCalibrationOffset(setting, OriginCalibModuleNames.StageY2, AxisName.Axis_Stage_Y3));
            WriteHomeOffset(homeOffsets, _hardwareManager.Axis_WS_X3, GetCalibrationOffset(setting, OriginCalibModuleNames.WSX3, AxisName.Axis_WS_X3));

            _acsAddressAccessService.Write(homeOffsetData.PA_HomeOffset, homeOffsets);
        }

        private static double GetCalibrationOffset(OriginCalibSetting setting, string moduleName, string axisName)
        {
            if (setting == null)
            {
                throw new ArgumentNullException(nameof(setting));
            }

            OriginCalibModuleItem module = setting.Modules.Find(item =>
                item != null && string.Equals(item.ModuleName, moduleName, StringComparison.OrdinalIgnoreCase));
            if (module == null || module.CalibrationAxes == null)
            {
                return 0d;
            }

            CalibrationAxisItem calibrationAxis = module.CalibrationAxes.Find(item =>
                item != null && string.Equals(item.AxisName, axisName, StringComparison.OrdinalIgnoreCase));
            if (calibrationAxis == null)
            {
                return 0d;
            }

            return calibrationAxis.Offset;
        }

        private static void WriteHomeOffset(double[] homeOffsets, IAxis axis, double offset)
        {
            if (homeOffsets == null)
            {
                throw new ArgumentNullException(nameof(homeOffsets));
            }

            if (axis == null)
            {
                throw new InvalidOperationException("The target axis for origin calibration home offset was not found.");
            }

            int axisIndex = axis.AxisIndex;
            ValidateAxisIndex(axisIndex, homeOffsets.Length);
            homeOffsets[axisIndex] = offset;
        }

        private void WaitForStartupWriteResult(AcsCommonAdress commonData)
        {
            if (commonData == null)
            {
                throw new ArgumentNullException(nameof(commonData));
            }

            DateTime deadline = DateTime.Now.AddMilliseconds(StartupTimeoutMilliseconds);
            int result = 0;

            while (DateTime.Now <= deadline)
            {
                object rawResult = _acsAddressAccessService.Read(commonData.AP_ComSupMotionRes);
                if (rawResult != null)
                {
                    result = Convert.ToInt32(rawResult);
                    if (result == -1)
                    {
                        throw new InvalidOperationException("Writing interferometer startup parameters to ACS failed.");
                    }

                    if (result == 1)
                    {
                        return;
                    }
                }

                Thread.Sleep(StartupPollingIntervalMilliseconds);
            }

            throw new TimeoutException("Writing interferometer startup parameters to ACS timed out.");
        }

        private static string ResolveInterferometerConfigPath()
        {
            List<string> candidatePaths = new List<string>
            {
                Path.Combine(AppDomain.CurrentDomain.BaseDirectory, InterferometerConfigRelativePath),
                Path.Combine(Environment.CurrentDirectory, WorkspaceInterferometerConfigRelativePath),
                Path.Combine(Environment.CurrentDirectory, InterferometerConfigRelativePath)
            };

            foreach (string candidatePath in candidatePaths)
            {
                if (File.Exists(candidatePath))
                {
                    return candidatePath;
                }
            }

            throw new FileNotFoundException("The interferometer startup configuration file was not found.");
        }

        private static bool TryGetCorrectionBufferAddress(
            string axisName,
            AcsInterferometerCompensationAdress interferometerData,
            out AcsAddressDefinition correctionBufferAddress)
        {
            if (interferometerData == null)
            {
                throw new ArgumentNullException(nameof(interferometerData));
            }

            correctionBufferAddress = null;
            if (string.IsNullOrWhiteSpace(axisName))
            {
                return false;
            }

            string normalizedAxisName = axisName.Trim().ToUpperInvariant();
            switch (normalizedAxisName)
            {
                case "X1":
                case "X11":
                    correctionBufferAddress = interferometerData.PA_Correction_X1;
                    return true;
                case "Y1":
                case "Y11":
                    correctionBufferAddress = interferometerData.PA_Correction_Y1;
                    return true;
                case "X2":
                case "X21":
                    correctionBufferAddress = interferometerData.PA_Correction_X2;
                    return true;
                case "Y2":
                case "Y21":
                    correctionBufferAddress = interferometerData.PA_Correction_Y2;
                    return true;
                default:
                    return false;
            }
        }

        private static int ResolveInterfereMotionTypeValue()
        {
            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly assembly in assemblies)
            {
                Type[] types;
                try
                {
                    types = assembly.GetTypes();
                }
                catch (ReflectionTypeLoadException ex)
                {
                    types = ex.Types;
                }

                foreach (Type type in types)
                {
                    if (type == null || !type.IsEnum || !string.Equals(type.Name, "ComSupMotionType", StringComparison.Ordinal))
                    {
                        continue;
                    }

                    if (!Enum.IsDefined(type, "Interfere"))
                    {
                        continue;
                    }

                    object enumValue = Enum.Parse(type, "Interfere");
                    return Convert.ToInt32(enumValue);
                }
            }

            throw new InvalidOperationException("The ComSupMotionType.Interfere enumeration value could not be resolved.");
        }
    }
}
