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 UpdateHostFTPReceiver : GenericReceiverBase, IUpdateHostFTPReceiver
    {
        private static log4net.ILog logger = log4net.LogManager.GetLogger(typeof(UpdateHostFTPReceiver));
        public IHostUpdateStatusRepository HostUpdateStatusRepository { get; set; }
        private IList<UpdateHostWorker> updateHostList = new List<UpdateHostWorker>();

        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 + "】文件不存在。");
            }
            this.updateHostList.Clear();
            foreach (var host in hosts)
            {
                SendUpdateRequest(host, fileMd5, (byte)fileType, fileHref.Substring(fileHref.IndexOf("/")));

                this.updateHostList.Add(new UpdateHostWorker(host, hostUpdate, MessageIP, fileHref, updateFile)); ;
                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);
            }
        }

        public override void Process(ReceiverContext context)
        {
            int startIndex = StructConverter.SizeOf(context.SystemHeader);
            UpdateHostPacketReply? reply = DecodeUpdateHostPacketReply(context.Data, startIndex);
            if (reply.HasValue)
            {
                //logger.Error(string.Format("收到ftp升级回复命令（{0}:{1}）：{2}", context.RemoteEndPoint.Address.ToString(), context.RemoteEndPoint.Port, Tools.ByteToString(context.Data)));
                var updateHostWorker = updateHostList.FirstOrDefault(r => r.Host.SysHotel.Code == context.SystemHeader.Value.HostNumber.ToHotelCode().ToString() && r.Host.HostNumber == context.SystemHeader.Value.HostNumber.ToString());
                if (updateHostWorker != null)
                {
                    SaveSystemLog(30, "升级主机", string.Format("收到主机({0})升级回复命令，状态：{1}", updateHostWorker.Host.RoomNumber, reply.Value.Status), "收到命令", "RCU", context.RemoteEndPoint.Address.ToString(), updateHostWorker.Host.SysHotel.ID);
                    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;
                    }
                    switch (reply.Value.Status)
                    {
                        case UpdateHostPacketReply.Ready:
                            hostUpdateStatus.Status = 0;
                            break;
                        case UpdateHostPacketReply.Completed:
                            Reply(context);//升级完成回复主机
                            hostUpdateStatus.Status = 1;
                            updateHostList.Remove(updateHostWorker);
                            break;
                        default:
                            hostUpdateStatus.Status = 2;
                            updateHostList.Remove(updateHostWorker);
                            break;
                    }
                    hostUpdateStatus.UpdatedTime = DateTime.Now;
                    HostUpdateStatusRepository.SaveOrUpdate(hostUpdateStatus);
                }
            }
        }
        public override CommandType CommandType
        {
            get { return CommandType.FTPUpdate; }
        }
        #region Private Methods

        /// <summary>
        /// 发送升级指令到RCU主机
        /// </summary>
        /// <param name="host"></param>
        /// <param name="updateFileMd5">升级文件MD5值</param>
        private void SendUpdateRequest(Host host, string updateFileMd5, byte fileType, string fileName)
        {
            byte[] data = CreateUpdateRequestPacket(updateFileMd5, fileType, fileName);
            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, byte fileType, string fileName)
        {
            SystemHeader systemHeader = CreateSystemHeader();
            int headerSize = StructConverter.SizeOf(systemHeader);

            byte[] ipArr = IPAddress.Parse(this.FTPServer).GetAddressBytes();
            byte[] portArr = BitConverter.GetBytes(this.FTPPort);//ftp通讯端口

            //string ftpName = SysSettingRepository.GetValue("FTPName").ToString();
            byte[] bFTPName = System.Text.Encoding.Default.GetBytes(this.FTPName);

            //string ftpPassword = SysSettingRepository.GetValue("FTPPassword").ToString();
            byte[] bFTPPassword = System.Text.Encoding.Default.GetBytes(this.FTPPassword);

            uint[] md5Arr = Tools.MD5StringToUIntArray(updateFileMd5);

            byte[] bFileName = System.Text.Encoding.Default.GetBytes(fileName);

            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(new byte[] { fileType }, 0, 1);//文件类型：0是bin，1是cfg
                stream.Write(BitConverter.GetBytes(this.FTPName.Length), 0, 1);//用户名长度
                stream.Write(BitConverter.GetBytes(this.FTPPassword.Length), 0, 1);//密码长度
                stream.Write(BitConverter.GetBytes(Tools.GetLength(fileName)), 0, 1);//文件名长度
                stream.Write(bFTPName, 0, bFTPName.Length);//用户名
                stream.Write(bFTPPassword, 0, bFTPPassword.Length);//密码
                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
    }
}
