using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;

namespace CFBandwidth
{
    public class BandwidthMeter
    {
        public List<double> results = new List<double>();
        public List<double> sizes = new List<double>();
        public List<List<double>> ListRtt = new List<List<double>>();

        public IPAddress destination { get; set; }

        int size = 32;

        Linear_Regression ln;

        byte[] data = new byte[1024];
        int recv;

        private DateTime executionTime;

        private Socket pingSocket;
        IPEndPoint iep;
        EndPoint ep;
        List<int> pingTimeOutId = new List<int>();
        private int identifier = 0;
        private int packetSize;

        Form1 formRef = null;
        private int packetsize;
        int received;                               // number of received bytes 
        double startTime;                           // stores sending time
        double stopTime;
        CFStopWatch sw;
        public int timeout { get; set; }                        // time after which packet will be marked as timeout
        IAsyncResult iarReceive;

        public int step { get; set; }
        public int MTU { get; set; }
        public int MIN { get; set; }
        public int iteratons { get; set; }

        bool flag = false;
        CFStopWatch stopwatch;
        List<double> RTTS;
        public BandwidthMeter(Form1 _formRef, int _mtu, int _min, int _step, int _iterrations, int _timeout, IPAddress _destination)
        {

            MTU = _mtu;
            MIN = _min;
            step = _step;
            iteratons = _iterrations;
            timeout = _timeout;
            destination = _destination;
            pingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
            iep = new IPEndPoint(destination, 0);
            ep = (EndPoint)iep;
            formRef = _formRef;

            #region default
            /*
            destination = IPAddress.Parse("192.168.1.1");
            pingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
            iep = new IPEndPoint(destination, 0);
            ep = (EndPoint)iep;
            formRef = _formRef;
            MIN = size;
            MTU = 1500;
              */
            #endregion
        }


        public void measurements()
        {
            executionTime = DateTime.Now;
            ICMP icmpPacket;

            while (size < 1500)
            {
                formRef.updateLabel("Current packet size: " + size.ToString());
                List<double> RTTS = new List<double>();
                for (int i = 0; i < iteratons; i++)
                {
                    packetSize = size;
                    packetsize = size;

                    icmpPacket = new ICMP();
                    icmpPacket.Type = 0x08;
                    icmpPacket.Code = 0x00;
                    icmpPacket.Checksum = 0;
                    icmpPacket.Message = new byte[packetSize - 4];

                    data = new byte[packetSize - 4];
                    icmpPacket.MessageSize = icmpPacket.Message.Length;
                    for (int j = 0; j < packetSize - 6; j++)
                    {
                        icmpPacket.Message[j] = 0x01;
                    }
                    Buffer.BlockCopy(data, 0, icmpPacket.Message, 0, data.Length);
                    Buffer.BlockCopy(BitConverter.GetBytes((short)identifier), 0, icmpPacket.Message, packetSize - 7, 2);
                    UInt16 chcksum = icmpPacket.getChecksum();
                    icmpPacket.Checksum = chcksum;
                    packetsize = icmpPacket.MessageSize + 4;

                    data = new byte[size + 2000];
                    sw = new CFStopWatch();


                    sw.Start();

                    startTime = sw.GetSplitTimeInMicroseconds();
                    pingSocket.SendTo(icmpPacket.getBytes(), packetsize, SocketFlags.None, iep);
                    Thread.Sleep(1000);
                    try // reveiving section
                    {
                        iarReceive = pingSocket.BeginReceive(data, 0, data.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), pingSocket);
                        do
                        {
                            if (iarReceive.IsCompleted == true)
                            {
                                ICMP receivedICMP = new ICMP(data, received);
                                if (pingTimeOutId.Contains(receivedICMP.getIdentifier()))
                                {
                                    iarReceive = pingSocket.BeginReceive(data, 0, data.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), pingSocket);
                                }
                                else
                                {
                                    RTTS.Add(stopTime - startTime);
                                    sw.Stop();
                                    formRef.updateListBox2("Packet identifier: " + receivedICMP.getIdentifier().ToString() + " RTT: " + Math.Round((RTTS.Last() / 1000), 2).ToString() + " ms");
                                    break;
                                }
                            }
                        } while ((sw.GetSplitTimeInMicroseconds() - startTime) < timeout * 1000);

                        if (iarReceive.IsCompleted == false)
                        {
                            formRef.updateListBox2("Packet identifier: " + identifier.ToString() + " timeout ");
                            formRef.updateListBox2(sw.GetSplitTimeInMicroseconds().ToString());
                            pingTimeOutId.Add(identifier);
                            sw.Stop();
                        }
                        else
                        {
                            sw.Stop();
                        }
                    }
                    catch (Exception e)
                    {

                    }
                    identifier++;
                }
                sizes.Add(size);
                if (RTTS.Count > 0)
                {
                    formRef.updateListBox1(" Size: " + size.ToString() + " Bytes, Min RTT: " + Math.Round((RTTS.Min() / 1000), 2).ToString() + " ms");
                }
                ListRtt.Add(new List<double>(RTTS));

                //results.Add(RTTS.Min() / 1000);
                results.Add(RTTS.Min());

                size = size + step;
            }
        }


        public void measurments1()
        {
            executionTime = DateTime.Now;

            size = MIN;

            //tutaj petla
            ICMP icmpPacket = new ICMP(size);
            icmpPacket.Type = 0x08;
            icmpPacket.Code = 0x00;
            icmpPacket.Checksum = 0;
            Buffer.BlockCopy(BitConverter.GetBytes((short)identifier), 0, icmpPacket.Message, 0, 2);    //sets value of 1st and 2nd byte in message
            Buffer.BlockCopy(BitConverter.GetBytes((short)1), 0, icmpPacket.Message, 2, 2);             //sets value of 3rd and 4th byte in message

            int packetsize = icmpPacket.MessageSize + 4;
            UInt16 chcksum = icmpPacket.getChecksum();
            icmpPacket.Checksum = chcksum;


            while (size < MTU)
            {

                formRef.updateLabel("Current packet size: " + size.ToString());
                RTTS = new List<double>();
                for (int i = 0; i < iteratons; i++)
                {
                    stopwatch = new CFStopWatch();
                    stopwatch.Calibrate();
                    stopwatch.Start();

                    startTime = stopwatch.GetSplitTimeInMicroseconds();
                    pingSocket.SendTo(icmpPacket.getBytes(), packetsize, SocketFlags.None, iep);
                    int SentIdentifier = BitConverter.ToInt16(icmpPacket.Message, 0);
                    //formRef.updateListBox2("SentIdentifier: " + SentIdentifier.ToString());
                    flag = false;
                    try
                    {

                        data = new byte[packetSize + 2000];
                        pingSocket.BeginReceive(data, 0, data.Length, SocketFlags.None, new AsyncCallback(ReceivedData), pingSocket);
                        Thread.Sleep(100);
                        while (flag == false && ((startTime + stopwatch.GetSplitTimeInMicroseconds()) < timeout * 1000)) ;
                        if (flag == false)//jesli byl timeout
                        {
                            pingTimeOutId.Add(SentIdentifier); // dodaje id do timeoutow
                            formRef.updateListBox2("Packet identifier: " + SentIdentifier.ToString() + " Timeout");
                        }
                        else//jesli nie bylo timeout
                        {
                            ICMP response = new ICMP(data, recv);
                            int ReceivedIdentifier = BitConverter.ToInt16(response.Message, 0);
                            if (!pingTimeOutId.Contains(ReceivedIdentifier)) //jesli nie bylo timeout
                            {
                                RTTS.Add(stopTime - startTime);
                                formRef.updateListBox2("Packet identifier: " + ReceivedIdentifier.ToString() + " RTT: " + Math.Round(RTTS.Last()/1000,2).ToString()+ " ms");
                            }
                            else
                            {

                            }
                        }
                    }
                    catch (SocketException e)
                    {

                    }
                    //receivePacket(pingSocket, (double)timeout, SentIdentifier);
                    identifier++;
                    icmpPacket = new ICMP(size);
                    icmpPacket.Type = 0x08;
                    icmpPacket.Code = 0x00;
                    icmpPacket.Checksum = 0;
                    Buffer.BlockCopy(BitConverter.GetBytes((short)identifier), 0, icmpPacket.Message, 0, 2);    //sets value of 1st and 2nd byte in message
                    Buffer.BlockCopy(BitConverter.GetBytes((short)1), 0, icmpPacket.Message, 2, 2);             //sets value of 3rd and 4th byte in message
                    packetsize = icmpPacket.MessageSize + 4;
                    chcksum = icmpPacket.getChecksum();
                    icmpPacket.Checksum = chcksum;
                }
                if (RTTS.Count > 0)
                {
                    
                    double min = RTTS.Min();
                    formRef.updateListBox1("Packet size: " + size.ToString()+ " MIN RTT: " + Math.Round(min/1000,2).ToString() + " ms");
                    results.Add(min);
                    ListRtt.Add(new List<double>(RTTS));
                }
                sizes.Add((double)size);
                size = size + step;
                icmpPacket = new ICMP(size);
                icmpPacket.Type = 0x08;
                icmpPacket.Code = 0x00;
                icmpPacket.Checksum = 0;
                Buffer.BlockCopy(BitConverter.GetBytes((short)identifier), 0, icmpPacket.Message, 0, 2);    //sets value of 1st and 2nd byte in message
                Buffer.BlockCopy(BitConverter.GetBytes((short)1), 0, icmpPacket.Message, 2, 2);             //sets value of 3rd and 4th byte in message
                packetsize = icmpPacket.MessageSize + 4;
                chcksum = icmpPacket.getChecksum();
                icmpPacket.Checksum = chcksum;
            }

            //koniec petli

        }

        public bool receivePacket(Socket pingSocket, double timeout, int SentIdentifier)
        {
            try
            {
                byte[]  data = new byte[packetSize + 2000];
                pingSocket.BeginReceive(data, 0, data.Length, SocketFlags.None, new AsyncCallback(ReceivedData), pingSocket);
                Thread.Sleep(100);
                while (flag == false && ((startTime + stopwatch.GetSplitTimeInMicroseconds()) < timeout * 1000)) ;
                if (flag == false)//jesli byl timeout
                {
                    pingTimeOutId.Add(SentIdentifier); // dodaje id do timeoutow
                    formRef.updateListBox2("Packet identifier: " + SentIdentifier.ToString() + " Timeout");
                    return false;
                }
                else//jesli nie bylo timeout
                {
                    ICMP response = new ICMP(data, recv);
                    int ReceivedIdentifier = BitConverter.ToInt16(response.Message, 0);
                    if (!pingTimeOutId.Contains(ReceivedIdentifier)) //jesli nie bylo timeout
                    {
                        RTTS.Add(stopTime - startTime);
                        formRef.updateListBox2("Packet identifier: " + ReceivedIdentifier.ToString() + "RTT: " + RTTS.Last().ToString());
                    }
                    else
                    {
                        receivePacket(pingSocket, timeout -startTime + stopwatch.GetSplitTimeInMicroseconds(),SentIdentifier);
                    }
                }
                return true;
            }
            catch (SocketException e)
            {
                return true;
            }

        }



        public void measurements2()
        {
            executionTime = DateTime.Now;
            ICMP icmpPacket;

            while (size < 1500)
            {
                List<double> RTTS = new List<double>();

                for (int i = 0; i < iteratons; i++)
                {
                    packetSize = size;
                    packetsize = size;

                    icmpPacket = new ICMP();
                    icmpPacket.Type = 0x08;
                    icmpPacket.Code = 0x00;
                    icmpPacket.Checksum = 0;
                    icmpPacket.Message = new byte[packetSize - 4];

                    data = new byte[packetSize - 4];
                    icmpPacket.MessageSize = icmpPacket.Message.Length;
                    for (int j = 0; j < packetSize - 6; j++)
                    {
                        icmpPacket.Message[j] = 0x01;
                    }
                    Buffer.BlockCopy(data, 0, icmpPacket.Message, 0, data.Length);
                    Buffer.BlockCopy(BitConverter.GetBytes((short)identifier), 0, icmpPacket.Message, packetSize - 7, 2);
                    UInt16 chcksum = icmpPacket.getChecksum();
                    icmpPacket.Checksum = chcksum;
                    identifier++;
                    packetsize = icmpPacket.MessageSize + 4;

                    data = new byte[size + 1000];
                    sw = new CFStopWatch();

                    sw.Start();

                    startTime = sw.GetSplitTimeInMicroseconds();
                    pingSocket.SendTo(icmpPacket.getBytes(), packetsize, SocketFlags.None, iep);

                    try // reveiving section
                    {
                        iarReceive = pingSocket.BeginReceive(data, 0, data.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), pingSocket);
                        Thread.Sleep(1000);
                        do
                        {
                            if (iarReceive.IsCompleted == true)
                            {
                                ICMP receivedICMP = new ICMP(data, received);
                                if (pingTimeOutId.Contains(receivedICMP.getIdentifier()))
                                {
                                    iarReceive = pingSocket.BeginReceive(data, 0, data.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), pingSocket);
                                }
                                else
                                {
                                    RTTS.Add(stopTime - startTime);
                                    sw.Stop();
                                    break;
                                }
                            }
                        } while ((sw.GetSplitTimeInMicroseconds() - startTime) < timeout * 1000);

                        if (iarReceive.IsCompleted == false)
                        {
                            formRef.updateListBox1(" timeout ");
                            pingTimeOutId.Add(identifier);
                        }
                        else
                        {
                            sw.Stop();
                        }
                    }
                    catch (Exception e)
                    {

                    }
                }
                sizes.Add(size+34);
                formRef.updateListBox1(" Size: " + size.ToString() + " Bytes, Min RTT: " + Math.Round((RTTS.Min() / 1000), 2).ToString() + " ms");

                ListRtt.Add(new List<double>(RTTS));

                //results.Add(RTTS.Min() / 1000);
                results.Add(RTTS.Min());

                size = size + step;
            }
        }



        void ReceivedData(IAsyncResult iar)
        {
            try
            {
                Socket remote = (Socket)iar.AsyncState;
                recv = remote.EndReceive(iar);
                stopTime = stopwatch.GetSplitTimeInMicroseconds();
                flag = true;
            }
            catch
            {
                formRef.updateListBox2("exception");
            }
        }

        public DateTime getExecutionTime()
        {
            return executionTime;
        }



        private void ReceiveCallback(IAsyncResult iar)
        {
            received = 0;
            try
            {
                Socket callbackSocket = (Socket)iar.AsyncState;
                received = callbackSocket.EndReceive(iar);
                stopTime = sw.GetSplitTimeInMicroseconds();
            }
            catch (SocketException se)
            {
                formRef.updateListBox1("->Error " + se.ErrorCode.ToString());
                formRef.updateListBox1(" " + received.ToString() + " received");
            }
        }


    }
}
