﻿using ICSharpCode.SharpZipLib.BZip2;
using NLog;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Barsik.Core
{
    public class UDPRequester
    {
        public delegate void ResponseHandler(DateTime time, string ip, int port, byte[] data);
        private event ResponseHandler OnDatareceived = delegate { };
        private EngineFactory engineFactory;

        private Logger logger = LogManager.GetCurrentClassLogger();
        public UDPRequester(EngineFactory engineFactory)
        {
            this.engineFactory = engineFactory;
        }

        public virtual async Task<Response> RequestAsync(ServerAddress address, byte[] data, params char[] messageHeader)
        {            
            try
            {
                var cts = new CancellationTokenSource(1000);
                var result = await ReceiveDataAsync(address, data, cts.Token, messageHeader);

                return new Response
                {
                    Data = result.Data,
                    IsTimeout = false,
                    Ping = result.Ping,
                    MessageHeaderChar = result.MessageHeaderChar,
                };
            }
            catch (OperationCanceledException)
            {
                return new Response
                {
                    Data = null,
                    IsTimeout = true,
                    Ping = -1,
                };
            }
        }

        private async Task<ReceiveDataInfo> ReceiveDataAsync(ServerAddress address, byte[] data, CancellationToken ct, params char[] header)
        {
            var udpClient = new UdpClient();
            var ipAddr = IPAddress.Parse(address.Ip);
            try
            {
                udpClient.Connect(ipAddr, address.Port);

                var sendTime = DateTime.Now;
                await udpClient.SendAsync(data, data.Length);

                Packet packet = null;            
                return await Task.Run(async () =>
                {
                    while (true)
                    {
                        var clientResult = await udpClient.ReceiveAsync().WithCancellation(ct);
                        var remoteIp = clientResult.RemoteEndPoint.Address.ToString();
                        var remotePort = clientResult.RemoteEndPoint.Port;
                        var receiveDate = DateTime.Now;
                        var packetReader = new PacketReader(clientResult.Buffer);
                        var packetheader = packetReader.ReadLong();
                        if (packet == null)
                        {
                            packet = engineFactory.CreatePacket(packetheader);
                        }
                        packet.AddData(clientResult.Buffer);
                        if (packet.IsCompleted)
                        {
                            var messageReader = new PacketReader(packet.Payload);
                            var messageheader = messageReader.ReadByte();
                            var messageHeaderChar = Convert.ToChar(messageheader);
                            if (header.Contains(messageHeaderChar))
                            {
                                var result = new ReceiveDataInfo
                                {
                                    Ping = (int)(DateTime.Now - sendTime).TotalMilliseconds,
                                    Data = packet.Payload,
                                    MessageHeaderChar = messageHeaderChar,
                                };
                                return result;
                            }
                        }
                    }
                });
            }
            catch 
            {
                throw new OperationCanceledException();
            }
            finally
            {
                udpClient.Close();
            }
        }

        class ReceiveDataInfo
        {
            public int Ping { get; set; }

            public byte[] Data { get; set; }

            public char MessageHeaderChar { get; set; }
        }
    }
}
