using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Net;
using System.Threading;
using MECF.Framework.RT.Core;
using MECF.Framework.RT.Core.IoProviders.Siemens;
using MECF.Framework.RT.Core.IoProviders.Siemens.IMessage;
using MECF.Framework.RT.Core.IoProviders.Siemens.Transfer;
using MECF.Framework.RT.Core.ThreadLock;
using MECF.Framework.RT.Core.IoProviders.Siemens.Net.StateOne;

namespace MECF.Framework.RT.Core.IoProviders.Siemens.Net.NetworkBase
{
    /// <summary>
    /// 支持长连接，短连接两个模式的通用客户端基类 ->
    /// Universal client base class that supports long connections and short connections to two modes
    /// </summary>
    /// <example>
    /// 无，请使用继承类实例化，然后进行数据交互。
    /// </example>
    public class NetworkDoubleBase<TNetMessage, TTransform> : NetworkBase where TNetMessage : INetMessage, new() where TTransform : IByteTransform, new()
    {
        #region Constructor

        /// <summary>
        /// 默认的无参构造函数 -> Default no-parameter constructor
        /// </summary>
        public NetworkDoubleBase( )
        {
            ByteTransform     = new TTransform( );                                           // 实例化变换类的对象
            InteractiveLock   = new SimpleHybirdLock( );                                     // 实例化数据访问锁
            connectionId      = SoftBasic.GetUniqueStringByGuidAndRandom( );  // 设备的唯一的编号
        }

        #endregion

        #region Private Member
        
        private TTransform byteTransform;                // 数据变换的接口
        private string ipAddress = "127.0.0.1";          // 连接的IP地址
        private int port = 10000;                        // 端口号
        private int connectTimeOut = 10000;              // 连接超时时间设置
        private int receiveTimeOut = 10000;              // 数据接收的超时时间
        private bool isPersistentConn = false;           // 是否处于长连接的状态
        private SimpleHybirdLock InteractiveLock;        // 一次正常的交互的互斥锁
        private bool IsSocketError = false;              // 指示长连接的套接字是否处于错误的状态
        private bool isUseSpecifiedSocket = false;       // 指示是否使用指定的网络套接字访问数据
        private string connectionId = string.Empty;      // 当前连接

        #endregion

        #region Public Member

        /// <summary>
        /// 当前客户端的数据变换机制，当你需要从字节数据转换类型数据的时候需要。->
        /// The current client's data transformation mechanism is required when you need to convert type data from byte data.
        /// </summary>
        /// <example>
        /// 主要是用来转换数据类型的，下面仅仅演示了2个方法，其他的类型转换，类似处理。
        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Core\NetworkDoubleBase.cs" region="ByteTransform" title="ByteTransform示例" />
        /// </example>
        public TTransform ByteTransform
        {
            get { return byteTransform; }
            set { byteTransform = value; }
        }

        /// <summary>
        /// 获取或设置连接的超时时间，单位是毫秒 -> Gets or sets the timeout for the connection, in milliseconds
        /// </summary>
        /// <example>
        /// 设置1秒的超时的示例
        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Core\NetworkDoubleBase.cs" region="ConnectTimeOutExample" title="ConnectTimeOut示例" />
        /// </example>
        /// <remarks>
        /// 不适用于异形模式的连接。
        /// </remarks>
        public int ConnectTimeOut
        {
            get{return connectTimeOut;}
            set { if (value >= 0) connectTimeOut = value; }
        }

        /// <summary>
        /// 获取或设置接收服务器反馈的时间，如果为负数，则不接收反馈 -> 
        /// Gets or sets the time to receive server feedback, and if it is a negative number, does not receive feedback
        /// </summary>
        /// <example>
        /// 设置1秒的接收超时的示例
        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Core\NetworkDoubleBase.cs" region="ReceiveTimeOutExample" title="ReceiveTimeOut示例" />
        /// </example>
        /// <remarks>
        /// 超时的通常原因是服务器端没有配置好，导致访问失败，为了不卡死软件，所以有了这个超时的属性。
        /// </remarks>
        public int ReceiveTimeOut
        {
            get { return receiveTimeOut; }
            set { receiveTimeOut = value; }
        }

        /// <summary>
        /// 获取或是设置服务器的IP地址
        /// </summary>
        /// <remarks>
        /// 最好实在初始化的时候进行指定，当使用短连接的时候，支持动态更改，切换；当使用长连接后，无法动态更改
        /// </remarks>
        /// <example>
        /// 以下举例modbus-tcp的短连接及动态更改ip地址的示例
        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Core\NetworkDoubleBase.cs" region="IpAddressExample" title="IpAddress示例" />
        /// </example>
        public virtual string IpAddress
        {
            get
            {
                return ipAddress;
            }
            set
            {
                if (!string.IsNullOrEmpty( value ))
                {
                    if (!IPAddress.TryParse( value, out IPAddress address ))
                    {
                        throw new Exception( StringResources.Language.IpAddresError );
                    }
                    ipAddress = value;
                }
                else
                {
                    ipAddress = "127.0.0.1";
                }
            }
        }

        /// <summary>
        /// 获取或设置服务器的端口号
        /// </summary>
        /// <remarks>
        /// 最好实在初始化的时候进行指定，当使用短连接的时候，支持动态更改，切换；当使用长连接后，无法动态更改
        /// </remarks>
        /// <example>
        /// 动态更改请参照IpAddress属性的更改。
        /// </example>
        public virtual int Port
        {
            get
            {
                return port;
            }
            set
            {
                port = value;
            }
        }

        /// <summary>
        /// 当前连接的唯一ID号，默认为长度20的guid码加随机数组成，方便列表管理，也可以自己指定
        /// </summary>
        /// <remarks>
        /// Current Connection ID, conclude guid and random data, also, you can spcified
        /// </remarks>
        public string ConnectionId
        {
            get { return connectionId; }
            set { connectionId = value; }
        }


        /// <summary>
        /// 当前的异形连接对象，如果设置了异形连接的话
        /// </summary>
        /// <remarks>
        /// 具体的使用方法请参照Demo项目中的异形modbus实现。
        /// </remarks>
        public AlienSession AlienSession { get; set; }


        #endregion

        #region Public Method

        /// <summary>
        /// 在读取数据之前可以调用本方法将客户端设置为长连接模式，相当于跳过了ConnectServer的结果验证，对异形客户端无效
        /// </summary>
        /// <example>
        /// 以下的方式演示了另一种长连接的机制
        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Core\NetworkDoubleBase.cs" region="SetPersistentConnectionExample" title="SetPersistentConnection示例" />
        /// </example>
        public void SetPersistentConnection( )
        {
            isPersistentConn = true;
        }

        #endregion

        #region Connect Close

        /// <summary>
        /// 切换短连接模式到长连接模式，后面的每次请求都共享一个通道
        /// </summary>
        /// <returns>返回连接结果，如果失败的话（也即IsSuccess为False），包含失败信息</returns>
        /// <example>
        ///   简单的连接示例，调用该方法后，连接设备，创建一个长连接的对象，后续的读写操作均公用一个连接对象。
        ///   <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Core\NetworkDoubleBase.cs" region="Connect1" title="连接设备" />
        ///   如果想知道是否连接成功，请参照下面的代码。
        ///   <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Core\NetworkDoubleBase.cs" region="Connect2" title="判断连接结果" />
        /// </example> 
        public OperateResult ConnectServer( )
        {
            isPersistentConn = true;
            OperateResult result = new OperateResult( );

            // 重新连接之前，先将旧的数据进行清空
            CoreSocket?.Close( );

            OperateResult<Socket> rSocket = CreateSocketAndInitialication( );

            if (!rSocket.IsSuccess)
            {
                IsSocketError = true;
                rSocket.Content = null;                 
                result.Message = rSocket.Message;
            }
            else
            {
                CoreSocket = rSocket.Content;
                result.IsSuccess = true;
            }

            return result;
        }


        /// <summary>
        /// 使用指定的套接字创建异形客户端
        /// </summary>
        /// <param name="session">异形客户端对象，查看<seealso cref="NetworkAlienClient"/>类型创建的客户端</param>
        /// <returns>通常都为成功</returns>
        /// <example>
        ///   简单的创建示例。
        ///   <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Core\NetworkDoubleBase.cs" region="AlienConnect1" title="连接设备" />
        ///   如果想知道是否创建成功。通常都是成功。
        ///   <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Core\NetworkDoubleBase.cs" region="AlienConnect2" title="判断连接结果" />
        /// </example> 
        /// <remarks>
        /// 不能和之前的长连接和短连接混用，详细参考 Demo程序 
        /// </remarks>
        public OperateResult ConnectServer( AlienSession session )
        {
            isPersistentConn = true;
            isUseSpecifiedSocket = true;


            if (session != null)
            {
                AlienSession?.Socket?.Close( );

                if (string.IsNullOrEmpty( ConnectionId ))
                {
                    ConnectionId = session.DTU;
                }

                if (ConnectionId == session.DTU)
                {
                    CoreSocket = session.Socket;
                    IsSocketError = false;
                    AlienSession = session;
                    return InitializationOnConnect( session.Socket );
                }
                else
                {
                    IsSocketError = true;
                    return new OperateResult( );
                }
            }
            else
            {
                IsSocketError = true;
                return new OperateResult( );
            }
        }


        /// <summary>
        /// 在长连接模式下，断开服务器的连接，并切换到短连接模式
        /// </summary>
        /// <returns>关闭连接，不需要查看IsSuccess属性查看</returns>
        /// <example>
        /// 直接关闭连接即可，基本上是不需要进行成功的判定
        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Core\NetworkDoubleBase.cs" region="ConnectCloseExample" title="关闭连接结果" />
        /// </example>
        public OperateResult ConnectClose( )
        {
            OperateResult result = new OperateResult( );
            isPersistentConn = false;

            InteractiveLock.Enter( );
            // 额外操作
            result = ExtraOnDisconnect( CoreSocket );
            // 关闭信息
            CoreSocket?.Close( );
            CoreSocket = null;
            InteractiveLock.Leave( );
            
            return result;
        }

        #endregion

        #region Initialization And Extra

        /// <summary>
        /// 连接上服务器后需要进行的初始化操作
        /// </summary>
        /// <param name="socket">网络套接字</param>
        /// <returns>是否初始化成功，依据具体的协议进行重写</returns>
        /// <example>
        /// 有些协议不需要握手信号，比如三菱的MC协议，Modbus协议，西门子和欧姆龙就存在握手信息，此处的例子是继承本类后重写的西门子的协议示例
        /// <code lang="cs" source="HslCommunication_Net45\Profinet\Siemens\SiemensS7Net.cs" region="NetworkDoubleBase Override" title="西门子重连示例" />
        /// </example>
        protected virtual OperateResult InitializationOnConnect( Socket socket )
        {
            return OperateResult.CreateSuccessResult( );
        }

        /// <summary>
        /// 在将要和服务器进行断开的情况下额外的操作，需要根据对应协议进行重写
        /// </summary>
        /// <param name="socket">网络套接字</param>
        /// <example>
        /// 目前暂无相关的示例，组件支持的协议都不用实现这个方法。
        /// </example>
        /// <returns>当断开连接时额外的操作结果</returns>
        protected virtual OperateResult ExtraOnDisconnect( Socket socket )
        {
            return OperateResult.CreateSuccessResult( );
        }

        #endregion
        
        #region Core Communication

        /***************************************************************************************
         * 
         *    主要的数据交互分为4步
         *    1. 连接服务器，或是获取到旧的使用的网络信息
         *    2. 发送数据信息
         *    3. 接收反馈的数据信息
         *    4. 关闭网络连接，如果是短连接的话
         * 
         **************************************************************************************/

        /// <summary>
        /// 获取本次操作的可用的网络套接字
        /// </summary>
        /// <returns>是否成功，如果成功，使用这个套接字</returns>
        private OperateResult<Socket> GetAvailableSocket( )
        {
            if (isPersistentConn)
            {
                // 如果是异形模式
                if (isUseSpecifiedSocket)
                {
                    if(IsSocketError)
                    {
                        return new OperateResult<Socket>( StringResources.Language.ConnectionIsNotAvailable );
                    }
                    else
                    {
                        return OperateResult.CreateSuccessResult( CoreSocket );
                    }
                }
                else
                {
                    // 长连接模式
                    if (IsSocketError || CoreSocket == null)
                    {
                        OperateResult connect = ConnectServer( );
                        if (!connect.IsSuccess)
                        {
                            IsSocketError = true;
                            return OperateResult.CreateFailedResult<Socket>( connect );
                        }
                        else
                        {
                            IsSocketError = false;
                            return OperateResult.CreateSuccessResult( CoreSocket );
                        }
                    }
                    else
                    {
                        return OperateResult.CreateSuccessResult( CoreSocket );
                    }
                }
            }
            else
            {
                // 短连接模式
                return CreateSocketAndInitialication( );
            }
        }
        
        /// <summary>
        /// 连接并初始化网络套接字
        /// </summary>
        /// <returns>带有socket的结果对象</returns>
        private OperateResult<Socket> CreateSocketAndInitialication( )
        {
            OperateResult<Socket> result = CreateSocketAndConnect( new IPEndPoint( IPAddress.Parse( ipAddress ), port ), connectTimeOut );
            if (result.IsSuccess)
            {
                // 初始化
                OperateResult initi = InitializationOnConnect( result.Content );
                if (!initi.IsSuccess)
                {
                    result.Content?.Close( );
                    result.IsSuccess = initi.IsSuccess;
                    result.CopyErrorFromOther( initi );
                }
            }
            return result;
        }

        /// <summary>
        /// 指示如何创建一个新的消息对象
        /// </summary>
        /// <returns>消息对象</returns>
        protected virtual INetMessage GetNewNetMessage()
        {
            return null;
        }
        
        /// <summary>
        /// 在其他指定的套接字上，使用报文来通讯，传入需要发送的消息，返回一条完整的数据指令
        /// </summary>
        /// <param name="socket">指定的套接字</param>
        /// <param name="send">发送的完整的报文信息</param>
        /// <remarks>
        /// 无锁的基于套接字直接进行叠加协议的操作。
        /// </remarks>
        /// <example>
        /// 假设你有一个自己的socket连接了设备，本组件可以直接基于该socket实现modbus读取，三菱读取，西门子读取等等操作，前提是该服务器支持多协议，虽然这个需求听上去比较变态，但本组件支持这样的操作。
        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Core\NetworkDoubleBase.cs" region="ReadFromCoreServerExample1" title="ReadFromCoreServer示例" />
        /// </example>
        /// <returns>接收的完整的报文信息</returns>
        public virtual OperateResult<byte[]> ReadFromCoreServer( Socket socket, byte[] send )
        {
            TNetMessage netMessage = new TNetMessage( );
            netMessage.SendBytes = send;

            // send
            OperateResult sendResult = Send( socket, send );
            if (!sendResult.IsSuccess)
            {
                socket?.Close( );
                return OperateResult.CreateFailedResult<byte[]>( sendResult );
            }

            if (receiveTimeOut < 0) return OperateResult.CreateSuccessResult( new byte[0] );

            // receive msg
            OperateResult<byte[]> resultReceive = ReceiveByMessage( socket, receiveTimeOut, netMessage );
            if (!resultReceive.IsSuccess)
            {
                socket?.Close( );
                return new OperateResult<byte[]>( StringResources.Language.ReceiveDataTimeout + receiveTimeOut );
            }

            // check
            if (!netMessage.CheckHeadBytesLegal( Token.ToByteArray( ) ))
            {
                socket?.Close( );
                return new OperateResult<byte[]>( StringResources.Language.CommandHeadCodeCheckFailed );
            }

            // Success
            return OperateResult.CreateSuccessResult( resultReceive.Content );
        }


        /// <summary>
        /// 使用底层的数据报文来通讯，传入需要发送的消息，返回一条完整的数据指令
        /// </summary>
        /// <param name="send">发送的完整的报文信息</param>
        /// <returns>接收的完整的报文信息</returns>
        /// <remarks>
        /// 本方法用于实现本组件还未实现的一些报文功能，例如有些modbus服务器会有一些特殊的功能码支持，需要收发特殊的报文，详细请看示例
        /// </remarks>
        /// <example>
        /// 此处举例有个modbus服务器，有个特殊的功能码0x09，后面携带子数据0x01即可，发送字节为 0x00 0x00 0x00 0x00 0x00 0x03 0x01 0x09 0x01
        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Core\NetworkDoubleBase.cs" region="ReadFromCoreServerExample2" title="ReadFromCoreServer示例" />
        /// </example>
        public OperateResult<byte[]> ReadFromCoreServer( byte[] send )
        {
            var result = new OperateResult<byte[]>( );

            InteractiveLock.Enter( );

            // 获取有用的网络通道，如果没有，就建立新的连接
            OperateResult<Socket> resultSocket = GetAvailableSocket( );
            if (!resultSocket.IsSuccess)
            {
                IsSocketError = true;
                if (AlienSession != null) AlienSession.IsStatusOk = false;
                InteractiveLock.Leave( );
                result.CopyErrorFromOther( resultSocket );
                return result;
            }

            OperateResult<byte[]> read = ReadFromCoreServer( resultSocket.Content, send );

            if (read.IsSuccess)
            {
                IsSocketError = false;
                result.IsSuccess = read.IsSuccess;
                result.Content = read.Content;
                result.Message = StringResources.Language.SuccessText;
            }
            else
            {
                IsSocketError = true;
                if (AlienSession != null) AlienSession.IsStatusOk = false;
                result.CopyErrorFromOther( read );
            }

            InteractiveLock.Leave( );
            if (!isPersistentConn) resultSocket.Content?.Close( );
            return result;
        }
        
        #endregion

        #region Object Override

        /// <summary>
        /// 返回表示当前对象的字符串
        /// </summary>
        /// <returns>字符串信息</returns>
        public override string ToString( )
        {
            return $"NetworkDoubleBase<{typeof( TNetMessage )}, {typeof( TTransform )}>";
        }


        #endregion
    }
}
