using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using Aitex.Core.RT.Log;
using Aitex.Core.Util;
using MECF.Framework.RT.Core.IoProviders;

namespace MECF.Framework.Simulator.Core.IoProviders
{
    public class PlcBuffer
    {
        public IoType Type;
        public int Offset;
        public int Size;
        public byte[] Buffer;
        public bool[] BoolValue;
        public ushort[] ShortValue;
        public float[] FloatValue;
    }

    public class MCProtocolPlcSimulator
    {
        public event Action<string> NotifyEvent;

        private PeriodicJob _threadSocket;
        private PeriodicJob _threadTimer;

        private Socket _socketServer;

        private string _ip;

 
        private bool _stopFlag;

        protected List<PlcBuffer> _buffers = new List<PlcBuffer>();
 
        private int _port = 6731;
        private int _socketId = 101;
        private int _stationId = 102;

        private byte[] _bufferIn;
        private byte[] _bufferOut;

 
        private MCProtocol.MC_RESPONSE_HEADER _response;
 
        public MCProtocolPlcSimulator(string ip, int port)
        {
            _ip = ip;

            _port = port;

            _response = new MCProtocol.MC_RESPONSE_HEADER()
            {
                ProtocolId = MCProtocol.MC_SUBHEADER_RESPONSE_MESSAGE,
                NetworkId = (byte)_socketId,
                StationId = (byte)_stationId,
                RequestIoNumber = MCProtocol.MC_REQUEST_MODULE_IO_NUMBER,
                RequestStationNumber = MCProtocol.MC_REQUEST_MODULE_STATION_NUMBER,
                ResponseDataLen = 0,
                CompleteCode = (ushort)(MCProtocol.MC_COMPLETE_CODE_SUCCESS  ),
            };

            _bufferOut = new byte[2048];
            _bufferIn = new byte[2048];

            _socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            _threadSocket = new PeriodicJob(50, OnMonitor, "MCProtocolPlcSimulator",true);

            _threadTimer = new PeriodicJob(1000, OnTimer, "MCProtocolPlcSimulatorTimer", true);
        }

        protected virtual bool OnTimer()
        {
            return true;
        }

        private void PerformNotifyEvent(string msg)
        {
            if (NotifyEvent != null)
            {
                NotifyEvent(msg);
            }
        }

        private bool OnMonitor()
        {
            try
            {
                EndPoint ep = new IPEndPoint(IPAddress.Loopback, _port);
                _socketServer.Bind(ep);
                _socketServer.Listen(3);

                Socket s;
                while (!_stopFlag)
                {
                    PerformNotifyEvent("Waiting for connection ...");
                    s = _socketServer.Accept();
                    PerformNotifyEvent("Connected.");
                    
                    s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
                    try
                    {
                        while (!_stopFlag && OnTransmission(s))
                        {
                            PerformNotifyEvent("Waiting for another command ...");
                        }
                        PerformNotifyEvent("receive error buffer content and exit ...");
                    }
                    catch (Exception ex)
                    {
                        LOG.Write(ex);
                        return true;
                    }
                    PerformNotifyEvent("A client disconnected from port " + _port);
                    s = null;
                }
            }
            catch (Exception ex)
            {
                LOG.Write(ex);
                try
                {
                    _socketServer.Close();
                }
                catch (Exception )
                {
                     
                }

                _socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                return true;
            }

            return true;
        }

        public void Start()
        {
            _threadSocket.Start();
        }

        public void Stop()
        {
            _stopFlag = true;
            _threadSocket.Stop();
        }

        protected bool OnTransmission(Socket s)
        {
            int size = s.Receive(_bufferIn, 0, MCProtocol.MC_QHEADER_COMMAND_SIZE, SocketFlags.None);
            if (size < MCProtocol.MC_QHEADER_COMMAND_SIZE)
            {
                return false;
            }

            MCProtocol.MC_COMMAND_HEADER header = MCProtocol.ToStruct<MCProtocol.MC_COMMAND_HEADER>(_bufferIn);
            int dataLength = header.RequestDataLen - MCProtocol.MC_BATCH_COMMAND_SIZE - MCProtocol.JUNK_SIZE;
            if (dataLength < 0)
            {
                return false;
            }

            size = s.Receive(_bufferIn, 0, MCProtocol.MC_BATCH_COMMAND_SIZE, SocketFlags.None);
            if (size < MCProtocol.MC_BATCH_COMMAND_SIZE)
            {
                return false;
            }

            MCProtocol.MC_BATCH_COMMAND cmd = MCProtocol.ToStruct<MCProtocol.MC_BATCH_COMMAND>(_bufferIn);
            int offset = cmd.Reserved * (0xFFFF + 1) + cmd.HeadAddr;
            if (dataLength > 0)
            {
                size = s.Receive(_bufferIn, 0, dataLength, SocketFlags.None);
                if (size < dataLength)
                {
                    return false;
                }
            }
 
            
            byte[] data = null;
            if (cmd.Command == MCProtocol.MC_COMMAND_BATCH_READ)
            {
                
                 data = GetData(offset, cmd.DevicePoints, cmd.SubCommand == MCProtocol.MC_SUBCOMMAND_BIT_UNITS);
            }
            else
            {
                SetData(offset, _bufferIn, dataLength, cmd.SubCommand == MCProtocol.MC_SUBCOMMAND_BIT_UNITS);
            }

            _response.ResponseDataLen = (ushort)(MCProtocol.JUNK_SIZE + (data == null ? 0 : data.Length));

            byte[] response = MCProtocol.Struct2Bytes(_response);
            Array.Copy(response, 0, _bufferOut, 0, response.Length);
            s.Send(_bufferOut, response.Length, SocketFlags.None);
 

            if (data != null)
            {
                Array.Copy(data, 0, _bufferOut, 0, data.Length);

                s.Send(_bufferOut, data.Length, SocketFlags.None);
 
            }
            return true;
        }

 
        protected void SetData(int headAddr, byte[] data, int length, bool isBit)
        {
            IoType ioType = isBit ? IoType.DO : IoType.AO;

            if (ioType == IoType.DO)
            {
                bool[] boolData = MCProtocol.TransByteDataToBoolArray(data, 0, length);

                PlcBuffer buffer = _buffers.Find(x => x.Type == ioType && x.Size == boolData.Length && x.Offset == headAddr);
                if (buffer != null)
                {
                    buffer.BoolValue = boolData;
                }

            }
            else
            {
                ushort[] shortValue = MCProtocol.Byte2Ushort(data, 0, length);
                PlcBuffer buffer = _buffers.Find(x=>x.Type == ioType && x.Offset == headAddr);
                if (buffer != null && shortValue != null && shortValue.Length > 0 && buffer.ShortValue.Length >= headAddr + shortValue.Length) 
                {
                    Array.Copy(shortValue, 0, buffer.ShortValue, headAddr, shortValue.Length);
                }
                 
            }
 
        }
 
        protected void SetDi(int headAddr, int offset, bool value)
        {
            PlcBuffer buffer = _buffers.Find(x =>x.Type == IoType.DI && x.Offset == headAddr );
            if (buffer != null)
            {
                buffer.BoolValue[offset] = value;
            }
        }

        protected void SetAi(int headAddr, int offset, ushort value)
        {
            PlcBuffer buffer = _buffers.Find(x => x.Type == IoType.AI && x.Offset == headAddr  );
            if (buffer != null)
            {
                buffer.ShortValue[offset] = value;
            }
        }

        protected virtual byte[] GetData(int headAddr, ushort length, bool isBit)
        {
            IoType ioType = isBit ? IoType.DI : IoType.AI;
            if(!isBit)
            {
                var tmp = _buffers.Find(x => x.Type == ioType && x.Offset == headAddr);
                if(tmp != null && tmp.ShortValue != null)
                {
                    var remain = tmp.ShortValue.Length - headAddr;
                    var size = remain >= length ? length : (remain > 0 ? remain : 0);
                    if (size > 0)
                    {
                        ushort[] bf = new ushort[size];
                        Array.Copy(tmp.ShortValue, headAddr, bf, 0, bf.Length);
                        return MCProtocol.Ushort2Byte(bf);
                    }
                    else
                        return null;
                }

            }

            PlcBuffer buffer = _buffers.Find(x => x.Type == ioType && x.Offset == headAddr);

            if (buffer == null)
            {
                return null;
            }

            byte[] result = null;
            if (ioType == IoType.DI)
            {
                bool[] value = buffer.BoolValue;
                int size = (value.Length + 1) / 2;
                 result = new byte[size];

                for (int i = 0; i < size; i++)
                {
                    if (value[i * 2 + 0])
                        result[i] += 0x10;
                    if ((i * 2 + 1) < value.Length)
                    {
                        if (value[i * 2 + 1])
                            result[i] += 0x01;
                    }
                }

                //result = MCProtocol.TransBoolArrayToByteData(buffer.BoolValue);
            }
            else
            {
                result = MCProtocol.Ushort2Byte(buffer.ShortValue);
            }

            return result;
        }
 


    }
}
