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

namespace PlcCom
{
    public class DeltaAxPLCNetConfig
    {
        private string axIP = "192.168.0.10";

        public string GetAxIP()
        {
            return axIP;
        }

        private void SetAxIP(string value)
        {
            axIP = value;
        }

        private string axPort = "11740";

        public string GetAxPort()
        {
            return axPort;
        }

        private void SetAxPort(string value)
        {
            axPort = value;
        }
    }
    public class DeltaAxCommProtocol : IPlcComProtocol
    {
        public bool IsConnected { get; set; }
        public string AxIP { get; }
        public string AxPort { get; }
        AXClient_t client;

        public DeltaAxCommProtocol(DeltaAxPLCNetConfig deltaAxPLCNetConfig) 
        {
            AxIP= deltaAxPLCNetConfig.GetAxIP();
            AxPort= deltaAxPLCNetConfig.GetAxPort();
            var rt = Initialize();
            client = new AXClient_t();
           
        }
        public DeltaAxCommProtocol():this(new DeltaAxPLCNetConfig())
        {

        }

        private uint Initialize()
        {
            //Configure log
            LogConfig_t logConfig = new LogConfig_t();
            logConfig.Enable = true;
            logConfig.Path = "./";
            logConfig.DateFormat = "%Y-%m-%d %H:%M:%S.%q";
            logConfig.MaxFileSize = 512 * 1024;
            logConfig.MaxBackupIndex = 5;
            logConfig.LevelMask = Convert.ToUInt32(LevelMask_t.LOG_LEVEL_FATAL | LevelMask_t.LOG_LEVEL_ERROR | LevelMask_t.LOG_LEVEL_WARN | LevelMask_t.LOG_LEVEL_INFO);
            logConfig.CategoryMask = Convert.ToUInt32(CategoryMask_t.LOG_CATEGORY_SYSTEM | CategoryMask_t.LOG_CATEGORY_CONNECT);

            var status = IACommService_t.SetLogConfig(logConfig);

            //Global initialization
            status = IACommService_t.Initialise();
            return status;
        }

        private uint WaitOperationComplete(ICallback callback, uint timeout/*second*/)
        {
            if (null == callback)
                return ErrorCode.InvalidArgument;

            uint status = ErrorCode.Good;
            TimeSpan begin = new TimeSpan(DateTime.Now.Ticks);
            while (!callback.IsDone &&
                ((new TimeSpan(DateTime.Now.Ticks)) - begin).TotalSeconds <= timeout)
            {
                Thread.Sleep(100);
            }

            if (!callback.IsDone)
            {
                status = ErrorCode.Timeout;
            }

            return status;
        }
        private uint Connect(AXClient_t client)
        {
            if (null == client)
                return ErrorCode.InvalidArgument;

            //Connection type
            bool isConnectByIPAddr = true;
            bool isConnectViaGateWay = false;

            //Gateway IP, port
            string gatewayIPAddr = "127.0.0.1";
            string gatewayPort = "1217";

            //Device IP, port, logic address
            //string axIPAddr = "192.168.0.10";
            //string axPort = "11740";
            string axIPAddr = AxIP;
            string axPort = AxPort;
            string axAddress = "3001.50A8";

            //User,  password
            string userName = "";
            string password = "";

            uint status;

            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("===================== Start to Connect ====================");

            if (isConnectByIPAddr)
            {
                if (isConnectViaGateWay)
                    status = client.ConnectViaGateway(gatewayIPAddr, gatewayPort, axIPAddr, axPort, userName, password);
                else
                    status = client.Connect(axIPAddr, axPort, userName, password);
            }
            else
            {
                if (isConnectViaGateWay)
                    status = client.ConnectViaGatewayByAddress(gatewayIPAddr, gatewayPort, axAddress, userName, password);
                else
                    status = client.ConnectByAddress(axAddress, userName, password);
            }
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("==================== Connect Completed ===================\n");

            return status;
        }

        /// <summary>
        /// 连接PLC
        /// </summary>
        /// <returns>返回代码，0：成功，其它：失败</returns>
        public (uint rt,string errReason) ConnectPLC()
        {
            //BrowseDeviceCallback browseDeviceCB = new BrowseDeviceCallback();
            //rt = AXClient_t.BrowseAsync(null, null, browseDeviceCB);
            //if (rt != 0)
            //    return (rt, "BrowseAsync");

            //rt =WaitOperationComplete(browseDeviceCB, 3/*seconds*/);
            //if (rt != 0)
            //    return (rt, "WaitOperationComplete");

            var rt = Connect(client);
            if (rt != 0)
            {
                IsConnected = false;
                return (rt, "Connect Failed");
            }
            else
            {
                var r = ReadValue("Application.Var_state.al", out object al);
                if (r == 0 && al is bool b && !b)
                {
                    Close();
                    return (0x7FFFFFFF, "");
                }
                
                IsConnected = true;
                return (rt, "Connect Success");
            }
        }

        /// <summary>
        /// 关闭连接
        /// </summary>
        /// <returns>返回代码，0：成功，其它：失败</returns>
        public uint Close()
        {
            var rt=client.Disconnect();
            client.Dispose();
            return rt;
        }

        /// <summary>
        /// 读取指定地址和指定类型的值
        /// </summary>
        /// <param name="address">PLC地址字符串</param>
        /// <param name="result">返回的读取结果</param>
        /// <returns>返回代码，0：成功，其它：失败</returns>
        public uint ReadValue(string address, out object result)
        {
            DataItem_t dataItemFromRead = null;
            var status = client.Read(address, ref dataItemFromRead);

            if (ErrorCode.Good == status && dataItemFromRead != null)
            {
                result = dataItemFromRead.Value.Get();
            }
            else
            {
                result = null;
            }
            if (dataItemFromRead != null)
            {
                dataItemFromRead.Dispose();
            }
            return status;
        }
        /// <summary>
        /// 批量读取指定地址和指定类型的值
        /// </summary>
        /// <param name="addresses">PLC地址字符串列表</param>
        /// <param name="results">返回的读取结果列表</param>
        /// <returns>返回代码，0：成功，其它：失败</returns>
        public uint ReadValue(List<string> addresses, out List<object> results)
        {
            DataItem_t[] dataItemFromReadList = null;
            results = null;
            var status = client.Read(addresses.ToArray(), ref dataItemFromReadList);
           
            if (ErrorCode.Good == status && dataItemFromReadList != null)
            {
                results= dataItemFromReadList.Select(x => x.Value.Get()).ToList();
            }
            if (dataItemFromReadList != null)
            {
                foreach (var dataItem in dataItemFromReadList)
                    dataItem.Dispose();
            }
            return status;
        }

        /// <summary>
        /// 写入指定类型的值到指定地址
        /// </summary>
        /// <param name="address"></param>
        /// <param name="value">要写入的值</param>
        /// <returns>返回代码，0：成功，其它：失败</returns>
        public uint WriteValue(string address, object value,ref uint result)
        {
            DataItem_t dataItemToWrite = new DataItem_t(value);
            //Write Symbol
            return client.Write(address, dataItemToWrite, ref result);
        }

        public uint WriteValue(List<string> addresses, List<object> values,ref List<uint> resultList)
        {
            uint[] results = null;
            DataItem_t[] dataItems= values.Select(v=>  new DataItem_t(v)).ToArray();
            var status = client.Write(addresses.ToArray(), dataItems, ref results);
            resultList= results?.ToList();
            return status;
        }
    }
    public interface ICallback
    {
        bool IsDone
        {
            get;
        }
    }
    public class BrowseDeviceCallback : IBrowseDeviceCallback_t, ICallback
    {
        private bool m_isDone;
        private List<BrowsedDeviceInfo_t> m_axDevices = new List<BrowsedDeviceInfo_t>();

        public BrowseDeviceCallback()
        {
        }
        ~BrowseDeviceCallback()
        {
            m_axDevices.Clear();
        }
        public override void OnBrowseDevices(BrowsedDeviceInfo_t axDevice, uint status, bool bCompleted)
        {
            if (status == ErrorCode.Good && !bCompleted)
            {
                Console.ForegroundColor = ConsoleColor.Cyan;
                Console.WriteLine(">>>>>>>>>>>>>>>>>>>> Found A New Device <<<<<<<<<<<<<<<<<<<");
            }
            else if (status == ErrorCode.Failed && !bCompleted)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("Browse Error!");
            }

            m_isDone = bCompleted;

            if (bCompleted)
            {
                Console.ForegroundColor = ConsoleColor.Cyan;
                Console.WriteLine(">>>>>>>>>>>>>>>>>> No More New Device Found <<<<<<<<<<<<<<<");
            }
        }

        public bool IsDone
        {
            get { return m_isDone; }
        }
    }

}
