using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using HighWayIot.Log4net;
using HslCommunication;
using HslCommunication.Profinet.Melsec;
using System.Diagnostics;
using System.Linq;

namespace HighWayIot.Plc
{
    public class PlcConnect
    {
        //private static readonly Lazy<PlcConnect> lazy = new Lazy<PlcConnect>(() => new PlcConnect());

        //public static PlcConnect Instance
        //{
        //    get
        //    {
        //        return lazy.Value;
        //    }
        //}

        private static LogHelper logHelper = LogHelper.Instance;

        /// <summary>
        /// 静态懒加载MelsecMcNet2 读取 CPU
        /// </summary>
        public static readonly MelsecMcNet MelsecInstance1 = CreateAb("192.168.0.10", 2001);


        /// <summary>
        /// 静态懒加载MelsecMcNet2 写入 CPU
        /// </summary>
        public static readonly MelsecMcNet MelsecInstance2 = CreateAb("192.168.0.10", 2002);

        /// <summary>
        /// 锁对象写
        /// </summary>
        private static readonly object lockerWrite = new object();

        /// <summary>
        /// 锁对象读
        /// </summary>
        private static readonly object lockerRead = new object();

        

        private PlcConnect()
        {

        }

        /// <summary>
        /// 初始化三菱的服务器
        /// </summary>
        /// <returns></returns>
        private static MelsecMcNet CreateAb(string ip, int port)
        {
            //string Ip = ;
            MelsecMcNet plc = new MelsecMcNet();
            try
            {
                plc.CommunicationPipe = new HslCommunication.Core.Pipe.PipeTcpNet(ip, port)
                {
                    ConnectTimeOut = 3000,    // 连接超时时间，单位毫秒
                    ReceiveTimeOut = 2000,   // 接收超时时间，单位毫秒
                    SleepTime = 0,
                    SocketKeepAliveTime = -1,
                    IsPersistentConnection = true,
                };

                OperateResult result;
                result = plc.ConnectServer();
                logHelper.Info($"Plc连接 信息：[{result.Message}] 错误代码：[{result.ErrorCode}]");

            }
            catch (Exception ex)
            {
                logHelper.Error("初始化PLC服务器发生错误！", ex);
            }

            return plc;
        }

        /// <summary>
        /// PLC2写入数据
        /// </summary>
        /// <param name="address">地址</param>
        /// <param name="value">值</param>
        /// <param name="type">数据类型</param>
        /// <returns></returns>
        public static OperateResult PlcWrite2(string address, object value, DataTypeEnum type)
        {
            var result = new OperateResult() { IsSuccess = false };
            try
            {
                lock (lockerWrite)
                {
                    //Stopwatch swWrite = new Stopwatch();
                    //swWrite.Start();
                    switch (type)
                    {
                        case DataTypeEnum.Bool:
                            result = MelsecInstance1.Write(address, Convert.ToBoolean(value));
                            break;
                        case DataTypeEnum.Byte:
                            result = MelsecInstance1.Write(address, Convert.ToByte(value));
                            break;
                        //case DataTypeEnum.Bytes:
                        //    result = MelsecInstance1.Write(address, ObjectToBytes(value));
                        //    break;
                        case DataTypeEnum.Int16:
                            result = MelsecInstance1.Write(address, Convert.ToInt16(value));
                            break;
                        case DataTypeEnum.UInt16:
                            result = MelsecInstance1.Write(address, Convert.ToUInt16(value));
                            break;
                        case DataTypeEnum.Int32:
                            result = MelsecInstance1.Write(address, Convert.ToInt32(value));
                            break;
                        case DataTypeEnum.UInt32:
                            result = MelsecInstance1.Write(address, Convert.ToUInt32(value));
                            break;
                        case DataTypeEnum.Int64:
                            result = MelsecInstance1.Write(address, Convert.ToInt64(value));
                            break;
                        case DataTypeEnum.UInt64:
                            result = MelsecInstance1.Write(address, Convert.ToUInt64(value));
                            break;
                        case DataTypeEnum.Float:
                            result = MelsecInstance1.Write(address, Convert.ToSingle(value));
                            break;
                        case DataTypeEnum.Double:
                            result = MelsecInstance1.Write(address, Convert.ToDouble(value));
                            break;
                        case DataTypeEnum.String:
                            result = MelsecInstance1.Write(address, Convert.ToString(value));
                            break;
                        default:
                            throw new ArgumentException($"Unsupported data type: {type}");
                    }
                    //swWrite.Stop();
                    //logHelper.Info($"Write address:[{address}] value:[{value}] result:[{result.IsSuccess}] time:[{swWrite.ElapsedMilliseconds}]");
                }
            }
            catch (Exception ex)
            {
                logHelper.Error("PLC写入数据发生错误！", ex);
            }
            return result;
        }

        /// <summary>
        /// 写入byte数组
        /// </summary>
        /// <param name="address"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public static OperateResult PlcWriteBytes2(string address, byte[] data)
        {
            OperateResult result = new OperateResult();
            try
            {
                lock (lockerWrite)
                {
                    result = MelsecInstance2.Write(address, data);
                }
            }
            catch (Exception ex)
            {
                logHelper.Error($"PLC2写入Bytes发生错误！address:[{address}]", ex);
                return default;
            }
            return result;
        }

        // 将对象序列化为 byte 数组
        public static byte[] ObjectToBytes(object obj)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                IFormatter formatter = new BinaryFormatter();
                formatter.Serialize(ms, obj);
                return ms.ToArray();
            }
        }

        // 将 byte 数组反序列化为对象
        public static object BytesToObject(byte[] bytes)
        {
            using (MemoryStream ms = new MemoryStream(bytes))
            {
                IFormatter formatter = new BinaryFormatter();
                return formatter.Deserialize(ms);
            }
        }

        /// <summary>
        /// 字符串读取2
        /// </summary>
        /// <param name="address"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public static OperateResult<string> ReadString2(string address, ushort length)
        {
            OperateResult<string> result = new OperateResult<string>();
            try
            {
                lock (lockerRead)
                {
                    result = MelsecInstance2.ReadString(address, length);
                }
            }
            catch (Exception ex)
            {
                logHelper.Error($"PLC2读取String发生错误！address:[{address}]", ex);
                return default;
            }
            return result;
        }

        /// <summary>
        /// 读取字节数组
        /// </summary>
        /// <param name="address"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public static OperateResult<byte[]> ReadByte2(string address, ushort length)
        {
            OperateResult<byte[]> result = new OperateResult<byte[]>();
            try
            {
                lock (lockerRead)
                {
                    //Stopwatch swRead = new Stopwatch();
                    //swRead.Start();
                    result = MelsecInstance2.Read(address, length);
                    //swRead.Stop();
                    //logHelper.Info($"Read address:[{address}] length:[{length}] result:[{result.IsSuccess}] time:[{swRead.ElapsedMilliseconds}]");
                }
            }
            catch (Exception ex)
            {
                logHelper.Error($"PLC2读取bytes发生错误！address:[{address}]", ex);
                return default;
            }
            return result;
        }

        /// <summary>
        /// 读取bool
        /// </summary>
        /// <param name="address"></param>
        /// <returns></returns>
        public static OperateResult<bool> ReadBool2(string address)
        {
            OperateResult<bool> result = new OperateResult<bool>();
            try
            {
                lock (lockerRead)
                {
                    result = MelsecInstance2.ReadBool(address);
                }
            }
            catch (Exception ex)
            {
                logHelper.Error($"PLC读取Bool发生错误！address:[{address}]", ex);
                return default;
            }
            return result;
        }

        /// <summary>
        /// 读取Int16
        /// </summary>
        /// <param name="address"></param>
        /// <returns></returns>
        public static OperateResult<short> ReadInt162(string address)
        {
            OperateResult<short> result = new OperateResult<short>();
            try
            {
                lock (lockerRead)
                {
                    result = MelsecInstance2.ReadInt16(address);
                }
            }
            catch (Exception ex)
            {
                logHelper.Error($"PLC读取Int16发生错误！address:[{address}]", ex);
                return default;
            }
            return result;
        }

        /// <summary>
        /// 读取UInt16
        /// </summary>
        /// <param name="address"></param>
        /// <returns></returns>
        public static OperateResult<ushort> ReadUInt162(string address)
        {
            OperateResult<ushort> result = new OperateResult<ushort>();
            try
            {
                lock (lockerRead)
                {
                    result = MelsecInstance2.ReadUInt16(address);
                }
            }
            catch (Exception ex)
            {
                logHelper.Error($"PLC读取Int16发生错误！address:[{address}]", ex);
                return default;
            }
            return result;
        }

        /// <summary>
        /// 读取Int32
        /// </summary>
        /// <param name="address"></param>
        /// <returns></returns>
        public static OperateResult<int> ReadInt322(string address)
        {
            OperateResult<int> result = new OperateResult<int>();
            try
            {
                lock (lockerRead)
                {
                    result = MelsecInstance2.ReadInt32(address);
                }
            }
            catch (Exception ex)
            {
                logHelper.Error($"PLC读取Int16发生错误！address:[{address}]", ex);
                return default;
            }
            return result;
        }

        /// <summary>
        /// 读取UInt32
        /// </summary>
        /// <param name="address"></param>
        /// <returns></returns>
        public static OperateResult<uint> ReadUInt322(string address)
        {
            OperateResult<uint> result = new OperateResult<uint>();
            try
            {
                lock (lockerRead)
                {
                    result = MelsecInstance2.ReadUInt32(address);
                }
            }
            catch (Exception ex)
            {
                logHelper.Error($"PLC读取Int16发生错误！address:[{address}]", ex);
                return default;
            }
            return result;
        }

        /// <summary>
        /// 读取Int64
        /// </summary>
        /// <param name="address"></param>
        /// <returns></returns>
        public static OperateResult<long> ReadInt642(string address)
        {
            OperateResult<long> result = new OperateResult<long>();
            try
            {
                lock(lockerRead)
                {
                    result = MelsecInstance2.ReadInt64(address);
                }
            }
            catch (Exception ex)
            {
                logHelper.Error($"PLC读取Int16发生错误！address:[{address}]", ex);
                return default;
            }
            return result;
        }

        /// <summary>
        /// 读取UInt64
        /// </summary>
        /// <param name="address"></param>
        /// <returns></returns>
        public static OperateResult<ulong> ReadUInt642(string address)
        {
            OperateResult<ulong> result = new OperateResult<ulong>();
            try
            {
                lock (lockerRead)
                {
                    result = MelsecInstance2.ReadUInt64(address);
                }
            }
            catch (Exception ex)
            {
                logHelper.Error($"PLC读取Int16发生错误！address:[{address}]", ex);
                return default;
            }
            return result;
        }

        /// <summary>
        /// 读取Float
        /// </summary>
        /// <param name="address"></param>
        /// <returns></returns>
        public static OperateResult<float> ReadFloat2(string address)
        {
            OperateResult<float> result = new OperateResult<float>();
            try
            {
                lock (lockerRead)
                {
                    result = MelsecInstance2.ReadFloat(address);
                }
            }
            catch (Exception ex)
            {
                logHelper.Error($"PLC读取Float发生错误！address:[{address}]", ex);
                return default;
            }
            return result;
        }

        /// <summary>
        /// 读取Double
        /// </summary>
        /// <param name="address"></param>
        /// <returns></returns>
        public static OperateResult<double> ReadDouble2(string address)
        {
            OperateResult<double> result = new OperateResult<double>();

            try
            {
                lock (lockerRead)
                {
                    result = MelsecInstance2.ReadDouble(address);
                }
            }
            catch (Exception ex)
            {
                logHelper.Error($"PLC读取Double发生错误！address:[{address}]", ex);
                return default;
            }
            return result;
        }
    }
}