using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Inspectron.HawkEye.UDPB
{
    public class UDPBSequence
    {
        private readonly uint _sequenceId;
        private readonly UDPBSocket _socket;
        private readonly ConcurrentQueue<byte[]> _receiveQueue;
        private readonly byte[] _reconstructionBuffer = new byte[10 * 1024 * 1024]; //10 mb


        public bool IsFinished { get; private set; }
        private readonly UDPBPacket[] _buffer = new UDPBPacket[10000];
        private List<uint> _nakPackets;

        public UDPBSequence(uint sequenceId, UDPBSocket socket, ConcurrentQueue<byte[]> receiveQueue)
        {
            _sequenceId = sequenceId;
            _socket = socket;
            _receiveQueue = receiveQueue;
        }

        public uint SequenceId => _sequenceId;


        public void AddData(UDPBPacket dataPacket)
        {
            if (dataPacket.SequenceId != _sequenceId)
            {
                //error?
                Console.WriteLine($"Error! Unexpected packet sequence {dataPacket.SequenceId} expecting {_sequenceId}");
                return;
            }


            _buffer[dataPacket.MessageId] = dataPacket;

            if (IsReconstructing)
            {
                _nakPackets.Remove(dataPacket.MessageId);
                Console.WriteLine($"packet {dataPacket.MessageId} restored");

            }
            
        }

        

        public byte[] LastReconstructedBuffer { get; private set; }

        
        public bool FinishSequence(uint packets, out List<uint> missingPacketIds, 
            out int dataSize)
        {
            
            missingPacketIds = new List<uint>();
            
            dataSize = 0;
            var defaultDataSize = UDPBQueue.PACKET_SIZE - UDPBPacket.packetHeaderSize;
            //validate
            for (uint i = 1; i < packets; i++)
                if (_buffer[i]==null)
                {
                    missingPacketIds.Add(i);
                    dataSize += defaultDataSize;
                    //error
                }
                else
                {
                    var packetLen = _buffer[i].Payload.Length;
                    Buffer.BlockCopy(_buffer[i].Payload, 0, _reconstructionBuffer, dataSize, packetLen);
                    dataSize += packetLen;
                }

            if (missingPacketIds.Count > 0)
            {
                Console.WriteLine($"Error! Missing packets:{missingPacketIds.Count} seq:{_sequenceId}");
                //send Nak
                
                
                
                _nakPackets = missingPacketIds;
                var nakLine = _nakPackets.Select(x => x.ToString()).Aggregate((s1, s2) => s1 + "," + s2);
                Console.WriteLine($"Nak: {nakLine}");
                RequestMissingPackets(missingPacketIds);
                RequestAgainIn(TimeSpan.FromMilliseconds(10), missingPacketIds);
                IsReconstructing = true;
                
                return false;
            }
            else
            {
                var packet = new UDPBPacket();
                packet.SequenceId = SequenceId;
                packet.Type = UDPBPacket.EPacketType.Ok;
                IsFinished = true;
                Console.WriteLine($"Finished seq:{_sequenceId}");
                _socket.SendPacket(packet);
                LastReconstructedBuffer = new byte[dataSize];
                Array.Copy(_reconstructionBuffer, 0, LastReconstructedBuffer, 0, dataSize);
                _receiveQueue.Enqueue(LastReconstructedBuffer);
                return true;
            }

            
        }
        private bool _shouldRequest=false;
        private void RequestAgainIn(TimeSpan timeout, List<uint> missingPacketIds)
        {
            _shouldRequest = true;



        }

        private void RequestMissingPackets(List<uint> missingPacketIds)
        {
            MemoryStream ms = new MemoryStream();
            BinaryWriter sw = new BinaryWriter(ms);
            var waitFor = missingPacketIds.Take(10).ToList();
            sw.Write(waitFor.Count);
            foreach (uint id in waitFor)
            {
                sw.Write(id);
            }

            var packet = new UDPBPacket();
            packet.SequenceId = SequenceId;
            packet.Payload = ms.ToArray();
            packet.Length = (uint) packet.Payload.Length;
            packet.Type = UDPBPacket.EPacketType.Nak;
            _socket.SendPacket(packet);
        }

        public bool IsReconstructing { get; private set; }
    }
}