// Ignore Spelling: timestamp Cmp

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IACommService4CSharp
{
    public class AXClient_t : IDisposable
    {
        private IClient4CSharp m_client;
        private bool m_bSymbolNameEncode;
        private bool m_disposed;
        private IReadCompleteCallback_t m_readCompleteCallback;
        private IWriteCompleteCallback_t m_writeCompleteCallback;
        private IDataChangeCallback_t m_dataChangeCallback;
        private readonly object m_locker = new object();
        private Dictionary<uint, string[]> m_symbolListCache = new Dictionary<uint, string[]>();

        public static uint BrowseAsync(string gatewayIP, string gatewayPort, IBrowseDeviceCallback_t browseCallback)
        {
            if (null == browseCallback || (null == gatewayIP && null != gatewayPort) || (null != gatewayIP && null == gatewayPort))
                return ErrorCode.InvalidArgument;

            DataValue_t gatewayInfos = null;
            if (gatewayIP != null && gatewayPort != null)
            {
                gatewayInfos = new DataValue_t(DType.Complex);
                var gatewayInfo = new DataValue_t(DType.String);

                gatewayInfo.SetValue(gatewayIP);
                gatewayInfos.SetValue("GatewayIPAddress", gatewayInfo);

                gatewayInfo.SetValue(gatewayPort);
                gatewayInfos.SetValue("GatewayPort", gatewayInfo);

                gatewayInfo.Dispose();
            }

            var clientMgr = CommClientManager as ICommClientManager24CSharp;

            if (null == clientMgr)
                return ErrorCode.NotSupported;

            var status = clientMgr.BrowseAsync("AXDriver", gatewayInfos == null ? null : gatewayInfos.Internal, browseCallback);

            if (gatewayInfos != null)
                gatewayInfos.Dispose();

            return status;
        }

        /* Parameter bool 'bSymbolNameEncode' : 
           'false' is mean that the instance of AXClient_t adapt to the symbol name encoding of DIADesigner-AX 1.5 or later, 
            or other AX designer which install CODOSYS communication 4.3 or later add-on.
           'true' is mean that the instance of AXClient_t adapt to the symbol name encoding of DIADesigner-AX 1.4 or below, 
            and which did not install CODOSYS communication 4.3 or later add-on.
        */
        public AXClient_t(bool bSymbolNameEncode = false)
        {
            m_client = CommClientManager.CreateClient("AXDriver");
            if (null == m_client)
                throw new IACommServiceException(ErrorCode.InternalError);
            m_bSymbolNameEncode = bSymbolNameEncode;
            m_disposed = false;
        }
        ~AXClient_t()
        {
            Dispose(false);
        }
        public uint Connect(string axIPAddr, string port = "11740", string userName = null, string password = null)
        {
            if (null == m_client)
                return ErrorCode.InvalidState;

            if (null == axIPAddr || null == port)
                return ErrorCode.InvalidArgument;

            GeneralConfig_t config = new GeneralConfig_t();
            config.SetParameter("IPAddress", axIPAddr);
            config.SetParameter("Port", port);

            if (userName != null)
                config.SetParameter("User", userName);
            if (password != null)
                config.SetParameter("Password", password);

            config.SetParameter("SymbolNameEncode", m_bSymbolNameEncode ? "Yes" : "No");

            return m_client.Connect(config.Internal);
        }
        public uint ConnectViaGateway(string gatewayIPAddr, string gatewayPort, string axIPAddr, string port = "11740", string userName = null, string password = null)
        {

            if (null == m_client)
                return ErrorCode.InvalidState;

            if (null == gatewayIPAddr || null == gatewayPort || null == axIPAddr || null == port)
                return ErrorCode.InvalidArgument;

            GeneralConfig_t config = new GeneralConfig_t();

            //Add gateway connector configuration
            config.SetParameter("GatewayEnable", "Enabled");
            config.SetParameter("GatewayName", "Gateway1");
            config.SetParameter("GatewayIpAddress", gatewayIPAddr);
            config.SetParameter("GatewayPort", gatewayPort);

            //Add AX Device connect configuration
            config.SetParameter("IPAddress", axIPAddr);
            config.SetParameter("Port", port);
            if (userName != null)
                config.SetParameter("User", userName);
            if (password != null)
                config.SetParameter("Password", password);

            return m_client.Connect(config.Internal);
        }
        public uint ConnectByAddress(string axAddress /*logical address*/, string userName = null, string password = null)
        {
            if (null == m_client)
                return ErrorCode.InvalidState;

            if (null == axAddress)
                return ErrorCode.InvalidArgument;

            GeneralConfig_t config = new GeneralConfig_t();
            config.SetParameter("Address", axAddress);
            config.SetParameter("ConnectType", "Logical_Address");

            if (userName != null)
                config.SetParameter("User", userName);
            if (password != null)
                config.SetParameter("Password", password);

            return m_client.Connect(config.Internal);
        }
        public uint ConnectViaGatewayByAddress(string gatewayIPAddr, string gatewayPort, string axAddress /*logical address*/, string userName = null, string password = null)
        {
            if (null == m_client)
                return ErrorCode.InvalidState;

            if (null == gatewayIPAddr || null == gatewayPort || null == axAddress)
                return ErrorCode.InvalidArgument;

            GeneralConfig_t config = new GeneralConfig_t();

            //Add gateway connector configuration
            config.SetParameter("GatewayEnable", "Enabled");
            config.SetParameter("GatewayName", "Gateway1");
            config.SetParameter("GatewayIpAddress", gatewayIPAddr);
            config.SetParameter("GatewayPort", gatewayPort);

            //Add AX Device connect configuration
            config.SetParameter("Address", axAddress);
            config.SetParameter("ConnectType", "Logical_Address");

            if (userName != null)
                config.SetParameter("User", userName);
            if (password != null)
                config.SetParameter("Password", password);

            return m_client.Connect(config.Internal);
        }
        public uint Disconnect()
        {
            return m_client.Disconnect();
        }
        public uint Read(string symbolName, ref DataItem_t dataItem)
        {
            if (null == symbolName)
                return ErrorCode.InvalidArgument;

            if (dataItem is null)
                dataItem = new DataItem_t();

            var addressList = new string[1] { symbolName };
            var dataItemList = new DataItem_t[1] { dataItem };

            return Read(addressList, ref dataItemList);
        }
        public uint Read(string[] symbolNameList, ref DataItem_t[] dataItemList)
        {
            if (null == symbolNameList)
                return ErrorCode.InvalidArgument;

            if (0 == symbolNameList.Length)
                return ErrorCode.NothingToDo;

            if (dataItemList is null)
            {
                dataItemList = new DataItem_t[symbolNameList.Length];
            }
            else if(dataItemList.Length < symbolNameList.Length)
            {
                var tempList = dataItemList.ToList<DataItem_t>();
                while(tempList.Count < symbolNameList.Length)
                {
                    tempList.Add(new DataItem_t());
                }
                dataItemList = tempList.ToArray<DataItem_t>();
            }
            else if(dataItemList.Length > symbolNameList.Length)
            {
                var tempList = dataItemList.ToList<DataItem_t>();
                tempList.RemoveRange(symbolNameList.Length, dataItemList.Length - symbolNameList.Length);
                dataItemList = tempList.ToArray<DataItem_t>();
            }

            var dataItemInternals = new IDataItem4CSharp[dataItemList.Length];
            for (int index = 0; index < dataItemList.Length; index++)
            {
                if (dataItemList[index] is null)
                    dataItemList[index] = new DataItem_t();

                dataItemInternals[index] = dataItemList[index].Internal;
            }
            return m_client.Read(symbolNameList, dataItemInternals);
        }
        public void SetReadAsyncCallback(IReadCompleteCallback_t callback)
        {
            m_readCompleteCallback = callback;
        }
        public uint ReadAsync(string[] symbolNameList)
        {
            if (null == symbolNameList)
                return ErrorCode.InvalidArgument;

            if (0 == symbolNameList.Length)
                return ErrorCode.NothingToDo;

            uint transactionId = 0;
            if (m_readCompleteCallback != null) m_readCompleteCallback.Lock();
            uint status = m_client.ReadAsync(symbolNameList, m_readCompleteCallback, ref transactionId);
            if (ErrorCode.Good == status)
            {
                m_readCompleteCallback.AddRequestCache(transactionId, symbolNameList);
            }
            if (m_readCompleteCallback != null) m_readCompleteCallback.Unlock();
            return status;
        }
        public uint Write(string symbolName, DataItem_t dataItem, ref uint result)
        {
            if (null == symbolName || null == dataItem)
                return ErrorCode.InvalidArgument;

            var addressList = new string[1] { symbolName };
            var dataItemList = new DataItem_t[1] { dataItem };
            uint[] resultList = null;

            var status = Write(addressList, dataItemList, ref resultList);
            if (ErrorCode.Good == status)
                result = resultList[0];

            return status;
        }
        public uint Write(string[] symbolNameList, DataItem_t[] dataItemList, ref uint[] resultList)
        {
            if (null == symbolNameList || null == dataItemList)
                return ErrorCode.InvalidArgument;

            if (0 == symbolNameList.Length)
                return ErrorCode.NothingToDo;

            if (symbolNameList.Length != dataItemList.Length)
                return ErrorCode.InvalidArgument;

            IDataItem4CSharp[] dataItemInternals = new IDataItem4CSharp[dataItemList.Length];
            for (int index = 0; index < dataItemList.Length; index++)
            {
                dataItemInternals[index] = dataItemList[index] != null ? dataItemList[index].Internal : null;
            }
            resultList = new uint[symbolNameList.Length];

            return m_client.Write(symbolNameList, dataItemInternals, resultList);
        }
        public void SetWriteAsyncCallback(IWriteCompleteCallback_t callback)
        {
            m_writeCompleteCallback = callback;
        }
        public uint WriteAsync(string[] symbolNameList, DataItem_t[] dataItemList)
        {
            if (null == symbolNameList || null == dataItemList)
                return ErrorCode.InvalidArgument;

            if (0 == symbolNameList.Length)
                return ErrorCode.NothingToDo;

            if (symbolNameList.Length != dataItemList.Length)
                return ErrorCode.InvalidArgument;

            IDataItem4CSharp[] dataItemInternals = new IDataItem4CSharp[dataItemList.Length];
            for (int index = 0; index < dataItemList.Length; index++)
            {
                dataItemInternals[index] = dataItemList[index] != null ? dataItemList[index].Internal : null;
            }

            uint transactionId = 0;
            if (m_writeCompleteCallback != null) m_writeCompleteCallback.Lock();
            uint status = m_client.WriteAsync(symbolNameList, dataItemInternals, m_writeCompleteCallback, ref transactionId);
            if (ErrorCode.Good == status)
                m_writeCompleteCallback.AddRequestCache(transactionId, symbolNameList);
            if (m_writeCompleteCallback != null) m_writeCompleteCallback.Unlock();

            return status;
        }
        public void SetDataChangeCallback(IDataChangeCallback_t callback)
        {
            m_dataChangeCallback = callback;
        }
        public uint Subscribe(string[] symbolNameList, ref uint samplingInterval, ref uint publishingInterval, ref uint handler)
        {
            if (null == symbolNameList)
                return ErrorCode.InvalidArgument;

            if (0 == symbolNameList.Length)
                return ErrorCode.NothingToDo;

            return m_client.Subscribe(symbolNameList, m_dataChangeCallback, ref samplingInterval, ref publishingInterval, ref handler);
        }
        public uint Unsubscribe(uint handler)
        {
            return m_client.Unsubscribe(handler);
        }
        public uint ReadDeviceInfo(ref AXDeviceInfo_t axDeviceInfo)
        {
            var axClient = m_client as IAXClient4CSharp;
            if (null == axClient)
                return ErrorCode.NotSupported;

            DataValue_t dataValue = new DataValue_t();

            string[] propertyNames =
            {
                "Name",
                "Address",
                "Vendor",
                "Version",
                "SeriesName",
                "SerialNumber",
                "DeviceID",
                "DeviceType",
                "ChannelNumber",
                "EncryptedCommunication",
                "Ethernet"
            };

            IDataValue4CSharp[] propertyValues = new IDataValue4CSharp[propertyNames.Length];
            for (uint index = 0; index < propertyValues.Length; index++)
            {
                propertyValues[index] = DataValueFactory4CSharp.Create(DType.Undefined, null);
            }

            var status = axClient.ReadDeviceProperties(propertyNames, propertyValues);
            if (status == ErrorCode.Good) {
                if (null == axDeviceInfo)
                    axDeviceInfo = new AXDeviceInfo_t();

                for (uint index = 0; index < propertyNames.Length; index++)
                {
                    axDeviceInfo.Internal.SetValue(propertyNames[index], propertyValues[index]);
                }
            }

            for (uint index = 0; index < propertyValues.Length; index++)
            {
                DataValueFactory4CSharp.Destroy(propertyValues[index]);
            }

            return status;
        }
        public uint BrowseSymbol(string address, ref ArrayBase_t<SymbolNodeInfo_t> childSymbolNodes)
        {
            if (null == address)
                return ErrorCode.InvalidArgument;

            var axClient = m_client as IAXClient4CSharp;

            if (null == axClient)
                return ErrorCode.NotSupported;

            if (null == childSymbolNodes)
                childSymbolNodes = new ArrayBase_t<SymbolNodeInfo_t>();

            return axClient.BrowseSymbol(address, null, childSymbolNodes.Internal);
        }
        public uint GetSymbolProperty(string address, ref SymbolNodeInfo_t symbolProperty)
        {
            if (null == address)
                return ErrorCode.InvalidArgument;

            var axClient = m_client as IAXClient4CSharp;

            if (null == axClient)
                return ErrorCode.NotSupported;

            if (null == symbolProperty)
                symbolProperty = new SymbolNodeInfo_t();

            return axClient.GetSymbolProperty(address, symbolProperty.Internal);
        }
        public uint GetConnectionState(ref ConnectionState connState)
        {
            if (null == m_client)
                return ErrorCode.InvalidState;

            var outputArgument = new DataValue_t(DType.Int32);

            var state = m_client.DoCommand("GetConnectionState", null, outputArgument.Internal);

            if(ErrorCode.Good == state)
            {
                int intValue = 0;
                outputArgument.GetValue(ref intValue);
                connState = (ConnectionState)intValue;
            }

            outputArgument.Dispose();

            return state;
        }

        public uint SetConnectionCallback(IConnectionCallback_t callback)
        {
            var axClient2 = m_client as IAXClient24CSharp;
            if (null == axClient2)
                return ErrorCode.NotSupported;

            return axClient2.SetConnectionCallback(callback);
        }
        public uint CreateGroup(string[] symbolNameList, ref uint groupId, ref uint[] resultList)
        {
            var axClient2 = m_client as IAXClient24CSharp;
            if (null == axClient2)
                return ErrorCode.NotSupported;

            resultList = new uint[symbolNameList.Length];
            uint status = axClient2.CreateGroup(symbolNameList, ref groupId, resultList);
            if (ErrorCode.Good == status)
            {
                lock (m_locker)
                {
                    m_symbolListCache.Add(groupId, symbolNameList);
                }
            }
            return status;
        }
        public uint GetSymbolCount(uint groupId, ref uint count)
        {
            var axClient2 = m_client as IAXClient24CSharp;
            if (null == axClient2)
                return ErrorCode.NotSupported;

            return axClient2.GetSymbolCount(groupId, ref count);
        }
        public uint GetSymbolAddress(uint groupId, uint index, ref string address)
        {
            var axClient2 = m_client as IAXClient24CSharp;
            if (null == axClient2)
                return ErrorCode.NotSupported;

            return axClient2.GetSymbolAddress(groupId, index, ref address);
        }
        public uint DeleteGroup(uint groupId)
        {
            var axClient2 = m_client as IAXClient24CSharp;
            if (null == axClient2)
                return ErrorCode.NotSupported;

            uint status = axClient2.DeleteGroup(groupId);
            if (ErrorCode.Good == status)
            {
                lock (m_locker)
                {
                    m_symbolListCache.Remove(groupId);
                }
            }
            return status;
        }
        public uint Read(uint groupId, ref DataItem_t[] dataItemList)
        {
            var axClient2 = m_client as IAXClient24CSharp;
            if (null == axClient2)
                return ErrorCode.NotSupported;

            uint count = 0;
            uint status = axClient2.GetSymbolCount(groupId, ref count);
            if (status != ErrorCode.Good)
                return status;

            if (dataItemList is null)
                dataItemList = new DataItem_t[count];

            IDataItem4CSharp[] dataItemInternals = new IDataItem4CSharp[dataItemList.Length];
            for (int index = 0; index < dataItemList.Length; index++)
            {
                if (dataItemList[index] is null)
                    dataItemList[index] = new DataItem_t();

                dataItemInternals[index] = dataItemList[index].Internal;
            }
            return axClient2.Read(groupId, dataItemInternals);
        }
        public uint ReadAsync(uint groupId)
        {
            var axClient2 = m_client as IAXClient24CSharp;
            if (null == axClient2)
                return ErrorCode.NotSupported;

            string[] symbolNameList = null;
            lock (m_locker)
            {
                if (!m_symbolListCache.ContainsKey(groupId))
                    return ErrorCode.NotFound;

                symbolNameList = m_symbolListCache[groupId];
            }

            uint transactionId = 0;
            if (m_readCompleteCallback != null) m_readCompleteCallback.Lock();
            uint status = axClient2.ReadAsync(groupId, m_readCompleteCallback, ref transactionId);
            if (ErrorCode.Good == status)
            {
                m_readCompleteCallback.AddRequestCache(transactionId, symbolNameList);
            }
            if (m_readCompleteCallback != null) m_readCompleteCallback.Unlock();
            return status;
        }
        public uint Write(uint groupId, DataItem_t[] dataItemList, ref uint[] resultList)
        {
            if (null == dataItemList)
                return ErrorCode.InvalidArgument;

            var axClient2 = m_client as IAXClient24CSharp;
            if (null == axClient2)
                return ErrorCode.NotSupported;

            uint count = 0;
            uint status = axClient2.GetSymbolCount(groupId, ref count);
            if (status != ErrorCode.Good)
                return status;

            if (dataItemList.Length != count)
                return ErrorCode.InvalidArgument;

            IDataItem4CSharp[] dataItemInternals = new IDataItem4CSharp[dataItemList.Length];
            for (int index = 0; index < dataItemList.Length; index++)
            {
                dataItemInternals[index] = dataItemList[index] != null ? dataItemList[index].Internal : null;
            }
            resultList = new uint[dataItemList.Length];

            return axClient2.Write(groupId, dataItemInternals, resultList);
        }
        public uint WriteAsync(uint groupId, DataItem_t[] dataItemList)
        {
            if (null == dataItemList)
                return ErrorCode.InvalidArgument;

            var axClient2 = m_client as IAXClient24CSharp;
            if (null == axClient2)
                return ErrorCode.NotSupported;

            string[] symbolNameList = null;
            lock (m_locker)
            {
                if (!m_symbolListCache.ContainsKey(groupId))
                    return ErrorCode.NotFound;

                symbolNameList = m_symbolListCache[groupId];
            }

            if (symbolNameList.Length != dataItemList.Length)
                return ErrorCode.InvalidArgument;

            IDataItem4CSharp[] dataItemInternals = new IDataItem4CSharp[dataItemList.Length];
            for (int index = 0; index < dataItemList.Length; index++)
            {
                dataItemInternals[index] = dataItemList[index] != null ? dataItemList[index].Internal : null;
            }

            uint transactionId = 0;
            if (m_writeCompleteCallback != null) m_writeCompleteCallback.Lock();
            uint status = axClient2.WriteAsync(groupId, dataItemInternals, m_writeCompleteCallback, ref transactionId);

            m_writeCompleteCallback.AddRequestCache(transactionId, symbolNameList);

            if (m_writeCompleteCallback != null) m_writeCompleteCallback.Unlock();

            return status;
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposeManaged)
        {
            if (!m_disposed)
            {
                if (disposeManaged)
                {
                    //do nothing
                }

                CommClientManager.DestroyClient(m_client);
                m_client = null;

                m_disposed = true;
            }
        }
        private static ICommClientManager4CSharp CommClientManager
        {
            get
            {
                if (!IACommService_t.IsInitialised)
                {
                    IACommService_t.Initialise();
                }
                IIACommService4CSharp commService = IACommService_t.Instance();
                return commService != null ? commService.ClientManager() : null;
            }
        }
    }
}
