using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Common;
using Dao;
using Domain;
using RCUHost.Protocols;

namespace RCUHost.Implement
{
    public class UpdateHostReceiver : GenericReceiverBase, IUpdateHostReceiver
    {
        private static log4net.ILog logger = log4net.LogManager.GetLogger(typeof(UpdateHostReceiver));
        private readonly int TFTP_PORT = Convert.ToInt16(System.Configuration.ConfigurationManager.AppSettings["TFTPPort"]);//TFTP通信端口
        private const int BLOCK_SIZE = 512;//TFTP块大小512字节
        public IHostRepository HostRepository { get; set; }
        public IHostUpdateStatusRepository HostUpdateStatusRepository { get; set; }
        private IList<UpdateHostWorker> updateHostList = new List<UpdateHostWorker>();
        /// <summary>
        /// 升级
        /// </summary>
        /// <param name="hostUpdate"></param>
        /// <param name="fileType"></param>
        /// <param name="fileHref"></param>
        /// <param name="fileMd5"></param>
        /// <param name="hosts"></param>
        public void Update(HostUpdate hostUpdate, FileType fileType, string fileHref, string fileMd5, IList<Host> hosts)
        {
            if (hosts == null || hosts.Count == 0)
            {
                throw new ApplicationException("升级失败，没有找到需要升级的主机。");
            }
            if (String.IsNullOrEmpty(fileHref))
            {
                throw new ApplicationException("升级失败，无效的升级文件。");
            }
            string updateFile = Tools.GetApplicationPath() + fileHref;
            if (!File.Exists(updateFile))
            {
                throw new ApplicationException("升级失败，【" + updateFile + "】文件不存在。");
            }

            //添加升级功能标志，告诉缓存，不能被拦截
            foreach (var item in hosts)
            {
                string Key = "Upgrade_UpdateSQL_" + item.HostNumber;
                MemoryCacheHelper.Set(Key, item.ID, DateTimeOffset.Now.AddMinutes(3));
            }

            FileInfo fileInfo = new FileInfo(updateFile);
            ushort blockNum = (ushort)Math.Ceiling((double)fileInfo.Length / BLOCK_SIZE);
            this.updateHostList.Clear();
            foreach (var host in hosts)
            {
                SendUpdateRequest(host, fileMd5, blockNum, (byte)fileType, fileHref.Substring(fileHref.IndexOf("/")));//发送升级通知
                Thread.Sleep(300);
                this.updateHostList.Add(new UpdateHostWorker(host, hostUpdate, MessageIP, fileHref, updateFile));
                if (hostUpdate == null)//从api获取升级文件方式为null，保存主机表升级状态信息
                {
                    HostRepository.SetUpgradeStatus(host, 0, true);//重置升级状态
                }
                else
                {
                    HostUpdateStatus hostUpdateStatus = HostUpdateStatusRepository.Get(host, hostUpdate);
                    if (hostUpdateStatus == null)
                    {
                        hostUpdateStatus = new HostUpdateStatus();
                        hostUpdateStatus.Host = host;
                        hostUpdateStatus.HostUpdate = hostUpdate;
                        hostUpdateStatus.PublishTime = DateTime.Now;
                    }
                    hostUpdateStatus.Status = 0;
                    hostUpdateStatus.UpdatedTime = DateTime.Now;
                    HostUpdateStatusRepository.SaveOrUpdate(hostUpdateStatus);
                }
                //logger.Error(string.Format("酒店{0}客房{1}开始升级：{2}", host.SysHotel.Code, host.RoomNumber, fileHref));
            }
        }

        public override void Process(ReceiverContext context1)
        {
            int startIndex = StructConverter.SizeOf(context1.SystemHeader);
            //var mydata = Tools.ByteToString(context1.Data);
            //logger.Error(string.Format("升级返回:{0}", mydata));
            UpdateHostPacketReply? reply1 = DecodeUpdateHostPacketReply(context1.Data, startIndex);
            var TTT = new Tuple<ReceiverContext, UpdateHostPacketReply?>(context1, reply1);
            //logger.Error(string.Format("收到tftp升级回复命令（{0}:{1}）：{2}，解析结果：{3}", context.RemoteEndPoint.Address.ToString(), context.RemoteEndPoint.Port, Tools.ByteToString(context.Data), reply.HasValue));
            if (reply1.HasValue)
            {
                Task.Factory.StartNew((State) =>
                {
                    var NNN = State as Tuple<ReceiverContext, UpdateHostPacketReply?>;
                    var context = NNN.Item1;
                    var reply = NNN.Item2;
                    var updateHostWorker = this.updateHostList.FirstOrDefault(r => r.Host.HostNumber == context.SystemHeader.Value.HostNumber.ToString());
                    var data = Tools.ByteToString(context1.Data);
                    logger.Error(string.Format("主机{0}升级返回:{1}", context.SystemHeader.Value.HostNumber.ToString(), data));
                    if (updateHostWorker == null)
                    {
                        return;
                    }
                    try
                    {
                        SaveSystemLog(30, "升级主机", string.Format("收到主机({0})升级回复命令，状态：{1}", updateHostWorker.Host.RoomNumber, reply.Value.Status), "收到命令", "RCU", context.RemoteEndPoint.Address.ToString(), updateHostWorker.Host.SysHotel.ID);
                    }
                    catch (Exception)
                    {

                    }
                    if (updateHostWorker.HostUpdate == null)
                    {
                        BarData bbb = new BarData();
                        bbb.HostID = updateHostWorker.Host.ID;

                        switch (reply.Value.Status)
                        {
                            case UpdateHostPacketReply.Ready:
                                HostRepository.SetUpgradeStatus(updateHostWorker.Host, 0);//升级就绪
                                //updateHostWorker.Update();
                                bbb.Upgrade_status = "升级就绪";
                                break;
                            case UpdateHostPacketReply.Completed:
                                Reply(context);//升级完成回复主机
                                HostRepository.SetUpgradeStatus(updateHostWorker.Host, 1);//升级完成
                                bbb.Upgrade_status = "升级完成";
                                break;
                            case UpdateHostPacketReply.BlockNumError:
                                bbb.Upgrade_status = "块错误";
                                HostRepository.SetUpgradeStatus(updateHostWorker.Host, 2);//升级失败
                                break;
                            case UpdateHostPacketReply.FileTypeError:
                                bbb.Upgrade_status = "文件错误";
                                HostRepository.SetUpgradeStatus(updateHostWorker.Host, 2);//升级失败
                                break;
                            case UpdateHostPacketReply.FileMD5Error:
                                bbb.Upgrade_status = "文件MD5校验错误";
                                HostRepository.SetUpgradeStatus(updateHostWorker.Host, 2);//升级失败
                                break;
                            default:
                                HostRepository.SetUpgradeStatus(updateHostWorker.Host, 2);//升级失败
                                bbb.Upgrade_status = "升级失败";
                                break;
                        }
                        UploadCurrentVersionReceiver.UP_Grade_Json(updateHostWorker.Host, bbb);
                    }
                    else
                    {
                        HostUpdateStatus hostUpdateStatus = HostUpdateStatusRepository.Get(updateHostWorker.Host, updateHostWorker.HostUpdate);
                        if (hostUpdateStatus == null)
                        {
                            hostUpdateStatus = new HostUpdateStatus();
                            hostUpdateStatus.Host = updateHostWorker.Host;
                            hostUpdateStatus.HostUpdate = updateHostWorker.HostUpdate;
                            hostUpdateStatus.PublishTime = DateTime.Now;
                            hostUpdateStatus.Status = 0;
                        }
                        BarData bbb = new BarData();
                        bbb.HostID = updateHostWorker.Host.ID;
                        switch (reply.Value.Status)
                        {
                            case UpdateHostPacketReply.Ready:
                                //updateHostWorker.Update();
                                hostUpdateStatus.Status = 0;//升级就绪
                                bbb.Upgrade_status = "升级就绪";
                                break;
                            case UpdateHostPacketReply.Completed:
                                bbb.Upgrade_status = "升级完成";
                                Reply(context);//升级完成回复主机
                                hostUpdateStatus.Status = 1;//升级完成
                                updateHostList.Remove(updateHostWorker);
                                break;
                            case UpdateHostPacketReply.BlockNumError:
                                bbb.Upgrade_status = "块错误";
                                hostUpdateStatus.Status = 2;//升级失败
                                updateHostList.Remove(updateHostWorker);
                                break;
                            case UpdateHostPacketReply.FileTypeError:
                                bbb.Upgrade_status = "文件错误";
                                hostUpdateStatus.Status = 2;//升级失败
                                updateHostList.Remove(updateHostWorker);
                                break;
                            case UpdateHostPacketReply.FileMD5Error:
                                bbb.Upgrade_status = "文件MD5校验错误";
                                hostUpdateStatus.Status = 2;//升级失败
                                updateHostList.Remove(updateHostWorker);
                                break;
                            default:
                                bbb.Upgrade_status = "升级失败";
                                hostUpdateStatus.Status = 2;//升级失败
                                updateHostList.Remove(updateHostWorker);
                                break;
                        }
                        bbb.Upgrade_DateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                        UploadCurrentVersionReceiver.UP_Grade_Json(updateHostWorker.Host, bbb);
                        hostUpdateStatus.UpdatedTime = DateTime.Now;
                        HostUpdateStatusRepository.SaveOrUpdate(hostUpdateStatus);
                    }
                }, TTT);
            }
        }
        public override CommandType CommandType
        {
            get { return CommandType.TFTPUpdate; }
        }

        #region Private Methods

        /// <summary>
        /// 发送升级指令到RCU主机
        /// </summary>
        /// <param name="host"></param>
        /// <param name="updateFileMd5">升级文件MD5值</param>
        private void SendUpdateRequest(Host host, string updateFileMd5, ushort blockNum, byte fileType, string fileName)
        {
            logger.Error("主机升级：" + host.SysHotel.Code + ":" + host.RoomNumber + " FileMd5:" + updateFileMd5 + " FileName:" + fileName);
            byte[] data = CreateUpdateRequestPacket(updateFileMd5, blockNum, fileType, fileName);
            logger.Error("升级HostNumber为：" + host.HostNumber);
            logger.Error("升级指令为：" + Tools.ByteToString(data));
            Send(data, host.HostNumber, host.MAC);// host.IP, host.Port);
        }
        /// <summary>
        /// 创建主机升级请求数据包
        /// </summary>
        /// <param name="updateFileMd5">升级文件MD5值</param>
        /// <param name="blockNum">块数量</param>
        /// <param name="fileType">文件类型</param>
        /// <param name="fileName">文件名（含酒店编码）</param>
        /// <returns></returns>
        private byte[] CreateUpdateRequestPacket(string updateFileMd5, ushort blockNum, byte fileType, string fileName)
        {
            SystemHeader systemHeader = CreateSystemHeader();
            int headerSize = StructConverter.SizeOf(systemHeader);

            byte[] ipArr = IPAddress.Parse(MessageIP).GetAddressBytes();
            byte[] portArr = BitConverter.GetBytes((ushort)TFTP_PORT);//tftp通讯端口
            uint[] md5Arr = Tools.MD5StringToUIntArray(updateFileMd5);
            byte[] bFileName = System.Text.Encoding.Default.GetBytes(fileName); //Tools.GetBytes(fileName, 20);

            using (var stream = new MemoryStream())
            {
                stream.Seek(headerSize, SeekOrigin.Begin);

                stream.Write(ipArr, 0, ipArr.Length);
                stream.Write(portArr, 0, portArr.Length);
                stream.Write(BitConverter.GetBytes(md5Arr[0]), 0, 4);
                stream.Write(BitConverter.GetBytes(md5Arr[1]), 0, 4);
                stream.Write(BitConverter.GetBytes(md5Arr[2]), 0, 4);
                stream.Write(BitConverter.GetBytes(md5Arr[3]), 0, 4);
                stream.Write(BitConverter.GetBytes(blockNum), 0, 2);
                stream.Write(new byte[] { fileType }, 0, 1);//文件类型，0是bin，1是cfg，2是bat
                stream.Write(bFileName, 0, bFileName.Length);//文件名
                stream.Write(new byte[] { 0, 0 }, 0, 2);//补两个长度

                //填充 SystemHeader
                stream.Seek(0, SeekOrigin.Begin);
                systemHeader.FrameLength = (ushort)stream.Length;
                systemHeader.FrameNo = 0xFFFF;
                byte[] headerData = StructConverter.StructToBytes(systemHeader);
                stream.Write(headerData, 0, headerData.Length);

                return stream.ToArray();
            }
        }
        /// <summary>
        /// 解码 UpdateHostPacketReply
        /// </summary>
        /// <param name="data"></param>
        /// <param name="startIndex"></param>
        /// <returns></returns>
        private UpdateHostPacketReply? DecodeUpdateHostPacketReply(byte[] data, int startIndex)
        {
            return StructConverter.BytesToStruct(data, startIndex, typeof(UpdateHostPacketReply)) as UpdateHostPacketReply?;
        }

        #endregion
    }

    public class UpdateHostWorker
    {
        private static log4net.ILog logger = log4net.LogManager.GetLogger(typeof(UpdateHostWorker));

        private readonly int TFTP_PORT = Convert.ToInt16(System.Configuration.ConfigurationManager.AppSettings["TFTPPort"]);//TFTP通信端口
        //private Comzept.Genesis.NetworkTools.TFTPClient tftpClient;
        public Host Host { get; private set; }
        public int Progress { get; private set; }
        public HostUpdate HostUpdate;
        //private Task task;
        public string RemoteFile;
        public string LocalFile;

        public UpdateHostWorker(Host host, HostUpdate hostUpdate, string tftpServer, string remoteFile, string localFile)
        {
            this.Host = host;
            this.HostUpdate = hostUpdate;
            this.RemoteFile = remoteFile;
            this.LocalFile = localFile;
            //this.tftpClient = new Comzept.Genesis.NetworkTools.TFTPClient(tftpServer, TFTP_PORT);
            //this.tftpClient.ReportCompletedProgress += new Comzept.Genesis.NetworkTools.TFTPClient.ReportCompletedProgressEventDelegate(tftpClient_ReportCompletedProgress);
        }
        /// <summary>
        ///  Puts the specified remote file.
        /// </summary>
        public void Update()
        {
            logger.Error(string.Format("酒店({0})客房({1})主机升级：RemoteFile:{2},LocalFile:{3}", this.Host.SysHotel.Code, this.Host.RoomNumber, this.RemoteFile, this.LocalFile));
            //this.tftpClient.Put(this.RemoteFile, this.LocalFile);
        }

        private void tftpClient_ReportCompletedProgress(string remoteIP, int completedBlock, int totalBlock)
        {
            this.Progress = completedBlock;
            logger.Error(string.Format("收到酒店({0})客房({1})主机升级总块数({2})", this.Host.SysHotel.Code, this.Host.RoomNumber, totalBlock));
        }

        public void Dispose()
        {
            //if (task != null)
            //{
            //    task.Dispose();
            //}
        }
    }
}