using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MECF.Framework.RT.Core.IoProviders.Common;
using MECF.Framework.RT.Core.IoProviders.Common.Net.NetworkBase;
using MECF.Framework.RT.Core.IoProviders.Common.Transfer;
namespace MECF.Framework.RT.Core.IoProviders.Melsec
{
    /// 
    /// 三菱PLC通讯类,采用Qna兼容3E帧协议实现,需要在PLC侧先的以太网模块先进行配置,必须为二进制通讯
    /// 
    /// 
    /// 目前组件测试通过的PLC型号列表 
    /// 
    /// - Q06UDV PLC  感谢hwdq0012
 
    /// - fx5u PLC  感谢山楂
 
    /// - Q02CPU PLC 
 
    /// - L02CPU PLC 
 
    /// 
    /// 地址的输入的格式说明如下:
    /// 
    ///   
    ///     地址名称
    ///     地址代号
    ///     示例
    ///     地址进制
    ///     字操作
    ///     位操作
    ///     备注
    ///   
    ///   - 
    ///     内部继电器
    ///     M
    ///     M100,M200
    ///     10
    ///     √
    ///     √
    ///     
    ///   
 
    ///   - 
    ///     输入继电器
    ///     X
    ///     X100,X1A0
    ///     16
    ///     √
    ///     √
    ///     
    ///   
 
    ///   - 
    ///     输出继电器
    ///     Y
    ///     Y100,Y1A0
    ///     16
    ///     √
    ///     √
    ///     
    ///   
 
    ///    - 
    ///     锁存继电器
    ///     L
    ///     L100,L200
    ///     10
    ///     √
    ///     √
    ///     
    ///   
 
    ///   - 
    ///     报警器
    ///     F
    ///     F100,F200
    ///     10
    ///     √
    ///     √
    ///     
    ///   
 
    ///   - 
    ///     边沿继电器
    ///     V
    ///     V100,V200
    ///     10
    ///     √
    ///     √
    ///     
    ///   
 
    ///   - 
    ///     链接继电器
    ///     B
    ///     B100,B1A0
    ///     16
    ///     √
    ///     √
    ///     
    ///   
 
    ///   - 
    ///     步进继电器
    ///     S
    ///     S100,S200
    ///     10
    ///     √
    ///     √
    ///     
    ///   
 
    ///   - 
    ///     数据寄存器
    ///     D
    ///     D1000,D2000
    ///     10
    ///     √
    ///     ×
    ///     
    ///   
 
    ///   - 
    ///     链接寄存器
    ///     W
    ///     W100,W1A0
    ///     16
    ///     √
    ///     ×
    ///     
    ///   
 
    ///   - 
    ///     文件寄存器
    ///     R
    ///     R100,R200
    ///     10
    ///     √
    ///     ×
    ///     
    ///   
 
    ///   - 
    ///     ZR文件寄存器
    ///     ZR
    ///     ZR100,ZR2A0
    ///     16
    ///     √
    ///     ×
    ///     
    ///   
 
    ///   - 
    ///     变址寄存器
    ///     Z
    ///     Z100,Z200
    ///     10
    ///     √
    ///     ×
    ///     
    ///   
 
    ///   - 
    ///     定时器的触点
    ///     TS
    ///     TS100,TS200
    ///     10
    ///     √
    ///     √
    ///     
    ///   
 
    ///   - 
    ///     定时器的线圈
    ///     TC
    ///     TC100,TC200
    ///     10
    ///     √
    ///     √
    ///     
    ///   
 
    ///   - 
    ///     定时器的当前值
    ///     TN
    ///     TN100,TN200
    ///     10
    ///     √
    ///     ×
    ///     
    ///   
 
    ///   - 
    ///     累计定时器的触点
    ///     SS
    ///     SS100,SS200
    ///     10
    ///     √
    ///     √
    ///     
    ///   
 
    ///   - 
    ///     累计定时器的线圈
    ///     SC
    ///     SC100,SC200
    ///     10
    ///     √
    ///     √
    ///     
    ///   
 
    ///   - 
    ///     累计定时器的当前值
    ///     SN
    ///     SN100,SN200
    ///     10
    ///     √
    ///     ×
    ///     
    ///   
 
    ///   - 
    ///     计数器的触点
    ///     CS
    ///     CS100,CS200
    ///     10
    ///     √
    ///     √
    ///     
    ///   
 
    ///   - 
    ///     计数器的线圈
    ///     CC
    ///     CC100,CC200
    ///     10
    ///     √
    ///     √
    ///     
    ///   
 
    ///   - 
    ///     计数器的当前值
    ///     CN
    ///     CN100,CN200
    ///     10
    ///     √
    ///     ×
    ///     
    ///   
 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    public class MelsecMcNet : NetworkDeviceBase
    {
        #region Constructor
        /// 
        /// 实例化三菱的Qna兼容3E帧协议的通讯对象
        /// 
        public MelsecMcNet( )
        {
            WordLength = 1;
        }
        /// 
        /// 实例化一个三菱的Qna兼容3E帧协议的通讯对象
        /// 
        /// PLC的Ip地址
        /// PLC的端口
        public MelsecMcNet( string ipAddress, int port )
        {
            WordLength = 1;
            IpAddress = ipAddress;
            Port = port;
        }
        #endregion
        #region Public Member
        /// 
        /// 网络号,通常为0
        /// 
        /// 
        /// 依据PLC的配置而配置,如果PLC配置了1,那么此处也填0,如果PLC配置了2,此处就填2,测试不通的话,继续测试0
        /// 
        public byte NetworkNumber { get; set; } = 0x00;
        /// 
        /// 网络站号,通常为0
        /// 
        /// 
        /// 依据PLC的配置而配置,如果PLC配置了1,那么此处也填0,如果PLC配置了2,此处就填2,测试不通的话,继续测试0
        /// 
        public byte NetworkStationNumber { get; set; } = 0x00;
        #endregion
        #region Virtual Address Analysis
        /// 
        /// 分析地址的方法,允许派生类里进行重写操作
        /// 
        /// 地址信息
        /// 解析后的数据信息
        protected virtual OperateResult McAnalysisAddress( string address )
        {
            return MelsecHelper.McAnalysisAddress( address );
        }
        #endregion
        #region Read Write Support
        /// 
        /// 从三菱PLC中读取想要的数据,输入地址,按照字单位读取,返回读取结果
        /// 
        /// 读取地址,格式为"M100","D100","W1A0"
        /// 读取的数据长度,字最大值960,位最大值7168
        /// 带成功标志的结果数据对象
        /// 
        /// 地址支持的列表参考  的备注说明
        /// 
        /// 
        /// 假设起始地址为D100,D100存储了温度,100.6℃值为1006,D101存储了压力,1.23Mpa值为123,D102,D103存储了产量计数,读取如下:
        /// 
        /// 以下是读取不同类型数据的示例
        /// 
        /// 
        public override OperateResult Read( string address, ushort length )
        {
            // 分析地址
            OperateResult coreResult = MelsecHelper.BuildReadMcCoreCommand( address, length, false, McAnalysisAddress );
            if (!coreResult.IsSuccess) return coreResult;
            
            // 核心交互
            var read = ReadFromCoreServer( PackMcCommand( coreResult.Content, this.NetworkNumber, this.NetworkStationNumber ) );
            if (!read.IsSuccess) return OperateResult.CreateFailedResult( read );
            // 错误代码验证
            ushort errorCode = BitConverter.ToUInt16( read.Content, 9 );
            if (errorCode != 0) return new OperateResult( errorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
            // 数据解析,需要传入是否使用位的参数
            return ExtractActualData( SoftBasic.BytesArrayRemoveBegin( read.Content, 11 ), false );
        }
        /// 
        /// 向PLC写入数据,数据格式为原始的字节类型
        /// 
        /// 初始地址
        /// 原始的字节数据
        /// 
        /// 假设起始地址为D100,D100存储了温度,100.6℃值为1006,D101存储了压力,1.23Mpa值为123,D102,D103存储了产量计数,写入如下:
        /// 
        /// 以下是写入不同类型数据的示例
        /// 
        /// 
        /// 结果
        public override OperateResult Write( string address, byte[] value )
        {
            // 分析地址
            OperateResult coreResult = MelsecHelper.BuildWriteWordCoreCommand( address, value, McAnalysisAddress );
            if (!coreResult.IsSuccess) return coreResult;
            // 核心交互
            OperateResult read = ReadFromCoreServer( PackMcCommand( coreResult.Content, NetworkNumber, NetworkStationNumber ) );
            if (!read.IsSuccess) return read;
            // 错误码校验
            ushort ErrorCode = BitConverter.ToUInt16( read.Content, 9 );
            if (ErrorCode != 0) return new OperateResult( ErrorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
            // 成功
            return OperateResult.CreateSuccessResult( );
        }
        #endregion
        #region Bool Operate Support
        /// 
        /// 从三菱PLC中批量读取位软元件,返回读取结果
        /// 
        /// 起始地址
        /// 读取的长度
        /// 带成功标志的结果数据对象
        /// 
        /// 地址支持的列表参考  的备注说明
        /// 
        /// 
        /// 
        /// 
        public OperateResult ReadBool( string address, ushort length )
        {
            // 获取指令
            OperateResult coreResult = MelsecHelper.BuildReadMcCoreCommand( address, length, true, McAnalysisAddress );
            if (!coreResult.IsSuccess) return OperateResult.CreateFailedResult( coreResult );
            // 核心交互
            var read = ReadFromCoreServer( PackMcCommand( coreResult.Content, NetworkNumber, NetworkStationNumber ) );
			if (!read.IsSuccess) return OperateResult.CreateFailedResult(read);
			// 错误代码验证
			ushort errorCode = BitConverter.ToUInt16( read.Content, 9 );
            if (errorCode != 0) return new OperateResult( errorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
            // 数据解析,需要传入是否使用位的参数
            var extract = ExtractActualData( SoftBasic.BytesArrayRemoveBegin( read.Content, 11 ), true );
            if(!extract.IsSuccess) return OperateResult.CreateFailedResult( extract );
            // 转化bool数组
            return OperateResult.CreateSuccessResult( extract.Content.Select( m => m == 0x01 ).Take( length ).ToArray( ) );
        }
        /// 
        /// 从三菱PLC中批量读取位软元件,返回读取结果
        /// 
        /// 起始地址
        /// 带成功标志的结果数据对象
        /// 参照  方法 
        public OperateResult ReadBool( string address )
        {
            OperateResult read = ReadBool( address, 1 );
            if (!read.IsSuccess) return OperateResult.CreateFailedResult( read );
            return OperateResult.CreateSuccessResult( read.Content[0] );
        }
        
        /// 
        /// 向PLC中位软元件写入bool数组,返回值说明,比如你写入M100,values[0]对应M100
        /// 
        /// 要写入的数据地址
        /// 要写入的实际数据,长度为8的倍数
        /// 
        /// 详细请查看方法的示例
        /// 
        /// 返回写入结果
        public OperateResult Write( string address, bool value )
        {
            return Write( address, new bool[] { value} );
        }
        /// 
        /// 向PLC中位软元件写入bool数组,返回值说明,比如你写入M100,values[0]对应M100
        /// 
        /// 要写入的数据地址
        /// 要写入的实际数据,可以指定任意的长度
        /// 
        /// 
        /// 
        /// 返回写入结果
        public OperateResult Write( string address, bool[] values )
        {
            OperateResult coreResult = MelsecHelper.BuildWriteBitCoreCommand( address, values, McAnalysisAddress );
            if (!coreResult.IsSuccess) return coreResult;
            // 核心交互
            OperateResult read = ReadFromCoreServer( PackMcCommand( coreResult.Content, NetworkNumber, NetworkStationNumber ) );
            if (!read.IsSuccess) return read;
            // 错误码校验
            ushort ErrorCode = BitConverter.ToUInt16( read.Content, 9 );
            if (ErrorCode != 0) return new OperateResult( ErrorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
            // 成功
            return OperateResult.CreateSuccessResult( );
        }
        #endregion
        #region Remote Operate
        /// 
        /// 远程Run操作
        /// 
        /// 是否成功
        public OperateResult RemoteRun( )
        {
            // 核心交互
            OperateResult read = ReadFromCoreServer( PackMcCommand( new byte[] { 0x01, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 }, NetworkNumber, NetworkStationNumber ) );
            if (!read.IsSuccess) return read;
            // 错误码校验
            ushort ErrorCode = BitConverter.ToUInt16( read.Content, 9 );
            if (ErrorCode != 0) return new OperateResult( ErrorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
            // 成功
            return OperateResult.CreateSuccessResult( );
        }
        
        /// 
        /// 远程Stop操作
        /// 
        /// 是否成功
        public OperateResult RemoteStop( )
        {
            // 核心交互
            OperateResult read = ReadFromCoreServer( PackMcCommand( new byte[] { 0x02, 0x10, 0x00, 0x00, 0x01, 0x00 }, NetworkNumber, NetworkStationNumber ) );
            if (!read.IsSuccess) return read;
            // 错误码校验
            ushort ErrorCode = BitConverter.ToUInt16( read.Content, 9 );
            if (ErrorCode != 0) return new OperateResult( ErrorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
            // 成功
            return OperateResult.CreateSuccessResult( );
        }
        /// 
        /// 远程Reset操作
        /// 
        /// 是否成功
        public OperateResult RemoteReset()
        {
            // 核心交互
            OperateResult read = ReadFromCoreServer( PackMcCommand( new byte[] { 0x06, 0x10, 0x00, 0x00, 0x01, 0x00 }, NetworkNumber, NetworkStationNumber ) );
            if (!read.IsSuccess) return read;
            // 错误码校验
            ushort ErrorCode = BitConverter.ToUInt16( read.Content, 9 );
            if (ErrorCode != 0) return new OperateResult( ErrorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
            // 成功
            return OperateResult.CreateSuccessResult( );
        }
        /// 
        /// 读取PLC的型号信息
        /// 
        /// 返回型号的结果对象
        public OperateResult ReadPlcType( )
        {
            // 核心交互
            OperateResult read = ReadFromCoreServer( PackMcCommand( new byte[] { 0x01, 0x01, 0x00, 0x00 }, NetworkNumber, NetworkStationNumber ) );
            if (!read.IsSuccess) return OperateResult.CreateFailedResult( read );
            // 错误码校验
            ushort ErrorCode = BitConverter.ToUInt16( read.Content, 9 );
            if (ErrorCode != 0) return new OperateResult( ErrorCode, StringResources.Language.MelsecPleaseReferToManulDocument );
            // 成功
            return OperateResult.CreateSuccessResult( Encoding.ASCII.GetString( read.Content, 11, 16 ).TrimEnd( ) );
        }
        #endregion
        #region Object Override
        /// 
        /// 获取当前对象的字符串标识形式
        /// 
        /// 字符串信息
        public override string ToString()
        {
            return $"MelsecMcNet[{IpAddress}:{Port}]";
        }
        #endregion
        #region Static Method Helper
        /// 
        /// 将MC协议的核心报文打包成一个可以直接对PLC进行发送的原始报文
        /// 
        /// MC协议的核心报文
        /// 网络号
        /// 网络站号
        /// 原始报文信息
        public static byte[] PackMcCommand(byte[] mcCore, byte networkNumber = 0, byte networkStationNumber = 0)
        {
            byte[] _PLCCommand = new byte[11 + mcCore.Length];
            _PLCCommand[0] = 0x50;                                               // 副标题
            _PLCCommand[1] = 0x00;
            _PLCCommand[2] = networkNumber;                                      // 网络号
            _PLCCommand[3] = 0xFF;                                               // PLC编号
            _PLCCommand[4] = 0xFF;                                               // 目标模块IO编号
            _PLCCommand[5] = 0x03;
            _PLCCommand[6] = networkStationNumber;                               // 目标模块站号
            _PLCCommand[7] = (byte)((_PLCCommand.Length - 9) % 256);             // 请求数据长度
            _PLCCommand[8] = (byte)((_PLCCommand.Length - 9) / 256);
            _PLCCommand[9] = 0x0A;                                               // CPU监视定时器
            _PLCCommand[10] = 0x00;
            mcCore.CopyTo( _PLCCommand, 11 );
            return _PLCCommand;
        }
        /// 
        /// 从PLC反馈的数据中提取出实际的数据内容,需要传入反馈数据,是否位读取
        /// 
        /// 反馈的数据内容
        /// 是否位读取
        /// 解析后的结果对象
        public static OperateResult ExtractActualData( byte[] response, bool isBit )
        {
            if (isBit)
            {
                // 位读取
                byte[] Content = new byte[response.Length * 2];
                for (int i = 0; i < response.Length; i++)
                {
                    if ((response[i] & 0x10) == 0x10)
                    {
                        Content[i * 2 + 0] = 0x01;
                    }
                    if ((response[i] & 0x01) == 0x01)
                    {
                        Content[i * 2 + 1] = 0x01;
                    }
                }
                return OperateResult.CreateSuccessResult( Content );
            }
            else
            {
                // 字读取
                return OperateResult.CreateSuccessResult( response );
            }
        }
        
        #endregion
    }
}