using MaxwellFramework.Core.Attributes;
using MainShell.Hardware;
using MwFramework.Device;
using System;

namespace MainShell.Hardware.Acs
{
    public interface IAcsAddressAccessService
    {
        bool IsReady { get; }

        object Read(AcsAddressDefinition addressDefinition);

        object Read(AcsAddressDefinition addressDefinition, int index1, int index2);

        object ReadRange(AcsAddressDefinition addressDefinition, int from1, int to1, int from2, int to2);

        T Read<T>(AcsAddressDefinition addressDefinition);

        bool Write(AcsAddressDefinition addressDefinition, object value);

        bool Write(AcsAddressDefinition addressDefinition, object value, int index1, int index2);

        bool WriteRange(AcsAddressDefinition addressDefinition, object value, int from1, int to1, int from2, int to2);
    }

    [Singleton]
    public sealed class AcsAddressAccessService : IAcsAddressAccessService
    {
        private const int DefaultBufferId = -1;
        private readonly HardwareManager _hardwareManager;

        public AcsAddressAccessService(HardwareManager hardwareManager)
        {
            _hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
        }

        public bool IsReady
        {
            get
            {
                return _hardwareManager.AcsCard != null
                    && _hardwareManager.AcsCard.Controller != null
                    && _hardwareManager.AcsCard.IsOpen;
            }
        }

        public object Read(AcsAddressDefinition addressDefinition)
        {
            AcsAddressDefinition validatedAddress = ValidateAddress(addressDefinition);
            IMotionController controller = GetController();
            string variableName = ResolveVariableName(validatedAddress);
            bool result= controller.ReadVariableBuffer(DefaultBufferId, variableName,out object val);
            if (!result)
            {
                throw CreateReadWriteException("Read", validatedAddress);
            }
            return val;
        }

        public object Read(AcsAddressDefinition addressDefinition, int index1, int index2)
        {
            AcsAddressDefinition validatedAddress = ValidateAddress(addressDefinition);
            ValidateIndex(index1, nameof(index1));
            ValidateIndex(index2, nameof(index2));
            IMotionController controller = GetController();
            string variableName = ResolveVariableName(validatedAddress);
            bool result = controller.ReadVariableBuffer(DefaultBufferId, variableName, out object val, index1, index2);
            if (!result)
            {
                throw CreateReadWriteException("Read", validatedAddress);
            }
            return val;
        }

        public object ReadRange(AcsAddressDefinition addressDefinition, int from1, int to1, int from2, int to2)
        {
            AcsAddressDefinition validatedAddress = ValidateAddress(addressDefinition);
            ValidateRange(from1, to1, nameof(from1), nameof(to1));
            ValidateRange(from2, to2, nameof(from2), nameof(to2));

            IMotionController controller = GetController();
            string variableName = ResolveVariableName(validatedAddress);
            object value;
            bool result = controller.ReadVariableBuffer(DefaultBufferId, variableName, out value, from1, to1, from2, to2);
            if (!result)
            {
                throw CreateReadWriteException("Read", validatedAddress);
            }

            return value;
        }

        public T Read<T>(AcsAddressDefinition addressDefinition)
        {
            AcsAddressDefinition validatedAddress = ValidateAddress(addressDefinition);
            ValidateTypeCompatibility(validatedAddress, typeof(T));
            object value = Read(validatedAddress);
            return ConvertValue<T>(value, validatedAddress);
        }

        public bool Write(AcsAddressDefinition addressDefinition, object value)
        {
            AcsAddressDefinition validatedAddress = ValidateAddress(addressDefinition);
            ValidateWriteValue(validatedAddress, value);
            IMotionController controller = GetController();
            string variableName = ResolveVariableName(validatedAddress);
            bool result = controller.WriteVariableBuffer(DefaultBufferId, variableName, value);
            if (!result)
            {
                throw CreateReadWriteException("Write", validatedAddress);
            }

            return true;
        }

        public bool Write(AcsAddressDefinition addressDefinition, object value, int index1, int index2)
        {
            AcsAddressDefinition validatedAddress = ValidateAddress(addressDefinition);
            ValidateWriteValue(validatedAddress, value);
            ValidateIndex(index1, nameof(index1));
            ValidateIndex(index2, nameof(index2));
            IMotionController controller = GetController();
            string variableName = ResolveVariableName(validatedAddress);
            bool result = controller.WriteVariableBuffer(DefaultBufferId, variableName, value, index1, index2);
            if (!result)
            {
                throw CreateReadWriteException("Write", validatedAddress);
            }

            return true;
        }

        public bool WriteRange(AcsAddressDefinition addressDefinition, object value, int from1, int to1, int from2, int to2)
        {
            AcsAddressDefinition validatedAddress = ValidateAddress(addressDefinition);
            ValidateWriteValue(validatedAddress, value);
            ValidateRange(from1, to1, nameof(from1), nameof(to1));
            ValidateRange(from2, to2, nameof(from2), nameof(to2));
            IMotionController controller = GetController();
            string variableName = ResolveVariableName(validatedAddress);
            bool result = controller.WriteVariableBuffer(DefaultBufferId, variableName, value, from1, to1, from2, to2);
            if (!result)
            {
                throw CreateReadWriteException("Write", validatedAddress);
            }

            return true;
        }

        private IMotionController GetController()
        {
            if (!IsReady)
            {
                throw new InvalidOperationException("ACS controller is not ready. Ensure AcsCard is initialized and opened.");
            }

            return _hardwareManager.AcsCard.Controller;
        }

        private static AcsAddressDefinition ValidateAddress(AcsAddressDefinition addressDefinition)
        {
            if (addressDefinition == null)
            {
                throw new ArgumentNullException(nameof(addressDefinition));
            }

            if (!addressDefinition.HasNumericAddress && !addressDefinition.HasSymbolicAddress)
            {
                throw new ArgumentException("ACS address definition does not contain a usable address.", nameof(addressDefinition));
            }

            return addressDefinition;
        }

        private static string ResolveVariableName(AcsAddressDefinition addressDefinition)
        {
            if (addressDefinition.HasSymbolicAddress)
            {
                return addressDefinition.SymbolicAddress;
            }

            return addressDefinition.Address.Value.ToString();
        }

        private static void ValidateTypeCompatibility(AcsAddressDefinition addressDefinition, Type targetType)
        {
            if (targetType == null)
            {
                throw new ArgumentNullException(nameof(targetType));
            }

            Type nonNullableTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType;
            if (nonNullableTargetType == typeof(object))
            {
                return;
            }

            if (addressDefinition.DataType == nonNullableTargetType)
            {
                return;
            }

            if (addressDefinition.IsInteger && IsIntegerType(nonNullableTargetType))
            {
                return;
            }

            if (addressDefinition.IsFloatingPoint && IsFloatingPointType(nonNullableTargetType))
            {
                return;
            }

            throw new InvalidOperationException($"Address {addressDefinition.Name} is defined as {addressDefinition.DataType.Name} and cannot be read as {nonNullableTargetType.Name}.");
        }

        private static T ConvertValue<T>(object value, AcsAddressDefinition addressDefinition)
        {
            if (value == null)
            {
                return default(T);
            }

            if (value is T typedValue)
            {
                return typedValue;
            }

            Type targetType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
            try
            {
                if (targetType == typeof(bool))
                {
                    return (T)(object)ConvertToBoolean(value);
                }

                object convertedValue = Convert.ChangeType(value, targetType);
                return (T)convertedValue;
            }
            catch (Exception ex)
            {
                throw new InvalidCastException($"The value of address {addressDefinition.Name} cannot be converted to {targetType.Name}.", ex);
            }
        }

        private static bool ConvertToBoolean(object value)
        {
            if (value is bool boolValue)
            {
                return boolValue;
            }

            if (value is string stringValue)
            {
                if (string.Equals(stringValue, "1", StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }

                if (string.Equals(stringValue, "0", StringComparison.OrdinalIgnoreCase))
                {
                    return false;
                }
            }

            return Convert.ToDouble(value) != 0d;
        }

        private static void ValidateWriteValue(AcsAddressDefinition addressDefinition, object value)
        {
            if (value == null)
            {
                throw new ArgumentNullException(nameof(value));
            }

            if (addressDefinition.Length > 1)
            {
                return;
            }

            Type valueType = value.GetType();
            if (addressDefinition.DataType.IsAssignableFrom(valueType))
            {
                return;
            }

            if (addressDefinition.IsBoolean && IsBooleanCompatibleType(valueType))
            {
                return;
            }

            if (addressDefinition.IsInteger && IsIntegerType(valueType))
            {
                return;
            }

            if (addressDefinition.IsFloatingPoint && (IsFloatingPointType(valueType) || IsIntegerType(valueType)))
            {
                return;
            }

            throw new InvalidOperationException($"Address {addressDefinition.Name} is defined as {addressDefinition.DataType.Name}, but the current value type is {valueType.Name}.");
        }

        private static bool IsBooleanCompatibleType(Type type)
        {
            return type == typeof(bool) || IsIntegerType(type);
        }

        private static bool IsIntegerType(Type type)
        {
            return type == typeof(byte)
                || type == typeof(sbyte)
                || type == typeof(short)
                || type == typeof(ushort)
                || type == typeof(int)
                || type == typeof(uint)
                || type == typeof(long)
                || type == typeof(ulong);
        }

        private static bool IsFloatingPointType(Type type)
        {
            return type == typeof(float)
                || type == typeof(double)
                || type == typeof(decimal);
        }

        private static void ValidateIndex(int index, string parameterName)
        {
            if (index < 0)
            {
                throw new ArgumentOutOfRangeException(parameterName, index, "ACS index cannot be less than 0.");
            }
        }

        private static void ValidateRange(int from, int to, string fromName, string toName)
        {
            ValidateIndex(from, fromName);
            ValidateIndex(to, toName);
            if (to < from)
            {
                throw new ArgumentException($"Parameter {toName} cannot be less than {fromName}.", toName);
            }
        }

        private static Exception CreateReadWriteException(string operationName, AcsAddressDefinition addressDefinition)
        {
            string addressText = addressDefinition.HasSymbolicAddress
                ? addressDefinition.SymbolicAddress
                : addressDefinition.Address.Value.ToString();
            return new InvalidOperationException($"{operationName} ACS address failed: {addressDefinition.Name}, address={addressText}.");
        }
    }
}
