using PlcSiemens.Communication;
using PlcSiemens.Protocol.Common;
using PlcSiemens.Protocol.ReadData;
using PlcSiemens.Protocol.Szl;
using PlcSiemens.ProtocolHandle;
using System.Text;

namespace PlcSiemens.O

{
    public class SimenssPlc
    {
        public ConnectionType ConnectionType { get; private set; }
        public ushort SrcRef { get; set; }
        public ushort DstRef { get; set; }
        public ushort SrcTSap { get; private set; }
        public ushort DstTSap { get; private set; }
        public ushort PduRequest { get; set; }
        public int RecvTimeout { get; set; }
        public int RemotePort { get; set; }
        public int IsoPduSize { get; set; }

        // ReSharper disable once InconsistentNaming
        public string IP { get; private set; }

        public bool Connected
        {
            get
            {
                if (_socket == null)
                    return false;
                return _socket.Connected;
            }
        }

        private Action<PlcSiemens.O.MessageEvent> _onMessageEvent;

        private readonly IsoSocket _socket;

        public SimenssPlc()
        {
            _socket = new IsoSocket();
            PduRequest = 480;
            IsoPduSize = 1024;
            RecvTimeout = 3000;
            RemotePort = 102;
            SrcRef = 0x0001; // RFC0983 states that SrcRef and DetRef should be 0
                             // and, in any case, they are ignored.
                             // S7 instead requires a number != 0
                             // Libnodave uses 0x0100
                             // S7Manager uses 0x0D00
                             // TIA Portal V12 uses 0x1D00
                             // WinCC     uses 0x0300
                             // Seems that every non zero value is good enough...
            DstRef = 0x0000;
            SrcTSap = 0x0100;
            DstTSap = 0x0000; // It's filled by connection functions
            ConnectionType = ConnectionType.PG;
        }

        public SimenssPlc(string ip, int rack, int slot)
        {
            _socket = new IsoSocket();
            PduRequest = 480 * 2;
            IsoPduSize = 1024;
            RecvTimeout = 3000;
            RemotePort = 102;
            SrcRef = 0x0001; // RFC0983 states that SrcRef and DetRef should be 0
                             // and, in any case, they are ignored.
                             // S7 instead requires a number != 0
                             // Libnodave uses 0x0100
                             // S7Manager uses 0x0D00
                             // TIA Portal V12 uses 0x1D00
                             // WinCC     uses 0x0300
                             // Seems that every non zero value is good enough...
            DstRef = 0x0000;
            SrcTSap = 0x0100;
            DstTSap = 0x0000; // It's filled by connection functions
            ConnectionType = ConnectionType.PG;

            var remoteTsap = (ushort)(((ushort)ConnectionType << 8) + (rack * 0x20) + slot);
            SetConnectionParams(ip, SrcTSap, remoteTsap);
        }

        #region 连接

        public void RegisterMessageEvent(Action<MessageEvent> messageAction)
        {
            if (messageAction == null) throw new ArgumentNullException("messageAction");
            _onMessageEvent = messageAction;
        }

        private void OnMessage(string method, string message)
        {
            if (_onMessageEvent != null)
                _onMessageEvent.Invoke(new MessageEvent(method, message));
        }

        public void SetConnectionType(ConnectionType connectionType)
        {
            ConnectionType = connectionType;
        }

        public void SetConnectionParams(string ip, ushort localTsap, ushort remoteTsap)
        {
            SrcTSap = localTsap;
            DstTSap = remoteTsap;
            IP = ip;
        }

        public bool ConnectTo(string ip, int rack, int slot)
        {
            var remoteTsap = (ushort)(((ushort)ConnectionType << 8) + (rack * 0x20) + slot);
            SetConnectionParams(ip, SrcTSap, remoteTsap);
            return Connect();
        }

        //暂时用系统SOCKET测试
        private int _connecting;//连接中

        private int _processing;//处理中
        private string _processMehtod;

        public bool Connect()
        {
            try
            {
                if (Interlocked.CompareExchange(ref _connecting, 1, 0) != 0)
                    return false;
                OnMessage("Connect", "正在连接");
                if (!_socket.Connect(IP, 102))
                    return false;

                IsoControlPduHandle pduHandle = new IsoControlPduHandle()
                {
                    IsoPduSize = IsoPduSize,
                    DstRef = DstRef,
                    DstTSap = DstTSap,
                    SrcRef = SrcRef,
                    SrcTSap = SrcTSap
                };
                pduHandle.Handle(_socket);
                if (pduHandle.ResultCode == ResultCode.OK)
                {
                    NegotiateHandle negotiateHandle = new NegotiateHandle() { PduRequest = PduRequest };
                    negotiateHandle.Handle(_socket);
                    if (negotiateHandle.ResultCode == ResultCode.OK)
                    {
                        PduRequest = negotiateHandle.Responses[0].Params.PduLength;
                        return true;
                    }
                    else
                    {
                        OnMessage("Connect", "PDU握手失败");
                    }
                }
                else
                {
                    OnMessage("Connect", "PDU握手失败");
                }
            }
            catch (Exception ex)
            {
                OnMessage("Connect", ex.Message);
            }
            finally
            {
                Interlocked.CompareExchange(ref _connecting, 0, 1);
            }
            return false;
        }

        #endregion 连接

        public bool GetLock(string method)
        {
            if (Interlocked.CompareExchange(ref _processing, 1, 0) != 0)
            {
                OnMessage(method, "执行失败,{_processMehtod}处理正在执行中");
                return false;
            }
            _processMehtod = method;
            return true;
        }

        public bool ReleaseLock()
        {
            _processMehtod = "";
            return Interlocked.CompareExchange(ref _processing, 0, 1) == 0;
        }

        #region [System Info functions]

        private SzlResponse ReadSzlHelper(SzlCmd szlcmd)
        {
            SzlHandle handle = new SzlHandle { SzlCmd = szlcmd };
            handle.Handle(_socket);
            return handle.SzlResponseFirst;
        }

        public SzlResponse ReadSzl(SzlCmd szlcmd)
        {
            if (!GetLock("ReadSzl"))
            {
                return null;
            }
            try
            {
                SzlHandle handle = new SzlHandle { SzlCmd = szlcmd };
                handle.Handle(_socket);

                OnMessage("ReadSzl", "{handle.ResultCode}");
                return handle.SzlResponseFirst;
            }
            catch (Exception ex)
            {
                OnMessage("ReadSzl", ex.Message);
            }
            finally
            {
                ReleaseLock();
            }
            return null;
        }

        public OrderInfo GetOrderCode()
        {
            OrderInfo orderCode = new OrderInfo();
            if (!GetLock("GetOrderCode"))
            {
                return orderCode;
            }
            try
            {
                var aaaa = ReadSzlHelper(SzlInfo.OrderCode);

                OnMessage("GetOrderCode", aaaa.Params.Err.ToString());
                if (aaaa.Params.Err != ResultCode.OK)
                    return orderCode;
                orderCode.Code = Encoding.UTF8.GetString(aaaa.Data.Data, 2, 20);
                orderCode.V1 = aaaa.Data.Data[aaaa.Data.Data.Length - 3];
                orderCode.V2 = aaaa.Data.Data[aaaa.Data.Data.Length - 2];
                orderCode.V3 = aaaa.Data.Data[aaaa.Data.Data.Length - 1];
                return orderCode;
            }
            catch (Exception ex)
            {
                OnMessage("GetOrderCode", ex.Message);
                return orderCode;
            }
            finally
            {
                ReleaseLock();
            }
        }

        public CpuInfo GetCpuInfo()
        {
            CpuInfo cpuInfo = new CpuInfo();
            if (!GetLock("GetCpuInfo"))
                return cpuInfo;
            try
            {
                var aaaa = ReadSzlHelper(SzlInfo.CpuInfo);

                OnMessage("GetCpuInfo", aaaa.Params.Err.ToString());
                if (aaaa.Params.Err != ResultCode.OK)
                    return cpuInfo;
                cpuInfo.ModuleTypeName = Encoding.UTF8.GetString(aaaa.Data.Data, 172, 32).TrimEnd('\0');
                cpuInfo.SerialNumber = Encoding.UTF8.GetString(aaaa.Data.Data, 138, 24).TrimEnd('\0');
                cpuInfo.AsName = Encoding.UTF8.GetString(aaaa.Data.Data, 2, 24).TrimEnd('\0');
                cpuInfo.Copyright = Encoding.UTF8.GetString(aaaa.Data.Data, 104, 26).TrimEnd('\0');
                cpuInfo.ModuleName = Encoding.UTF8.GetString(aaaa.Data.Data, 36, 24).TrimEnd('\0');
            }
            catch (Exception ex)
            {
                OnMessage("GetCpuInfo", ex.Message);
            }
            finally
            {
                ReleaseLock();
            }
            return cpuInfo;
        }

        public CpInfo GetCpInfo()
        {
            CpInfo info = new CpInfo();
            if (!GetLock("GetCpuInfo"))
                return info;

            try
            {
                var aaaa = ReadSzlHelper(SzlInfo.CpInfo);
                OnMessage("GetCpInfo", aaaa.Params.Err.ToString());
                if (aaaa.Params.Err != ResultCode.OK)
                    return info;
                info.MaxPduLengt = DWordAt(aaaa.Data.Data, 2, 2);
                //aaaa.ResDataFirst.Data[2] * 256 + aaaa.ResDataFirst.Data[3];
                info.MaxConnections = DWordAt(aaaa.Data.Data, 4, 2);
                // aaaa.ResDataFirst.Data[4] * 256 + aaaa.ResDataFirst.Data[5];
                info.MaxMpiRate = DWordAt(aaaa.Data.Data, 6, 4);
                //aaaa.ResDataFirst.Data[6] * 256 + aaaa.ResDataFirst.Data[7];
                info.MaxBusRate = DWordAt(aaaa.Data.Data, 10, 4);
                //aaaa.ResDataFirst.Data[10] * 256 + aaaa.ResDataFirst.Data[7];
            }
            catch (Exception ex)
            {
                OnMessage("GetCpInfo", ex.Message);
            }
            finally
            {
                ReleaseLock();
            }
            return info;
        }

        #endregion [System Info functions]

        private int DWordAt(byte[] buffer, int index, int length)
        {
            int value = 0;
            for (int i = 0; i < length; i++)
            {
                value = (value << 8) + buffer[index + i];
            }

            return value;
        }

        #region [Date/Time functions]

        public DateTime GetS7DateTime()
        {
            if (!GetLock("GetS7DateTime"))
                return DateTime.MinValue;
            try
            {
                GetDateTimeHandle handle = new GetDateTimeHandle();
                handle.Handle(_socket);
                OnMessage("GetS7DateTime", handle.ResultCode.ToString());
                if (handle.ResultCode == ResultCode.OK)
                {
                    return handle.Response.GetDateTime();
                }
                else
                {
                    return DateTime.MinValue;
                }
            }
            catch (Exception ex)
            {
                OnMessage("GetS7DateTime", ex.Message);
                return DateTime.MinValue;
            }
            finally
            {
                ReleaseLock();
            }
        }

        public bool SetS7DateTime(DateTime curDateTime)
        {
            if (!GetLock("SetS7DateTime"))
                return false;

            try
            {
                SetDateTimeHandle handle = new SetDateTimeHandle { DateTime = curDateTime };
                handle.Handle(_socket);

                OnMessage("SetS7DateTime", handle.ResultCode.ToString());
                return handle.ResultCode == ResultCode.OKFF;
            }
            catch (Exception ex)
            {
                OnMessage("SetS7DateTime", ex.Message);
                return false;
            }
            finally
            {
                ReleaseLock();
            }
        }

        #endregion [Date/Time functions]

        #region [Control functions]

        public S7CpuStatus GetPlcStatus()
        {
            S7CpuStatus status = S7CpuStatus.Unknown;
            if (!GetLock("GetPlcStatus"))
            {
                return status;
            }
            try
            {
                SzlResponse first = ReadSzlHelper(SzlInfo.PlcStatus);
                OnMessage("GetPlcStatus", first.Params.Err.ToString());

                if (first.Params.Err == ResultCode.OK)
                {
                    status = (S7CpuStatus)first.Data.Data[3];
                    if (status != S7CpuStatus.Run)
                        status = S7CpuStatus.Stop;
                }
                return status;
            }
            catch (Exception ex)
            {
                OnMessage("GetPlcStatus", ex.Message);
                return status;
            }
            finally
            {
                ReleaseLock();
            }
        }

        public bool PlcStop()
        {
            if (!GetLock("PlcStop"))
                return false;

            try
            {
                var handle = new PlcStopHandle();
                handle.Handle(_socket);

                OnMessage("PlcStop", handle.ResultCode.ToString());
                return handle.ResultCode == ResultCode.OK;
            }
            catch (Exception ex)
            {
                OnMessage("PlcStop", ex.Message);
                return false;
            }
            finally
            {
                ReleaseLock();
            }
        }

        public bool PlcHotStart()
        {
            if (!GetLock("PlcHotStart"))
                return false;

            try
            {
                var handle = new PlcHotStartHandle();
                handle.Handle(_socket);

                OnMessage("PlcHotStart", handle.ResultCode.ToString());
                return handle.ResultCode == ResultCode.OK;
            }
            catch (Exception ex)
            {
                OnMessage("PlcHotStart", ex.Message);
                return false;
            }
            finally
            {
                ReleaseLock();
            }
        }

        public bool PlcColdStart()
        {
            if (!GetLock("PlcColdStart"))
                return false;

            try
            {
                var handle = new PlcColdStartHandle();
                handle.Handle(_socket);

                OnMessage("PlcColdStart", handle.ResultCode.ToString());
                return handle.ResultCode == ResultCode.OK;
            }
            catch (Exception ex)
            {
                OnMessage("PlcColdStart", ex.Message);
                return false;
            }
            finally
            {
                ReleaseLock();
            }
        }

        public bool CopyRamToRom()
        {
            if (!GetLock("CopyRamToRom"))
                return false;

            try
            {
                var handle = new CopyRamToRomHandle();
                handle.Handle(_socket);

                OnMessage("CopyRamToRom", handle.ResultCode.ToString());
                return handle.ResultCode == ResultCode.OK;
            }
            catch (Exception ex)
            {
                OnMessage("CopyRamToRom", ex.Message);
                return false;
            }
            finally
            {
                ReleaseLock();
            }
        }

        public bool Compress()
        {
            if (!GetLock("Compress"))
                return false;

            try
            {
                var handle = new CompressHandle();
                handle.Handle(_socket);

                OnMessage("Compress", handle.Response.Header.Error.ToString());
                return handle.ResultCode == ResultCode.OK;
            }
            catch (Exception ex)
            {
                OnMessage("Compress", ex.Message);
                return false;
            }
            finally
            {
                ReleaseLock();
            }
        }

        #endregion [Control functions]

        #region [Security functions]

        public ProtectionInfo GetProtection()
        {
            ProtectionInfo info = new ProtectionInfo();
            if (!GetLock("GetProtection"))
            {
                return info;
            }
            try
            {
                var sss = ReadSzlHelper(SzlInfo.Protection);

                OnMessage("GetProtection", sss.Data.Ret.ToString());
                if (sss.Params.Err != ResultCode.OK) return info;
                info.SchSchal = (RWLevel)DWordAt(sss.Data.Data, 2, 2);
                info.SchPar = (ProtectionLevel)DWordAt(sss.Data.Data, 4, 2);
                info.SchRel = (CpuLevel)DWordAt(sss.Data.Data, 6, 2);
                info.BartSch = (RunStatus)DWordAt(sss.Data.Data, 8, 2);
                info.AnlSch = (StartupSwitch)DWordAt(sss.Data.Data, 10, 2);
                return info;
            }
            catch (Exception ex)
            {
                OnMessage("GetProtection", ex.Message);
                return info;
            }
            finally
            {
                ReleaseLock();
            }
        }

        public bool SetPwd(string password)
        {
            if (!GetLock("SetPwd")) return false;

            try
            {
                var handle = new SetPasswordHandle { Password = password };
                handle.Handle(_socket);

                OnMessage("SetPwd", handle.Response.Params.Err.ToString());
                return handle.ResultCode == ResultCode.OK;
            }
            catch (Exception ex)
            {
                OnMessage("SetPwd", ex.Message);
                return false;
            }
            finally
            {
                ReleaseLock();
            }
        }

        public bool ClearPwd()
        {
            if (!GetLock("ClearPwd")) return false;

            try
            {
                var handle = new SetPasswordHandle();
                handle.Handle(_socket);

                OnMessage("ClearPwd", handle.ResultCode.ToString());
                return handle.ResultCode == ResultCode.OK;
            }
            catch (Exception ex)
            {
                OnMessage("ClearPwd", ex.Message);
                return false;
            }
            finally
            {
                ReleaseLock();
            }
        }

        #endregion [Security functions]

        #region [Blocks]

        public ListBlocksHandle ListBlocks()
        {
            if (!GetLock("ListBlocks")) return null;
            try
            {
                var handle = new ListBlocksHandle();
                handle.Handle(_socket);
                OnMessage("ListBlocks", handle.ResultCode.ToString());
                return handle;
            }
            catch (Exception ex)
            {
                OnMessage("ListBlocks", ex.Message);
                return null;
            }
            finally
            {
                ReleaseLock();
            }
        }

        public DataBlockOfTypeHandle ListBlocksOfType(BlockType blockType)
        {
            if (!GetLock("ListBlocksOfType")) return null;
            try
            {
                var handle = new DataBlockOfTypeHandle();
                handle.BlockType = blockType;
                handle.Handle(_socket);
                OnMessage("ListBlocksOfType", handle.ResultCode.ToString());
                return handle;
            }
            catch (Exception ex)
            {
                OnMessage("ListBlocksOfType", ex.Message);
                return null;
            }
            finally
            {
                ReleaseLock();
            }
        }

        public DataBlockInfoHandle BlockInfoGet(ushort db, BlockType blockType)
        {
            if (!GetLock("BlockInfoHandle")) return null;
            try
            {
                var handle = new DataBlockInfoHandle();
                handle.BlockType = blockType;
                handle.DB = db;
                handle.Handle(_socket);
                OnMessage("BlockInfoHandle", handle.ResultCode.ToString());
                return handle;
            }
            catch (Exception ex)
            {
                OnMessage("BlockInfoHandle", ex.Message);
                return null;
            }
            finally
            {
                ReleaseLock();
            }
        }

        #endregion [Blocks]

        private object readlock = new object();

        public DataItem ReadArea(AreaType area, ushort db, ushort start, ushort amount, DataType dataType)
        {
            lock (readlock)
            {
                if (!GetLock("ReadArea"))
                {
                    return null;
                }
                else
                {
                }
                try
                {
                    var handle = new ReadHandle
                    {
                        PduLength = PduRequest,
                        DataItem = new DataItem()
                        {
                            AreaType = area,
                            Db = db,
                            Start = start,
                            Length = amount,
                            DataType = dataType
                        }
                    };
                    handle.Handle(_socket);
                    OnMessage("ReadArea", handle.ResultCode.ToString());
                    if (handle.ResultCode != ResultCode.OK)
                    {
                        throw new Exception(handle.ResultCode.ToString());
                    }
                    return handle.DataItem;
                }
                catch (Exception ex)
                {
                    OnMessage("ReadArea", ex.Message);
                }
                finally
                {
                    ReleaseLock();
                }
                return null;
            }
        }

        public ReadMultiHandle ReadMulti(List<DataItem> items)
        {
            if (!GetLock("ReadMulti")) return null;
            try
            {
                var handle = new ReadMultiHandle { DataItem = items };

                handle.Handle(_socket);
                OnMessage("ReadMulti", handle.ResultCode.ToString());

                return handle;
            }
            catch (Exception ex)
            {
                OnMessage("ReadMulti", ex.Message);
                return null;
            }
            finally

            {
                ReleaseLock();
            }
        }

        public bool WriteArea(AreaType area, ushort db, ushort start, ushort amount, DataType dataType, byte[] writeData)
        {
            if (IP == "192.168.0.120" && db == 520)
            {
                if (start % 36 == 16)
                {
                    if (writeData.Max() == 0)
                    {
                    }
                }
                else if (start % 36 == 0)
                {
                    if (writeData.Length > 16)
                    {
                        if (writeData.Skip(16).Take(2).Max() == 0)
                        {
                        }
                    }
                    else
                    {
                    }
                }
            }

            if (!GetLock("WriteArea"))
                return false;
            try
            {
                var handle = new WriteHandle()
                {
                    PduLength = PduRequest,
                    DataItem = new DataItem()
                    {
                        AreaType = area,
                        Db = db,
                        Start = start,
                        Length = amount,
                        DataType = dataType,
                        Data = writeData
                    }
                };
                OnMessage("WriteArea", handle.ResultCode.ToString());
                handle.Handle(_socket);
                return handle.ResultCode == ResultCode.OK || handle.ResultCode == ResultCode.OKFF;
            }
            catch (Exception ex)
            {
                OnMessage("WriteArea", ex.Message);
                return false;
            }
            finally
            {
                ReleaseLock();
            }
        }

        public WriteMultiHandle WriteMulti(List<DataItem> items)
        {
            if (!GetLock("WriteMulti")) return null;

            try
            {
                var handle = new WriteMultiHandle { DataItem = items };

                handle.Handle(_socket);
                OnMessage("WriteMulti", handle.ResultCode.ToString());

                return handle;
            }
            catch (Exception ex)
            {
                OnMessage("WriteMulti", ex.Message);
                return null;
            }
            finally
            {
                ReleaseLock();
            }
        }

        // ReSharper disable once InconsistentNaming
        public DataItem DBGet(ushort db)
        {
            //if (!GetLock("DBGet")) return null;
            try
            {
                var handle = new DataBlockInfoHandle();
                handle.BlockType = BlockType.DB;
                handle.DB = db;
                handle.Handle(_socket);
                if (handle.ResultCode == ResultCode.OK)
                {
                    var res = ReadArea(AreaType.DB, handle.DB, 0, handle.Response.Data.Mc7Len, DataType.Byte);
                    if (res.Err == ResultCode.OKFF)
                    {
                        return res;
                    }
                    OnMessage("DBGet", "ReadArea:{handle.ResultCode}");
                }
                else
                    OnMessage("DBGet", "RequestBlock:{handle.ResultCode}");
            }
            catch (Exception ex)
            {
                OnMessage("DBGet", ex.Message);
            }
            //finally
            //{
            //    ReleaseLock();
            //}
            return null;
        }

        // ReSharper disable once InconsistentNaming
        public bool DBFill(ushort db, byte value)
        {
            try
            {
                var handle = new DataBlockInfoHandle();
                handle.BlockType = BlockType.DB;
                handle.DB = db;
                handle.Handle(_socket);
                if (handle.ResultCode == ResultCode.OK)
                {
                    byte[] datas = new byte[handle.Response.Data.Mc7Len];
                    for (int i = 0; i < handle.Response.Data.Mc7Len; i++)
                    {
                        datas[i] = value;
                    }
                    return WriteArea(AreaType.DB, handle.DB, 0, handle.Response.Data.Mc7Len, DataType.Byte, datas);
                }
                else
                    OnMessage("DBFill", "RequestBlock:{handle.ResultCode}");

                return false;
            }
            catch (Exception ex)
            {
                OnMessage("DBFill", ex.Message);
                return false;
            }
            //finally
            //{
            //    ReleaseLock();
            //}
        }

        public bool UpLoad(BlockType blockType, ushort db, bool isFull)
        {
            UploadStartHandle handle = new UploadStartHandle();
            handle.BlockType = blockType;
            handle.BlockNum = db;

            handle.Handle(_socket);
            if (handle.ResultCode != ResultCode.OK)
                return false;

            UploadHandle handle1 = new UploadHandle();
            handle1.UploadID = handle.Response.Params.UploadID;
            handle1.Handle(_socket);

            return false;
        }
    }
}
