
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using static ILRewriteProfilerHelper.ProfilerHelper;

namespace ILRewriteProfilerHelper

{
    public class RUDP
    {
        public const ushort RUDP_VERSION = 1;
        public const int RUDP_TIMEOUT = 2000;
        public const int RUDP_WINDOW = 1;
        public const int RUDP_DATA = 1;
        public static int count = 0;
      //  public static int count1 = 0;
      //  public int counter = 0;
        public const int RUDP_ACK = 2;
        public const int RUDP_SYN = 4;
        public int RUDP_MAXRETRANS =1 ;
        public delegate int RUDPSocketHandler(int fd, object arg);
        public const int SYN_SENT = 0;
        public const int OPENING = 1;
        public const int OPEN = 2;
        public Sockets sockets_list_head = null;
        public static EventData ee = null;
        public static EventData ee_timers = null;
        public enum EventDataType
        {
            EVENT_FD,
            EVENT_TIME
        }
        public enum RudpEvent
        {
            RUDP_EVENT_TIMEOUT,
            RUDP_EVENT_CLOSED
        }

        public class EventData
        {
            public EventData e_next;
            public RUDPSocketHandler e_fn;
            public EventDataType e_type;
            public int e_fd;
            public DateTime e_time;
            public object e_arg;
        }
        [Serializable]
        public struct RUDPHeader
        {
            public ushort type;
            public ushort version;
            public int seqno;
            public int duplicate_flag;
        }


        //The fd_set class represents a set of file descriptors, commonly used in networking applications for managing socket descriptors
        public class fd_set
        {
            private HashSet<int> fileDescriptors;

            public fd_set()
            {
                fileDescriptors = new HashSet<int>();
            }

            public void clr(int fd)
            {
                fileDescriptors.Remove(fd);
            }

            public bool IsSet(int fd)
            {
                return fileDescriptors.Contains(fd);
            }

            public void Set(int fd)
            {
                fileDescriptors.Add(fd);
            }

            public void Zero()
            {
                fileDescriptors.Clear();
            }

            // Additional method to get the count of file descriptors in the set
            public int count()
            {
                return fileDescriptors.Count;
            }

        }

        public class Sockets
        {
            public int rsock;
            public Session sessions_list_head;
            public Sockets next;
        }

        public class Data
        {
            public dynamic item;
            public int len;
            public Data next;
        }

        public class DataPool
        {
            private Data head;

            public void AddData(Data dataItem)
            {


                if (dataItem == null)
                {
                    throw new ArgumentNullException(nameof(dataItem));
                }
                if (head == null)
                {
                    head = dataItem;
                }
                else
                {
                    Data current = head;
                    while (current.next != null)
                    {
                        current = current.next;
                    }
                    current.next = dataItem;
                }
            }
            public void ClearDataPool()
            {
                head = null;
                // At this point, the Data objects in the pool become eligible for garbage collection
                // and will be automatically reclaimed by the garbage collector.
            }
        }

        [Serializable]
        public class RUDPPacket
        {
            public RUDPHeader header;
            public int payload_length;
            public byte[] payload = new byte[54 * 1024];
           
        }
        public class RUDPDataPool
        {
            private List<RUDPPacket> packetList;

            public RUDPDataPool()
            {
                packetList = new List<RUDPPacket>();
            }

            public void AddData(RUDPPacket packet)
            {
                if (packet == null)
                {
                    throw new ArgumentNullException(nameof(packet));
                }

                packetList.Add(packet);
            }

            public void ClearDataPool()
            {
                packetList.Clear();

                // At this point, the RUDPPacket objects in the list become eligible for garbage collection
                // and will be automatically reclaimed by the garbage collector.
            }
        }

        public class SenderSession
        {
            public int status;
            public byte seqNo;
            public RUDPPacket[] sliding_window = new RUDPPacket[RUDP_WINDOW];
            public int[] retransmission_attempts = new int[RUDP_WINDOW];
            public Data data_queue;
            public object[] data_timeout_arg = new object[RUDP_WINDOW];
            public object syn_timeout_arg;
            public int syn_retransmit_attempts;

        }

        public class ReceiverSession
        {
            public int status;
            public byte expected_seqNo;

        }



        public class Session
        {
            public SenderSession sender;
            public ReceiverSession receiver;
            public IPEndPoint address;
            public Session next;


        }

        public class TimeoutArgs
        {
            public int fd;
            public RUDPPacket packet;
            public IPEndPoint recipient;

        }


        public static int TimerCmp(TimeSpan time1, TimeSpan time2)
        {
            return TimeSpan.Compare(time1, time2);
        }

    


        //   The EventTimeout method is responsible for adding a timeout event to the event data linked list.This timeout event triggers after a specified time(t) and executes the provided handler function(fn) with the given arguments(arg)
        public void EventTimeout(DateTime t, RUDPSocketHandler fn, object arg)
        {

            try
            {
                EventData e = new EventData
                {
                    e_fn = fn,
                    e_arg = arg,
                    e_type = EventDataType.EVENT_TIME,
                    e_time = t
                };

                //EventData e_prev = null;
                //EventData e1 = ee_timers;

                //while (e1 != null && DateTime.Compare(e.e_time, e1.e_time) >= 0)
                //{
                //    e_prev = e1;
                //    e1 = e1.e_next;
                //}
                //e.e_next = e1;
                //if (e_prev != null)
                //{
                //    e_prev.e_next = e;
                //}
                //else
                //{
                //    // If e_prev is null, it means the new node should be the head of the list
                //    ee_timers = e;
                //}

                EventData e1;
                ref EventData e_prev = ref ee_timers;
                for (e1 = ee_timers; e1 != null; e1 = e1.e_next)
                {
                    if (DateTime.Compare(e.e_time, e1.e_time) < 0)
                        break;
                    e_prev = ref e1.e_next;
                }
                e.e_next = e1;
                e_prev = e;

                

            }
            catch (Exception ex)
            {
                var LineNumber = new StackTrace(ex, true).GetFrame(0).GetFileLineNumber();
                logger.LogError($"Exception Occurred Exception: [" + ex.Message + "---line no=" + LineNumber); return;
            }
        }


        //This method allows for the dynamic removal of specific events from the event data linked list, enabling more flexible event management within the event loop.
        public int EventDelete(ref EventData firstp, RUDPSocketHandler fn, object arg)
        {
            EventData e;
            ref EventData e_prev = ref firstp;
            for (e = firstp; e != null; e = e.e_next)
            {
                if (fn == e.e_fn && arg == e.e_arg)
                {
                    e_prev = e.e_next;

                    return 0;
                }
                e_prev = ref e.e_next;
            }
            return -1;
        }

        public int EventTimeoutDelete(RUDPSocketHandler fn, object arg)
        {
            return EventDelete(ref ee_timers, fn, arg);
        }

       
        //The EventFd method seems to be responsible for registering file descriptor events along with their corresponding event handler functions in an event loop. 
        public static int EventFd(int fd, RUDPSocketHandler fn, object arg)
        {

            try
            {
                EventData e = new EventData();
                e.e_fd = fd;
                e.e_fn = fn;
                e.e_arg = arg;
                e.e_type = EventDataType.EVENT_FD;
                e.e_next = ee;
                ee = e;
                return 0;
            }
            catch (Exception ex)
            {
                var LineNumber = new StackTrace(ex, true).GetFrame(0).GetFileLineNumber();
                logger.LogError($"Exception Occurred Exception: [" + ex.Message + "---line no=" + LineNumber); return -1;
            }
        }

        //public static TimeSpan GetTimeOfDay()
        //{
        //    return DateTime.Now.TimeOfDay;
        //}

        //public static TimeSpan SubtractTimeSpans(TimeSpan time1, TimeSpan time2)
        //{
        //    return time1 - time2;
        //}

       
       // The EventLoop method seems to implement an event loop that continuously monitors events and handles them accordingly
        public static bool EventLoop()
        {
            try
            {
                EventData e;
                EventData e1;
                fd_set fdset = new fd_set();
                int n = 0;
                DateTime t0;
                TimeSpan t;
            

                while (ee != null)
                {
                    fdset.Zero();
                    for (e = ee; e != null; e = e.e_next)
                    {
                        if (e.e_type == EventDataType.EVENT_FD)
                        {
                            fdset.Set(e.e_fd);
                        }

                    }

                    if (ee_timers != null)
                    {

                        // t0 = GetTimeOfDay();
                        //  t = SubtractTimeSpans(ee_timers.e_time, t0);

                        t0 = DateTime.Now; // Get current time
                         t = ee_timers.e_time - t0; // Subtract DateTime objects to get a TimeSpan
      
                        if (t.TotalSeconds < 0)
                        {

                            n = 0;
                        }
                        else
                        {
                            n = fdset.count() > 0 ? SelectSockets(fdset, t, UDPclass.globalIP.socketUDP) : 0;
                        }
                    }
                    else

                        n = fdset.count() > 0 ? SelectSockets(fdset, TimeSpan.Zero, UDPclass.globalIP.socketUDP) : 0;
                  
                    if (n == 0)
                    {  /* Timeout */


                        e = ee_timers;

                        if (ee_timers != null)
                        {
                       
                            ee_timers = ee_timers.e_next;

                            if (e.e_fn(0, e.e_arg) < 0)
                            {
                                return false;
                            }
                            continue;
                        }
                    }
                    e = ee;
                    while (e != null)
                    {
                        e1 = e.e_next;
                        if (e.e_type == EventDataType.EVENT_FD && fdset.IsSet(e.e_fd))
                        {
                            int result = e.e_fn(e.e_fd, e.e_arg);
                            if (result < 0)
                            {
                                return false; // If event function returns a negative value, return -1 indicating an error
                            }
                        }
                        e = e1;
                    }
                }
                return true;
            }
            catch (Exception ex)
            {
                var LineNumber = new StackTrace(ex, true).GetFrame(0).GetFileLineNumber();
                logger.LogError($"Exception Occurred Exception: [" + ex.Message + "---line no=" + LineNumber); return false;
            }
        }


        //This method allows for monitoring sockets for readiness using the select mechanism and can be used for implementing asynchronous I/O operations in network programming scenarios
        public static int SelectSockets(fd_set fdset, TimeSpan timeout, Socket configuredSocket)
        {
            List<Socket> socketList = new List<Socket>();
            EventData e = ee;

            while (e != null)
            {
                if (e.e_type == EventDataType.EVENT_FD && fdset.IsSet(e.e_fd))
                {
                    // Replace this with your actual socket setup
                    socketList.Add(configuredSocket);
                }
                e = e.e_next;
            }

            Socket.Select(socketList, null, null, (int)timeout.TotalMilliseconds);
            return socketList.Count;
        }


        // this method sets up a new RUDP socket, initializes its data structures, and registers a callback event for handling incoming packets. It ensures that the socket is ready to send and receive RUDP packets on the specified port
        public int rudp_socket(int port)
        {
            try
            {

                int sockfd = 0;
                IPEndPoint address = new IPEndPoint(IPAddress.Any, port);
                UDPclass.globalIP.socketUDP.Bind(address);
                int socket = ((IPEndPoint)UDPclass.globalIP.socketUDP.LocalEndPoint).Port;

                // Create a new sockets struct and add it to the list of sockets
                Sockets newSocket = new Sockets
                {
                    rsock = socket,
                    sessions_list_head = null,
                    next = null,

                };

                if (sockets_list_head == null)
                {
                    sockets_list_head = newSocket;
                }
                else
                {
                    Sockets temp = sockets_list_head;

                    while (temp.next != null)
                    {
                        temp = temp.next;
                    }
                    temp.next = newSocket;
                }

                // Register callback event for this socket descriptor
               if( EventFd(socket, receiveCallback, sockfd) < 0)
                {
                    logger.LogError("Error in registering callback function");                       
                }
                return socket;
            }
            catch (Exception ex)
            {
                var LineNumber = new StackTrace(ex, true).GetFrame(0).GetFileLineNumber();
                logger.LogError($"Exception Occurred Exception: [" + ex.Message + "---line no=" + LineNumber); return -1;
            }
        }


        private byte[] buffer = new byte[65507];
      //  private RUDPPacket reusablePacket1 = new RUDPPacket();
      //  private RUDPHeader reusableheader1 = new RUDPHeader();
        public UDPclass UDP = null;
        RUDPDataPool rudpdataPool = null;
        private ReceiverSession reusableReceiverSession = new ReceiverSession();


       //  this method handles the reception of RUDP packets, manages sessions, processes packet types, and logs relevant information.It ensures reliable communication over UDP sockets
        public int receiveCallback(int file, object arg)
        {

            try
            {

                if (UDP == null)
                {
                    IntPtr procnameptr = NativeMethods.getprocname_Bridge();
                    string procname = Marshal.PtrToStringAnsi(procnameptr);
                    UDP = new UDPclass(procname);
                }

                RUDPPacket received_packet = null;
                int bytesRead = UDPclass.globalIP.socketUDP.Receive(buffer);
                received_packet = deSerializePacket(buffer);
                RUDPHeader rudpheader = received_packet.header;
                byte[] payloadBytes = received_packet.payload;
                string payloadString = Encoding.ASCII.GetString(payloadBytes);
                UDP.parseJSON(ref payloadString);
                string type;

                switch (rudpheader.type)
                {
                    case 1:
                        type = "DATA";
                        break;
                    case 2:
                        type = "ACK";
                        break;
                    case 4:
                        type = "SYN";
                        break;
                    default:
                        type = "BAD";
                        break;
                }

                logger.LogError($"Received {type} packet from {UDPclass.globalIP.GlobalEndPoint.Address}:{UDPclass.globalIP.GlobalEndPoint.Port} seq number={rudpheader.seqno} on socket={file}");

                if (sockets_list_head == null)
                {
                 logger.LogError($"sockets_list_head == null");

                    return -1;
                }
                else
                {
                    Sockets temp = sockets_list_head;
                    while (temp != null)
                    {
                        if (temp.rsock == file)
                        {

                            break;
                        }
                        temp = temp.next;
                    }

                    if (temp.rsock == file)
                    {
                        if (temp.sessions_list_head == null)
                        {
                            if (rudpheader.type == RUDP_SYN)
                            {
                                // SYN Received. Create a new session at the head of the list
                                Session new_session = reusableSession;
                                new_session.address = UDPclass.globalIP.GlobalEndPoint;
                                new_session.next = null;
                                new_session.sender = null;
                                new_session.receiver = reusableReceiverSession;
                                new_session.receiver.status = OPENING;
                                new_session.receiver.expected_seqNo = (byte)(rudpheader.seqno + 1);
                                temp.sessions_list_head = new_session;
                                new_session.receiver = null; // Set to null if there's no resource cleanup needed
                                new_session = null; // Set to null to release the reference
                               RUDPPacket reusablePacket1 = new RUDPPacket();
                               RUDPHeader reusableheader1 = new RUDPHeader();
                                reusableheader1.type = RUDP_ACK;
                                reusableheader1.version = RUDP_VERSION;
                                reusableheader1.seqno = (int)new_session.receiver.expected_seqNo;
                                reusablePacket1.header = reusableheader1;
                                reusablePacket1.payload_length = 0;
                                rudpdataPool.AddData(reusablePacket1);
                                send_packet(1, file, reusablePacket1, UDPclass.globalIP.GlobalEndPoint);
                                rudpdataPool.ClearDataPool();
                            }
                            else
                            {
                                // No sessions exist, and we got a non-SYN packet
                                // We ignore this
                            }
                        }
                        else
                        //  if (temp.sessions_list_head != null)
                        {
                            int sessionFound = 0;

                            // Check if we already have a session for this peer
                            Session temp2 = temp.sessions_list_head;
                            Session last_session = null;

                            while (temp2 != null)
                            {
                                if (temp2.next == null)
                                {
                                    last_session = temp2;
                                }

                                if (temp2.address.Address.Equals(UDPclass.globalIP.GlobalEndPoint.Address) && temp2.address.Port == UDPclass.globalIP.GlobalEndPoint.Port)
                                {
                                    // Found an existing session
                                    sessionFound = 1;
                                    break;
                                }
                                temp2 = temp2.next;
                            }

                            if (sessionFound == 0)
                            {
                                if (rudpheader.type == RUDP_SYN)
                                {
                                    // SYN Received. Create a new session at the head of the list
                                    Session new_session = reusableSession;
                                    new_session.address = UDPclass.globalIP.GlobalEndPoint;
                                    new_session.next = null;
                                    new_session.sender = null;
                                    new_session.receiver = reusableReceiverSession;
                                    ReceiverSession new_receiver_session = reusableReceiverSession;
                                    new_receiver_session.status = OPENING;
                                    new_receiver_session.expected_seqNo = (byte)(rudpheader.seqno + 1);
                                    new_session.receiver = new_receiver_session;
                                    last_session.next = new_session;
                                    RUDPPacket reusablePacket1 = new RUDPPacket();
                                    RUDPHeader reusableheader1 = new RUDPHeader();
                                    reusableheader1.type = RUDP_ACK;
                                    reusableheader1.version = RUDP_VERSION;
                                    reusableheader1.seqno = (int)new_session.receiver.expected_seqNo;
                                    reusablePacket1.header = reusableheader1;
                                    reusablePacket1.payload_length = 0;
                                    rudpdataPool.AddData(reusablePacket1);
                                    send_packet(1, file, reusablePacket1, UDPclass.globalIP.GlobalEndPoint);
                                    rudpdataPool.ClearDataPool();
                                }
                                else
                                {
                                    // Session does not exist, and we received a non-SYN
                                    // We ignore it
                                }
                            }
                            else
                           //   if (sessionFound == 1)
                            {
                                // We did find a session for this peer
                                if (rudpheader.type == RUDP_SYN)
                                {
                                    if (temp2.receiver == null || temp2.receiver.status == OPENING)
                                    {
                                        // We have a sender session already with this peer, but not a receiver session
                                        // So we create a receiver session with the peer
                                        ReceiverSession new_receiver_session = reusableReceiverSession;
                                        new_receiver_session.expected_seqNo = (byte)(rudpheader.seqno + 1);
                                        new_receiver_session.status = OPENING;
                                        temp2.receiver = new_receiver_session;
                                        RUDPPacket reusablePacket1 = new RUDPPacket();
                                        RUDPHeader reusableheader1 = new RUDPHeader();
                                        reusableheader1.type = RUDP_ACK;
                                        reusableheader1.version = RUDP_VERSION;
                                        reusableheader1.seqno = (int)temp2.receiver.expected_seqNo;
                                        reusablePacket1.header = reusableheader1;
                                        reusablePacket1.payload_length = 0;
                                        rudpdataPool.AddData(reusablePacket1);
                                        send_packet(1, file, reusablePacket1, UDPclass.globalIP.GlobalEndPoint);
                                        rudpdataPool.ClearDataPool();
                                        temp2 = null;
                                    }
                                    else
                                    {
                                        // Received a SYN when there is already an active receiver session, so we ignore it
                                    }
                                }

                                if (rudpheader.type == RUDP_ACK)
                                {
                                    // We receive an ACK
                                    int ack_sqn = received_packet.header.seqno;

                                    if (temp2.sender.status == SYN_SENT)
                                    {
                                        // This is an ACK for a SYN
                                        int syn_sqn = (int)temp2.sender.seqNo;
                                        if (ack_sqn - 1 == syn_sqn)
                                        {
                                            // Deleting the retransmission timeout
                                            EventTimeoutDelete(TimeoutCallback, temp2.sender.syn_timeout_arg);            
                                            temp2.sender.status = OPEN;
                                            while (temp2.sender.data_queue != null)
                                            {
                                                if (temp2.sender.sliding_window[RUDP_WINDOW - 1] != null)
                                                {

                                                    break;
                                                }
                                                else
                                                {
                                                    int index = 0;
                                                    int i;

                                                    // Finding the first unused window slot
                                                    for (i = RUDP_WINDOW - 1; i >= 0; i--)
                                                    {
                                                        if (temp2.sender.sliding_window[i] == null)
                                                        {
                                                            index = i;
                                                        }
                                                    }

                                                    // Send the packet and add it to the window and remove it from the queue
                                                    RUDPPacket reusablePacket1 = new RUDPPacket();
                                                    RUDPHeader reusableheader1 = new RUDPHeader();
                                                    reusableheader1.type = RUDP_DATA;
                                                    reusableheader1.version = RUDP_VERSION;
                                                    reusableheader1.seqno = ++syn_sqn;
                                                    temp2.sender.seqNo += 1;
                                                    reusablePacket1.header = reusableheader1;
                                                    reusablePacket1.payload_length = temp2.sender.data_queue.len;
                                                    Buffer.BlockCopy(temp2.sender.data_queue.item, 0, reusablePacket1.payload, 0, reusablePacket1.payload_length);
                                                    temp2.sender.sliding_window[index] = reusablePacket1;
                                                    temp2.sender.retransmission_attempts[index] = 0;
                                                    Data temp3 = temp2.sender.data_queue;
                                                    temp2.sender.data_queue = temp2.sender.data_queue.next;

                                                    if (temp3 != null)
                                                    {
                                                        temp3.item = null;
                                                        temp3.len = 0;
                                                        temp3 = null;
                                                    }
                                                    rudpdataPool.AddData(reusablePacket1);
                                                    send_packet(0, file, reusablePacket1, UDPclass.globalIP.GlobalEndPoint);
                                                    rudpdataPool.ClearDataPool();                                          
                                                }
                                            }
                                        }
                                    }
                                    else if (temp2.sender.status == OPEN)
                                    {

                                        // This is an ACK for DATA
                                        if (temp2.sender.sliding_window[0] != null)
                                        {
                                            // Checking if ACK is for the first item in the window
                                            if (temp2.sender.sliding_window[0].header.seqno == rudpheader.seqno - 1)
                                            {
                                                EventTimeoutDelete(TimeoutCallback, temp2.sender.data_timeout_arg[0]);
                                                temp2.sender.sliding_window[0] = null;
                                                if (RUDP_WINDOW == 1)
                                                {
                                                    temp2.sender.sliding_window[0] = null;
                                                    temp2.sender.retransmission_attempts[0] = 0;
                                                    temp2.sender.data_timeout_arg[0] = null;
                                                }
                                                else
                                                {
                                                    for (int i = 0; i < RUDP_WINDOW - 1; i++)
                                                    {
                                                        temp2.sender.sliding_window[i] = temp2.sender.sliding_window[i + 1];
                                                        temp2.sender.retransmission_attempts[i] = temp2.sender.retransmission_attempts[i + 1];
                                                        temp2.sender.data_timeout_arg[i] = temp2.sender.data_timeout_arg[i + 1];

                                                        if (i == RUDP_WINDOW - 2)
                                                        {
                                                            temp2.sender.sliding_window[i + 1] = null;
                                                            temp2.sender.retransmission_attempts[i + 1] = 0;
                                                            temp2.sender.data_timeout_arg[i + 1] = null;
                                                        }
                                                    }
                                                }

                                                while (temp2.sender.data_queue != null)
                                                {
                                                    if (temp2.sender.sliding_window[RUDP_WINDOW - 1] != null)
                                                    {
                                                        break;
                                                    }
                                                    else
                                                    {
                                                        int index = 0;
                                                        int i;

                                                        // Finding the first unused window slot
                                                        for (i = RUDP_WINDOW - 1; i >= 0; i--)
                                                        {
                                                            if (temp2.sender.sliding_window[i] == null)
                                                            {
                                                                index = i;
                                                            }
                                                        }

                                                        // Send the packet and add it to the window and remove it from the queue
                                                        RUDPPacket reusablePacket1 = new RUDPPacket();
                                                        RUDPHeader reusableheader1 = new RUDPHeader();
                                                        reusableheader1.type = RUDP_DATA;
                                                        reusableheader1.version = RUDP_VERSION;
                                                        temp2.sender.seqNo = (byte)(temp2.sender.seqNo + 1);
                                                        reusableheader1.seqno = (int)temp2.sender.seqNo;
                                                        reusablePacket1.header = reusableheader1;
                                                        reusablePacket1.payload_length = temp2.sender.data_queue.len;
                                                        Buffer.BlockCopy(temp2.sender.data_queue.item, 0, reusablePacket1.payload, 0, reusablePacket1.payload_length);
                                                        temp2.sender.sliding_window[index] = reusablePacket1;
                                                        temp2.sender.retransmission_attempts[index] = 0;
                                                        Data temp3 = temp2.sender.data_queue;
                                                        temp2.sender.data_queue = temp2.sender.data_queue.next;

                                                        if (temp3 != null)
                                                        {
                                                            temp3.item = null;
                                                            temp3.len = 0;
                                                            temp3 = null;
                                                        }
                                                        rudpdataPool.AddData(reusablePacket1);
                                                        send_packet(0, file, reusablePacket1, UDPclass.globalIP.GlobalEndPoint);
                                                        rudpdataPool.ClearDataPool();
                                                    }
                                                }
                                           }
                                        }
                                    }
                                }
                            }
                            temp2 = null;
                        }
                    }
                    temp = null;
                }
            }
            catch (SocketException ex)
            {
                if (ex.SocketErrorCode == SocketError.WouldBlock)
                {
                    logger.LogError("Socket Exception: " + ex.Message);
                    return 0;
                }
                else
                {
                    logger.LogError("Socket Exception: " + ex.Message);
                    return -1;
                }
            }
            return 0;
        }
        
      
        private static Random random = new Random();
        private Session reusableSession = new Session();
        private SenderSession reusableSenderSession = new SenderSession();
        private Session reusableSession1 = new Session();
        private SenderSession reusableSenderSession1 = new SenderSession();
     //   private RUDPPacket reusablePacket = new RUDPPacket();
     //   private RUDPHeader reusableheader = new RUDPHeader();

        //this method handles the transmission of data over RUDP, managing sessions, packet construction, and error handling. It ensures reliable data transmission over unreliable networks.
        public int rudp_sendto(int rsocket, byte[] data, int len, IPEndPoint to)
        {

            try
            {
                
                DataPool dataPool = new DataPool();
                rudpdataPool = new RUDPDataPool();
                int newSessionCreated = 1;
                int seqNo = 0;

                if (sockets_list_head == null)
                {
                    logger.LogInfo("Error: Attempt to send on an invalid socket. No sockets in the list");
                    return -1;
                }
                else
                {
                    // Find the correct socket in our list
                    Sockets temp = sockets_list_head;

                    while (temp != null)
                    {
                        if (temp.rsock == rsocket)
                        {
                            break;
                        }
                        temp = temp.next;
                    }
                  
                    if (temp.rsock == rsocket)
                    {
                        // We found the correct socket, now see if a session already exists for this peer
                        Data reusableDataItem = new Data();
                        reusableDataItem.item = new byte[len];
                        Buffer.BlockCopy(data, 0, reusableDataItem.item, 0, len); reusableDataItem.len = len;
                        reusableDataItem.next = null;

                        if (temp.sessions_list_head == null)
                        {

                            // The list is empty, so we create a new session at the head of the list
                            // This will be a sender address
                            Session newSession = reusableSession1;
                            newSession.address = to;
                            newSession.next = null;
                            newSession.receiver = null;
                            newSession.sender = reusableSenderSession1;
                            newSession.sender.status = SYN_SENT;
                            newSession.sender.seqNo = (byte)random.Next();
                            newSession.sender.data_queue = reusableDataItem;

                            for (int i = 0; i < RUDP_WINDOW; i++)
                            {
                                newSession.sender.retransmission_attempts[i] = 0;
                                newSession.sender.data_timeout_arg[i] = 0;
                                newSession.sender.sliding_window[i] = null;
                            }

                            newSession.sender.syn_retransmit_attempts = 0;
                            seqNo = (int)newSession.sender.seqNo;
                            temp.sessions_list_head = newSession;
                        }
                        else
                        {
                            int sessionFound = 0;

                            // Check if we already have a session for this peer
                            Session temp2 = temp.sessions_list_head;
                            Session last_in_list = null;
                            while (temp2 != null)
                            {

                                if (temp2.address.Address.Equals(to.Address) &&
                                        temp2.address.Port == to.Port &&
                                        temp2.address.AddressFamily == to.AddressFamily)
                                {

                                    // If the window has any free slots and the data queue is empty and the state is OPEN, we will send the packet
                                    int dataIsQueued = 0;
                                    int weMustQueue = 1;

                                    if (temp2.sender == null)
                                    {
                                        SenderSession newSenderSession = reusableSenderSession1;
                                        newSenderSession.data_queue = null;
                                        newSenderSession.status = SYN_SENT;
                                        newSenderSession.seqNo = (byte)random.Next();

                                        for (int i = 0; i < RUDP_WINDOW; i++)
                                        {
                                            newSenderSession.retransmission_attempts[i] = 0;
                                            newSenderSession.data_timeout_arg[i] = 0;
                                            newSenderSession.sliding_window[i] = null;
                                        }
                                        newSenderSession.syn_retransmit_attempts = 0;
                                        seqNo = (int)newSenderSession.seqNo;
                                        temp2.sender = newSenderSession;
                                         RUDPPacket reusablePacket = new RUDPPacket();
                                        RUDPHeader reusableheader = new RUDPHeader();
                                        reusableheader.type = RUDP_SYN;
                                        reusableheader.version = RUDP_VERSION;
                                        reusableheader.seqno = seqNo;
                                        reusablePacket.header = reusableheader;
                                        reusablePacket.payload_length = 0;
                                        rudpdataPool.AddData(reusablePacket);
                                        send_packet(0, rsocket, reusablePacket, to);
                                        newSessionCreated = 0; // Don't send the SYN twice
                                        rudpdataPool.ClearDataPool();
                                        break;
                                    }

                                    if (temp2.sender.data_queue != null)
                                    {
                                        dataIsQueued = 1;
                                    }

                                    if (temp2.sender.status == OPEN && dataIsQueued == 0)
                                    {
                                        int i;
                                        for (i = 0; i < RUDP_WINDOW; i++)
                                        {
                                            if (temp2.sender.sliding_window[i] == null)
                                            {
                                                RUDPPacket reusablePacket = new RUDPPacket();
                                                RUDPHeader reusableheader = new RUDPHeader();
                                                reusableheader.type = RUDP_DATA;
                                                reusableheader.version = RUDP_VERSION;
                                                temp2.sender.seqNo = (byte)(temp2.sender.seqNo + 1);
                                                reusableheader.seqno = (int)temp2.sender.seqNo;
                                                reusablePacket.header = reusableheader;
                                                reusablePacket.payload_length = len;
                                                Buffer.BlockCopy(data, 0, reusablePacket.payload, 0, reusablePacket.payload_length);
                                                temp2.sender.sliding_window[i] = reusablePacket;
                                                temp2.sender.retransmission_attempts[i] = 0;
                                                rudpdataPool.AddData(reusablePacket);

                                                send_packet(0, rsocket, reusablePacket, to);
                                                weMustQueue = 0;
                                                rudpdataPool.ClearDataPool();                                          
                                                break;
                                            }
                                            else
                                            {
                                           }
                                        }
                                    }

                                    if (weMustQueue == 1)
                                    {
                                        // If so, queue the data
                                        if (temp2.sender.data_queue == null)
                                        {
                                            // First entry in the data queue
                                            temp2.sender.data_queue = reusableDataItem;
                                        }
                                        else
                                        {
                                            // Add to the end of the data queue
                                            Data temp3 = temp2.sender.data_queue;
                                            while (temp3.next != null)
                                            {
                                                temp3 = temp3.next;
                                            }
                                            temp3.next = reusableDataItem;
                                            temp3 = null;
                                        }
                                    }
                                    sessionFound = 1;
                                    newSessionCreated = 0;
                                    break;
                                }

                                if (reusableDataItem != null)
                                {
                                    dataPool.ClearDataPool();
                                    reusableDataItem.item = null;
                                    reusableDataItem.len = 0;
                                }
                                if (temp2.next == null)
                                    last_in_list = temp2;
                                temp2 = temp2.next;
                            }
                            if (sessionFound == 0)
                            {
                                // If not, create a new session and send a SYN
                                Session newSession = reusableSession1;
                                newSession.address = to;
                                newSession.next = null;
                                newSession.receiver = null;
                                newSession.sender = reusableSenderSession1;
                                newSession.sender.status = SYN_SENT;
                                newSession.sender.seqNo = (byte)random.Next();

                                for (int i = 0; i < RUDP_WINDOW; i++)
                                {
                                    newSession.sender.retransmission_attempts[i] = 0;
                                    newSession.sender.data_timeout_arg[i] = 0;
                                    newSession.sender.sliding_window[i] = null;
                                }

                                // Initialize SenderSession other properties here
                                newSession.sender.syn_retransmit_attempts = 0;
                                seqNo = (int)newSession.sender.seqNo;
                                temp.sessions_list_head = newSession;

                                // Added to rear of the session list
                                last_in_list.next = newSession;
                                temp = null;
                            }
                        }
                    }
                    else
                    {
                        logger.LogInfo("Error: Attempt to send on an invalid socket. Socket not found");
                        return -1;
                    }
                  temp = null;
                }

                if (newSessionCreated == 1)
                {
                    // Sending the SYN for the new session
                    RUDPPacket reusablePacket = new RUDPPacket();
                    RUDPHeader reusableheader = new RUDPHeader();
                    reusableheader.type = RUDP_SYN;
                    reusableheader.version = RUDP_VERSION;
                    reusableheader.seqno = seqNo;
                    reusablePacket.header = reusableheader;
                    reusablePacket.payload_length = 0;
                    rudpdataPool.AddData(reusablePacket);
                    send_packet(0, rsocket, reusablePacket, to);
                    rudpdataPool.ClearDataPool();              
                }
                return 0;
            }
            catch (Exception ex)
            {
                var LineNumber = new StackTrace(ex, true).GetFrame(0).GetFileLineNumber();
                logger.LogError($"Exception Occurred Exception: [" + ex.Message + "---line no=" + LineNumber); return -1;
            }
        }

        public int retry_flag = 0;
        //function is invoked when a timeout event occurs, indicating that a packet transmission has exceeded the expected time duration without receiving an acknowledgment
        public int TimeoutCallback(int fd, object args)
        {
            try
            {
                TimeoutArgs timeArgs = args as TimeoutArgs;
                Sockets temp = sockets_list_head;
                while (temp != null)
                {
                    if (temp.rsock == timeArgs.fd)
                    {
                        break;
                    }
                    temp = temp.next;
                }

                if (temp != null && temp.rsock == timeArgs.fd)
                {
                    int sessionFound = 0;

                    Session temp2 = temp.sessions_list_head;
                    while (temp2 != null)
                    {
                        if (temp2.address.Equals(timeArgs.recipient))
                        {
                            sessionFound = 1;
                            break;
                        }
                        temp2 = temp2.next;
                    }

                    if (sessionFound == 1)
                    {
                        if (timeArgs.packet.header.type == RUDP_SYN)
                        {
                            if (temp2.sender.syn_retransmit_attempts >= RUDP_MAXRETRANS)
                            {
                                return 0;
                            }
                            else
                            {
                                retry_flag = 1;
                                temp2.sender.syn_retransmit_attempts++;
                                send_packet(0, timeArgs.fd, timeArgs.packet, timeArgs.recipient);
                                retry_flag = 0;

                            }
                        }
                        else
                        {
                            int index = 0;
                            for (int i = 0; i < RUDP_WINDOW; i++)
                            {
                                if (temp2.sender.sliding_window[i] != null && temp2.sender.sliding_window[i].header.seqno == timeArgs.packet.header.seqno)
                                {
                                    index = i;
                                }
                            }

                            if (temp2.sender.retransmission_attempts[index] >= RUDP_MAXRETRANS)
                            {
                                return 0;
                            }
                            else
                            {
                                retry_flag = 1;
                                temp2.sender.retransmission_attempts[index]++;
                                send_packet(0, timeArgs.fd, timeArgs.packet, timeArgs.recipient);
                                retry_flag = 0;

                            }
                        }
                    }
                    temp2 = null;
                }
                temp = null;
            
                return 0;
            }
            catch (Exception ex)
            {
                var LineNumber = new StackTrace(ex, true).GetFrame(0).GetFileLineNumber();
                logger.LogError($"Exception Occurred Exception: [" + ex.Message + "---line no=" + LineNumber); return -1;
            }
        }

        //provides a mechanism for asynchronously sending data over a socket, enhancing the responsiveness and efficiency of network communication
        private void SendChunk(Socket s, EndPoint ep, byte[] data, int offset, int chunkSize)
        {
            s.BeginSendTo(data, offset, chunkSize, SocketFlags.None, ep, ar =>
            {
                try
                {
                    s.EndSendTo(ar);
                }
                catch (Exception ex)
                {
                    var LineNumber = new StackTrace(ex, true).GetFrame(0).GetFileLineNumber();
                    logger.LogError($"Exception Occurred Exception: [" + ex.Message + "---line no=" + LineNumber); return ;
                }
            }, null);
        }

        //This function encapsulates the process of sending a UDP packet , handling timeouts
        public int send_packet(int isAck, int rsocket, RUDPPacket p, IPEndPoint recipient)
        {
            try
            {

                if(retry_flag == 1)
                {

                    p.header.duplicate_flag = 1;
                }
                string type;
                if (p.header.type == RUDP_DATA)
                {
                    type = "DATA";
                }
                else if (p.header.type == RUDP_ACK)
                {
                    type = "ACK";
                }
                else if (p.header.type == RUDP_SYN)
                {
                    type = "SYN";
                }
                else
                {
                    type = "BAD";
                }

                byte[] packetBytes = SerializePacket(p);

                SendChunk(UDPclass.globalIP.socketUDP, recipient, packetBytes, 0, packetBytes.Length);
                logger.LogError($"Sending count {++count}  {type} packet  seq number={p.header.seqno} duplicateflag={p.header.duplicate_flag} length={p.payload_length}");
              //  logger.LogError($"Sending count {++count} count1: {++count1}   {type} packet to {recipient.Address}:{recipient.Port} seq number={p.header.seqno} on socket={rsocket}");
                if (isAck == 0)
                {

                    // Set a timeout event, unless the packet is an ACK
                    TimeoutArgs timeArgs = new TimeoutArgs();
                    timeArgs.packet = p;
                    timeArgs.recipient = recipient;
                    timeArgs.fd = rsocket;

                    //DateTime currentTime = DateTime.Now;
                    //DateTime timeoutTime = currentTime.AddMilliseconds(RUDP_TIMEOUT);
                    //TimeSpan timeoutDuration = timeoutTime - DateTime.Now;

                    DateTime currentTime = DateTime.Now;
                    TimeSpan delay = TimeSpan.FromMilliseconds(RUDP_TIMEOUT);
                    DateTime timeoutTime = currentTime.Add(delay);
                    Sockets temp = sockets_list_head;

                    while (temp != null)
                    {
                        if (temp.rsock == timeArgs.fd)
                        {
                            break;
                        }
                        temp = temp.next;
                    }

                    if (temp.rsock == timeArgs.fd)
                    {
                        int sessionFound = 0;
                        // Check if we already have a session for this peer
                        Session temp2 = temp.sessions_list_head;
                        while (temp2 != null)
                        {
                            if (temp2.address.Address.Equals(timeArgs.recipient.Address) &&
                                temp2.address.Port == timeArgs.recipient.Port &&
                                temp2.address.AddressFamily == timeArgs.recipient.AddressFamily)
                            {
                                // Found an existing session
                                sessionFound = 1;
                                break;
                            }
                            temp2 = temp2.next;
                        }

                        if (sessionFound == 1)
                        {
                            if (timeArgs.packet.header.type == RUDP_SYN)
                            {
                                temp2.sender.syn_timeout_arg = timeArgs;
                            }
                            else if (timeArgs.packet.header.type == RUDP_DATA)
                            {
                                int index = 0;
                                for (int i = 0; i < RUDP_WINDOW; i++)
                                {
                                    if (temp2.sender.sliding_window[i] != null &&
                                        temp2.sender.sliding_window[i].header.seqno == timeArgs.packet.header.seqno)
                                    {
                                        index = i;
                                    }
                                }
                                temp2.sender.data_timeout_arg[index] = timeArgs;
                            }
                        }
                        temp2 = null;
                        temp = null;
                    }
                    EventTimeout(timeoutTime, TimeoutCallback, timeArgs);
                }
                return 0;
            }
            catch (Exception ex)
            {
                var LineNumber = new StackTrace(ex, true).GetFrame(0).GetFileLineNumber();
                logger.LogError($"Exception Occurred Exception: [" + ex.Message + "---line no=" + LineNumber); return -1;
            }
        }

        //this function facilitates the conversion of a RUDPPacket object into a byte array suitable for transmission over a network
        public byte[] SerializePacket(RUDPPacket packet)
        {
            try
            {
                int headerSize = sizeof(ushort) + sizeof(ushort) + sizeof(int) + sizeof(int) + sizeof(int); // Assuming 2 shorts and 3 int in the header
                int size = headerSize + packet.payload.Length;
                byte[] buffer = new byte[size];
                int offset = 0;
                // Use BitConverter to convert ushort and int to bytes
                Buffer.BlockCopy(BitConverter.GetBytes((ushort)packet.header.version), 0, buffer, offset, sizeof(ushort));
                offset += sizeof(ushort);
                Buffer.BlockCopy(BitConverter.GetBytes((ushort)packet.header.type), 0, buffer, offset, sizeof(ushort));
                offset += sizeof(ushort);
                Buffer.BlockCopy(BitConverter.GetBytes(packet.header.seqno), 0, buffer, offset, sizeof(int));
                offset += sizeof(int);
                Buffer.BlockCopy(BitConverter.GetBytes(packet.header.duplicate_flag), 0, buffer, offset, sizeof(int));
                offset += sizeof(int);
                Buffer.BlockCopy(BitConverter.GetBytes(packet.payload_length), 0, buffer, offset, sizeof(int));
                offset += sizeof(int);

                //// Serialize the duplicate_flag field
                //Buffer.BlockCopy(BitConverter.GetBytes(packet.duplicate_flag), 0, buffer, offset, sizeof(int));
                //offset += sizeof(int);

                // Copy payload directly to the buffer
                Buffer.BlockCopy(packet.payload, 0, buffer, offset, packet.payload.Length);
                return buffer;
            }
            catch (Exception ex)
            {
                var LineNumber = new StackTrace(ex, true).GetFrame(0).GetFileLineNumber();
                logger.LogError($"Exception Occurred Exception: [" + ex.Message + "---line no=" + LineNumber); return null;
            }
        }

        //  This function is responsible for deserializing a byte array into a RUDPPacket object, which represents a packet in the RUDP protocol

        private RUDPPacket deserializedPacket = new RUDPPacket();
        public RUDPPacket deSerializePacket(byte[] data)
        {
            try
            {
                using (MemoryStream ms = new MemoryStream(data))
                {
                    using (BinaryReader reader = new BinaryReader(ms))
                    {
                        // Read and reconstruct the header
                        deserializedPacket.header = new RUDPHeader();
                        deserializedPacket.header.version = reader.ReadUInt16();
                        deserializedPacket.header.type = reader.ReadUInt16();
                        deserializedPacket.header.seqno = reader.ReadInt32();
                        // Read the duplicate_flag
                        deserializedPacket.header.duplicate_flag = reader.ReadInt32();
                        // Read the payload length
                        deserializedPacket.payload_length = reader.ReadInt32();
                        
                        // Read the payload
                        deserializedPacket.payload = reader.ReadBytes(deserializedPacket.payload_length);
                    }
                }
                return deserializedPacket;
            }
            catch (Exception ex)
            {
                var LineNumber = new StackTrace(ex, true).GetFrame(0).GetFileLineNumber();
                logger.LogError($"Exception Occurred Exception: [" + ex.Message + "---line no=" + LineNumber); return null;
            }
        }
    }
}
