using HslCommunication.BasicFramework;
using HslCommunication.Core;
using HslCommunication.Core.Address;
using HslCommunication.Core.IMessage;
using HslCommunication.Core.Net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace HslCommunication.Profinet.Melsec
{
    /// <summary>
    /// 三菱PLC通讯类,采用Qna兼容3E帧协议实现,需要在PLC侧先的以太网模块先进行配置,必须为ASCII通讯格式
    /// </summary>
    /// <remarks>
    /// 地址的输入的格式说明如下:
    /// <list type="table">
    ///   <listheader>
    ///     <term>地址名称</term>
    ///     <term>地址代号</term>
    ///     <term>示例</term>
    ///     <term>地址进制</term>
    ///     <term>字操作</term>
    ///     <term>位操作</term>
    ///     <term>备注</term>
    ///   </listheader>
    ///   <item>
    ///     <term>内部继电器</term>
    ///     <term>M</term>
    ///     <term>M100,M200</term>
    ///     <term>10</term>
    ///     <term>√</term>
    ///     <term>√</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>输入继电器</term>
    ///     <term>X</term>
    ///     <term>X100,X1A0</term>
    ///     <term>16</term>
    ///     <term>√</term>
    ///     <term>√</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>输出继电器</term>
    ///     <term>Y</term>
    ///     <term>Y100,Y1A0</term>
    ///     <term>16</term>
    ///     <term>√</term>
    ///     <term>√</term>
    ///     <term></term>
    ///   </item>
    ///    <item>
    ///     <term>锁存继电器</term>
    ///     <term>L</term>
    ///     <term>L100,L200</term>
    ///     <term>10</term>
    ///     <term>√</term>
    ///     <term>√</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>报警器</term>
    ///     <term>F</term>
    ///     <term>F100,F200</term>
    ///     <term>10</term>
    ///     <term>√</term>
    ///     <term>√</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>边沿继电器</term>
    ///     <term>V</term>
    ///     <term>V100,V200</term>
    ///     <term>10</term>
    ///     <term>√</term>
    ///     <term>√</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>链接继电器</term>
    ///     <term>B</term>
    ///     <term>B100,B1A0</term>
    ///     <term>16</term>
    ///     <term>√</term>
    ///     <term>√</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>步进继电器</term>
    ///     <term>S</term>
    ///     <term>S100,S200</term>
    ///     <term>10</term>
    ///     <term>√</term>
    ///     <term>√</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>数据寄存器</term>
    ///     <term>D</term>
    ///     <term>D1000,D2000</term>
    ///     <term>10</term>
    ///     <term>√</term>
    ///     <term>×</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>链接寄存器</term>
    ///     <term>W</term>
    ///     <term>W100,W1A0</term>
    ///     <term>16</term>
    ///     <term>√</term>
    ///     <term>×</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>文件寄存器</term>
    ///     <term>R</term>
    ///     <term>R100,R200</term>
    ///     <term>10</term>
    ///     <term>√</term>
    ///     <term>×</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>ZR文件寄存器</term>
    ///     <term>ZR</term>
    ///     <term>ZR100,ZR2A0</term>
    ///     <term>16</term>
    ///     <term>√</term>
    ///     <term>×</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>变址寄存器</term>
    ///     <term>Z</term>
    ///     <term>Z100,Z200</term>
    ///     <term>10</term>
    ///     <term>√</term>
    ///     <term>×</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>定时器的触点</term>
    ///     <term>TS</term>
    ///     <term>TS100,TS200</term>
    ///     <term>10</term>
    ///     <term>√</term>
    ///     <term>√</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>定时器的线圈</term>
    ///     <term>TC</term>
    ///     <term>TC100,TC200</term>
    ///     <term>10</term>
    ///     <term>√</term>
    ///     <term>√</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>定时器的当前值</term>
    ///     <term>TN</term>
    ///     <term>TN100,TN200</term>
    ///     <term>10</term>
    ///     <term>√</term>
    ///     <term>×</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>累计定时器的触点</term>
    ///     <term>SS</term>
    ///     <term>SS100,SS200</term>
    ///     <term>10</term>
    ///     <term>√</term>
    ///     <term>√</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>累计定时器的线圈</term>
    ///     <term>SC</term>
    ///     <term>SC100,SC200</term>
    ///     <term>10</term>
    ///     <term>√</term>
    ///     <term>√</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>累计定时器的当前值</term>
    ///     <term>SN</term>
    ///     <term>SN100,SN200</term>
    ///     <term>10</term>
    ///     <term>√</term>
    ///     <term>×</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>计数器的触点</term>
    ///     <term>CS</term>
    ///     <term>CS100,CS200</term>
    ///     <term>10</term>
    ///     <term>√</term>
    ///     <term>√</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>计数器的线圈</term>
    ///     <term>CC</term>
    ///     <term>CC100,CC200</term>
    ///     <term>10</term>
    ///     <term>√</term>
    ///     <term>√</term>
    ///     <term></term>
    ///   </item>
    ///   <item>
    ///     <term>计数器的当前值</term>
    ///     <term>CN</term>
    ///     <term>CN100,CN200</term>
    ///     <term>10</term>
    ///     <term>√</term>
    ///     <term>×</term>
    ///     <term></term>
    ///   </item>
    /// </list>
    /// </remarks>
    /// <example>
    /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\MelsecAscii.cs" region="Usage" title="简单的短连接使用" />
    /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\MelsecAscii.cs" region="Usage2" title="简单的长连接使用" />
    /// </example>
    public class MelsecMcAsciiNet : NetworkDeviceBase<MelsecQnA3EAsciiMessage, RegularByteTransform>
    {
        #region Constructor

        /// <summary>
        /// 实例化三菱的Qna兼容3E帧协议的通讯对象
        /// </summary>
        public MelsecMcAsciiNet()
        {
            WordLength = 1;
        }

        /// <summary>
        /// 实例化一个三菱的Qna兼容3E帧协议的通讯对象
        /// </summary>
        /// <param name="ipAddress">PLC的Ip地址</param>
        /// <param name="port">PLC的端口</param>
        public MelsecMcAsciiNet(string ipAddress, int port)
        {
            WordLength = 1;
            IpAddress = ipAddress;
            Port = port;
        }

        #endregion

        #region Public Member

        /// <summary>
        /// 网络号
        /// </summary>
        public byte NetworkNumber { get; set; } = 0x00;

        /// <summary>
        /// 网络站号
        /// </summary>
        public byte NetworkStationNumber { get; set; } = 0x00;


        #endregion

        #region Address Analysis

        /// <summary>
        /// 分析地址的方法,允许派生类里进行重写操作
        /// </summary>
        /// <param name="address">地址信息</param>
        /// <param name="length">数据长度</param>
        /// <returns>解析后的数据信息</returns>
        protected virtual OperateResult<McAddressData> McAnalysisAddress(string address, ushort length)
        {
            return McAddressData.ParseMelsecFrom(address, length);
        }

        #endregion

        #region Read Write Override

        /// <summary>
        /// 从三菱PLC中读取想要的数据,返回读取结果,读取的单位为字
        /// </summary>
        /// <param name="address">读取地址,格式为"M100","D100","W1A0"</param>
        /// <param name="length">读取的数据长度,字最大值960,位最大值7168</param>
        /// <returns>带成功标志的结果数据对象</returns>
        /// <remarks>
        /// 地址支持的列表参考 <seealso cref="MelsecMcAsciiNet"/> 的备注说明
        /// </remarks>
        /// <example>
        /// 假设起始地址为D100,D100存储了温度,100.6℃值为1006,D101存储了压力,1.23Mpa值为123,D102,D103存储了产量计数,读取如下:
        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\MelsecAscii.cs" region="ReadExample2" title="Read示例" />
        /// 以下是读取不同类型数据的示例
        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\MelsecAscii.cs" region="ReadExample1" title="Read示例" />
        /// </example>
        public override OperateResult<byte[]> Read(string address, ushort length)
        {
            // 分析地址
            OperateResult<McAddressData> addressResult = McAnalysisAddress(address, length);
            if (!addressResult.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(addressResult);

            List<byte> bytesContent = new List<byte>();
            ushort alreadyFinished = 0;
            while (alreadyFinished < length)
            {
                ushort readLength = (ushort)Math.Min(length - alreadyFinished, 450);
                addressResult.Content.Length = readLength;
                OperateResult<byte[]> read = ReadAddressData(addressResult.Content);
                if (!read.IsSuccess) return read;

                bytesContent.AddRange(read.Content);
                alreadyFinished += readLength;

                // 字的话就是正常的偏移位置,如果是位的话,就转到位的数据
                if (addressResult.Content.McDataType.DataType == 0)
                    addressResult.Content.AddressStart += readLength;
                else
                    addressResult.Content.AddressStart += readLength * 16;
            }
            return OperateResult.CreateSuccessResult(bytesContent.ToArray());
        }

        private OperateResult<byte[]> ReadAddressData(McAddressData addressData)
        {
            // 地址分析
            byte[] coreResult = MelsecHelper.BuildAsciiReadMcCoreCommand(addressData, false);

            // 核心交互
            var read = ReadFromCoreServer(PackMcCommand(coreResult, NetworkNumber, NetworkStationNumber));
            if (!read.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(read);

            // 错误代码验证
            ushort errorCode = Convert.ToUInt16(Encoding.ASCII.GetString(read.Content, 18, 4), 16);
            if (errorCode != 0) return new OperateResult<byte[]>(errorCode, StringResources.Language.MelsecPleaseReferToManulDocument);

            // 数据解析,需要传入是否使用位的参数
            return ExtractActualData(read.Content, false);
        }

        /// <summary>
        /// 向PLC写入数据,数据格式为原始的字节类型
        /// </summary>
        /// <param name="address">初始地址</param>
        /// <param name="value">原始的字节数据</param>
        /// <example>
        /// 假设起始地址为D100,D100存储了温度,100.6℃值为1006,D101存储了压力,1.23Mpa值为123,D102,D103存储了产量计数,写入如下:
        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\MelsecAscii.cs" region="WriteExample2" title="Write示例" />
        /// 以下是读取不同类型数据的示例
        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\MelsecAscii.cs" region="WriteExample1" title="Write示例" />
        /// </example>
        /// <returns>结果</returns>
        public override OperateResult Write(string address, byte[] value)
        {
            // 分析地址
            OperateResult<McAddressData> addressResult = McAnalysisAddress(address, 0);
            if (!addressResult.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(addressResult);

            // 地址分析
            byte[] coreResult = MelsecHelper.BuildAsciiWriteWordCoreCommand(addressResult.Content, value);

            // 核心交互
            OperateResult<byte[]> read = ReadFromCoreServer(PackMcCommand(coreResult, NetworkNumber, NetworkStationNumber));
            if (!read.IsSuccess) return read;

            // 错误码验证
            ushort errorCode = Convert.ToUInt16(Encoding.ASCII.GetString(read.Content, 18, 4), 16);
            if (errorCode != 0) return new OperateResult<byte[]>(errorCode, StringResources.Language.MelsecPleaseReferToManulDocument);

            // 写入成功
            return OperateResult.CreateSuccessResult();
        }

        #endregion

        #region Bool Operate Support

        /// <summary>
        /// 从三菱PLC中批量读取位软元件,返回读取结果
        /// </summary>
        /// <param name="address">起始地址</param>
        /// <param name="length">读取的长度</param>
        /// <returns>带成功标志的结果数据对象</returns>
        /// <remarks>
        /// 地址支持的列表参考 <seealso cref="MelsecMcAsciiNet"/> 的备注说明
        /// </remarks>
        /// <example>
        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\MelsecAscii.cs" region="ReadBool" title="Bool类型示例" />
        /// </example>
        public override OperateResult<bool[]> ReadBool(string address, ushort length)
        {
            // 分析地址
            OperateResult<McAddressData> addressResult = McAnalysisAddress(address, length);
            if (!addressResult.IsSuccess) return OperateResult.CreateFailedResult<bool[]>(addressResult);

            // 地址分析
            byte[] coreResult = MelsecHelper.BuildAsciiReadMcCoreCommand(addressResult.Content, true);

            // 核心交互
            var read = ReadFromCoreServer(PackMcCommand(coreResult, NetworkNumber, NetworkStationNumber));
            if (!read.IsSuccess) return OperateResult.CreateFailedResult<bool[]>(read);

            // 错误代码验证
            ushort errorCode = Convert.ToUInt16(Encoding.ASCII.GetString(read.Content, 18, 4), 16);
            if (errorCode != 0) return new OperateResult<bool[]>(errorCode, StringResources.Language.MelsecPleaseReferToManulDocument);

            // 数据解析,需要传入是否使用位的参数
            var extract = ExtractActualData(read.Content, true);
            if (!extract.IsSuccess) return OperateResult.CreateFailedResult<bool[]>(extract);

            // 转化bool数组
            return OperateResult.CreateSuccessResult(extract.Content.Select(m => m == 0x01).Take(length).ToArray());
        }

        /// <summary>
        /// 向PLC中位软元件写入bool数组,返回值说明,比如你写入M100,values[0]对应M100
        /// </summary>
        /// <param name="address">要写入的数据地址</param>
        /// <param name="values">要写入的实际数据,可以指定任意的长度</param>
        /// <example>
        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Profinet\MelsecAscii.cs" region="WriteBool" title="Write示例" />
        /// </example>
        /// <returns>返回写入结果</returns>
        public override OperateResult Write(string address, bool[] values)
        {
            // 分析地址
            OperateResult<McAddressData> addressResult = McAnalysisAddress(address, 0);
            if (!addressResult.IsSuccess) return addressResult;

            // 解析指令
            byte[] coreResult = MelsecHelper.BuildAsciiWriteBitCoreCommand(addressResult.Content, values);

            // 核心交互
            OperateResult<byte[]> read = ReadFromCoreServer(PackMcCommand(coreResult, NetworkNumber, NetworkStationNumber));
            if (!read.IsSuccess) return read;

            // 错误码验证
            ushort errorCode = Convert.ToUInt16(Encoding.ASCII.GetString(read.Content, 18, 4), 16);
            if (errorCode != 0) return new OperateResult<byte[]>(errorCode, StringResources.Language.MelsecPleaseReferToManulDocument);

            // 写入成功
            return OperateResult.CreateSuccessResult();
        }

        #endregion

        #region Remote Operate

        /// <summary>
        /// 远程Run操作
        /// </summary>
        /// <returns>是否成功</returns>
        public OperateResult RemoteRun()
        {
            // 核心交互
            OperateResult<byte[]> read = ReadFromCoreServer(PackMcCommand(Encoding.ASCII.GetBytes("1001000000010000"), NetworkNumber, NetworkStationNumber));
            if (!read.IsSuccess) return read;

            // 错误码校验
            ushort errorCode = Convert.ToUInt16(Encoding.ASCII.GetString(read.Content, 18, 4), 16);
            if (errorCode != 0) return new OperateResult(errorCode, StringResources.Language.MelsecPleaseReferToManulDocument);

            // 成功
            return OperateResult.CreateSuccessResult();
        }

        /// <summary>
        /// 远程Stop操作
        /// </summary>
        /// <returns>是否成功</returns>
        public OperateResult RemoteStop()
        {
            // 核心交互
            OperateResult<byte[]> read = ReadFromCoreServer(PackMcCommand(Encoding.ASCII.GetBytes("100200000001"), NetworkNumber, NetworkStationNumber));
            if (!read.IsSuccess) return read;

            // 错误码校验
            ushort errorCode = Convert.ToUInt16(Encoding.ASCII.GetString(read.Content, 18, 4), 16);
            if (errorCode != 0) return new OperateResult(errorCode, StringResources.Language.MelsecPleaseReferToManulDocument);

            // 成功
            return OperateResult.CreateSuccessResult();
        }

        /// <summary>
        /// 读取PLC的型号信息
        /// </summary>
        /// <returns>返回型号的结果对象</returns>
        public OperateResult<string> ReadPlcType()
        {
            // 核心交互
            OperateResult<byte[]> read = ReadFromCoreServer(PackMcCommand(Encoding.ASCII.GetBytes("01010000"), NetworkNumber, NetworkStationNumber));
            if (!read.IsSuccess) return OperateResult.CreateFailedResult<string>(read);

            // 错误码校验
            ushort errorCode = Convert.ToUInt16(Encoding.ASCII.GetString(read.Content, 18, 4), 16);
            if (errorCode != 0) return new OperateResult<string>(errorCode, StringResources.Language.MelsecPleaseReferToManulDocument);

            // 成功
            return OperateResult.CreateSuccessResult(Encoding.ASCII.GetString(read.Content, 22, 16).TrimEnd());
        }

        #endregion

        #region Object Override

        /// <summary>
        /// 获取当前对象的字符串标识形式
        /// </summary>
        /// <returns>字符串信息</returns>
        public override string ToString()
        {
            return $"MelsecMcAsciiNet[{IpAddress}:{Port}]";
        }

        #endregion

        #region Static Method Helper

        /// <summary>
        /// 将MC协议的核心报文打包成一个可以直接对PLC进行发送的原始报文
        /// </summary>
        /// <param name="mcCore">MC协议的核心报文</param>
        /// <param name="networkNumber">网络号</param>
        /// <param name="networkStationNumber">网络站号</param>
        /// <returns>原始报文信息</returns>
        public static byte[] PackMcCommand(byte[] mcCore, byte networkNumber = 0, byte networkStationNumber = 0)
        {
            byte[] plcCommand = new byte[22 + mcCore.Length];
            plcCommand[0] = 0x35;                                                                        // 副标题
            plcCommand[1] = 0x30;
            plcCommand[2] = 0x30;
            plcCommand[3] = 0x30;
            plcCommand[4] = SoftBasic.BuildAsciiBytesFrom(networkNumber)[0];                         // 网络号
            plcCommand[5] = SoftBasic.BuildAsciiBytesFrom(networkNumber)[1];
            plcCommand[6] = 0x46;                                                                        // PLC编号
            plcCommand[7] = 0x46;
            plcCommand[8] = 0x30;                                                                        // 目标模块IO编号
            plcCommand[9] = 0x33;
            plcCommand[10] = 0x46;
            plcCommand[11] = 0x46;
            plcCommand[12] = SoftBasic.BuildAsciiBytesFrom(networkStationNumber)[0];                  // 目标模块站号
            plcCommand[13] = SoftBasic.BuildAsciiBytesFrom(networkStationNumber)[1];
            plcCommand[14] = SoftBasic.BuildAsciiBytesFrom((ushort)(plcCommand.Length - 18))[0];     // 请求数据长度
            plcCommand[15] = SoftBasic.BuildAsciiBytesFrom((ushort)(plcCommand.Length - 18))[1];
            plcCommand[16] = SoftBasic.BuildAsciiBytesFrom((ushort)(plcCommand.Length - 18))[2];
            plcCommand[17] = SoftBasic.BuildAsciiBytesFrom((ushort)(plcCommand.Length - 18))[3];
            plcCommand[18] = 0x30;                                                                        // CPU监视定时器
            plcCommand[19] = 0x30;
            plcCommand[20] = 0x31;
            plcCommand[21] = 0x30;
            mcCore.CopyTo(plcCommand, 22);

            return plcCommand;
        }

        /// <summary>
        /// 从PLC反馈的数据中提取出实际的数据内容,需要传入反馈数据,是否位读取
        /// </summary>
        /// <param name="response">反馈的数据内容</param>
        /// <param name="isBit">是否位读取</param>
        /// <returns>解析后的结果对象</returns>
        public static OperateResult<byte[]> ExtractActualData(byte[] response, bool isBit)
        {
            if (isBit)
            {
                // 位读取
                byte[] Content = new byte[response.Length - 22];
                for (int i = 22; i < response.Length; i++)
                {
                    Content[i - 22] = response[i] == 0x30 ? (byte)0x00 : (byte)0x01;
                }

                return OperateResult.CreateSuccessResult(Content);
            }
            else
            {
                // 字读取
                byte[] Content = new byte[(response.Length - 22) / 2];
                for (int i = 0; i < Content.Length / 2; i++)
                {
                    ushort tmp = Convert.ToUInt16(Encoding.ASCII.GetString(response, i * 4 + 22, 4), 16);
                    BitConverter.GetBytes(tmp).CopyTo(Content, i * 2);
                }

                return OperateResult.CreateSuccessResult(Content);
            }
        }

        #endregion
    }
}