///////////////////////////////////////////////////////////////////////////////
//
//  Microsoft Research Singularity
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  File:   UdpBlast.cs
//
//  Note:
//

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Microsoft.Singularity.Test.Network
{
    public class UdpBlast
    {
        private const int FixedHeaderBytes = 42;    // Ether + IP + UDP

        static bool verbose = true;

        public static void Usage()
        {
            Console.WriteLine("UdpBlast [-p <bytesPerPkt>] [-r <bitsPerSec>] [-t duration] [-v] host port");
            Environment.Exit(-2);
        }

        public static void VerboseLog(string format, params object [] args)
        {
            if (verbose) {
                Console.Write(format, args);
            }
        }

        private static int
        GetPacketSize(long bitsPerSecond,
                      long bitsThisSecond,
                      int  bytesPerPacket)
        {
            long bitsRemaining = bitsPerSecond - bitsThisSecond;
            if (bitsRemaining >= (long)8 * bytesPerPacket) {
                return bytesPerPacket - FixedHeaderBytes;
            }
            else {
                int size = ((int)(bitsPerSecond - bitsThisSecond) / 8
                            - FixedHeaderBytes);
                if (size < 1) {
                    return 1;
                }
                return size;
            }
        }

        public static void
        Blast(Socket s,
              uint   bitsPerSecond,
              int    bytesPerPacket,
              uint   durationSeconds
              )
        {
            byte [] bigPacket   =
                new byte[GetPacketSize(bitsPerSecond, 0, bytesPerPacket)];
            byte [] smallPacket = null;

            DateTime startTime  = DateTime.Now;
            DateTime endTime    = (startTime +
                                   TimeSpan.FromSeconds(durationSeconds));

            DateTime secondStart = startTime;
            DateTime secondEnd   = startTime + TimeSpan.FromSeconds(1);

            DateTime now = startTime;

            uint second         = 0;
            long bitsThisSecond = 0;
            long pktsThisSecond = 0;
            long totalBits      = 0;
            long totalPackets   = 0;

            VerboseLog("UdpBlast: {0} bytesPerPacket, Limiting transfer to {1} bitsPerSecond, for {2} seconds",
                              bytesPerPacket, bitsPerSecond, durationSeconds);

            while (now < endTime) {
                byte [] packet = bigPacket;
                while (now < secondEnd && bitsThisSecond < bitsPerSecond) {
                    int size = GetPacketSize(bitsPerSecond,
                                             bitsThisSecond,
                                             bytesPerPacket);
                    if (size != packet.Length) {
                        if (smallPacket == null || size != smallPacket.Length)
                            smallPacket = new byte [size];
                        packet = smallPacket;
                        Console.Write(".");
                    }
                    s.Send(packet);
                    bitsThisSecond += (packet.Length + FixedHeaderBytes) * 8;
                    pktsThisSecond++;
                    totalPackets++;
                    now = DateTime.Now;
                }

                while (now < secondEnd) {
                    Thread.Sleep(0);
                    now = DateTime.Now;
                }

                TimeSpan tau = secondEnd - secondStart;

                long bytesPerSecond = (long)((bitsThisSecond / tau.TotalSeconds) / 8);

                VerboseLog(
                    "Round {0} duration(secs) {1:f2} packets {2} rate (bps) {3:e3}, bytes/sec {4:e3}\n",
                    second, tau.TotalSeconds,
                    pktsThisSecond,
                    bitsThisSecond / tau.TotalSeconds,
                    bytesPerSecond
                    );

                second++;
                totalBits      += bitsThisSecond;
                bitsThisSecond  = 0;
                pktsThisSecond   = 0;
                secondStart     = now;
                secondEnd       = now + TimeSpan.FromSeconds(1);
            }

            Console.WriteLine(
                "Average rate: packets/sec {0}, bits/sec {1:e3}, bytes/sec {2:e3}",
                totalPackets / (now - startTime).TotalSeconds,
                totalBits / (now - startTime).TotalSeconds,
                (totalBits / (now - startTime).TotalSeconds) / 8
                );
        }

        public static IPAddress ParseAddress(string srep)
        {
            try {
                return IPAddress.Parse(srep);
            }
            catch {
                try {
                    VerboseLog("Resolving: {0}\n", srep);
                    IPHostEntry iphe = Dns.GetHostEntry(srep);
                    return iphe.AddressList[0];
                }
                catch {
                    VerboseLog("Falling back to any address");
                    return IPAddress.Any;
                }
            }
        }

        private static bool ParseUInt32(string srep, out UInt32 value)
        {
            try {
                value = UInt32.Parse(srep);
                return true;
            }
            catch (FormatException) {
            }
            catch (OverflowException) {
            }
            value = 0;
            return false;
        }

        private static bool ParseDouble(string srep, out Double value)
        {
            try {
                value = Double.Parse(srep);
                return true;
            }
            catch (FormatException) {
            }
            catch (OverflowException) {
            }
            value = 0;
            return false;
        }

        public static int Main(string [] args)
        {
            int i = 0;

            uint bitsPerSecond  = 64000;
            uint bytesPerPacket = 1500;
            uint duration       = 30;

            double rate;

            while (i < args.Length &&
                   (args[i][0] == '-' || args[i][0] == '/')) {
                switch (Char.ToLower(args[i][1])) {
                    case 'p':
                        if (args.Length - i < 2)
                            Usage();
                        if (!ParseUInt32(args[++i], out bytesPerPacket) ||
                            bytesPerPacket <= FixedHeaderBytes) {
                            Console.WriteLine("Bad packet size: {0}", args[i]);
                            Environment.Exit(-1);
                            return -1;
                        }
                        break;
                    case 'r':
                        if (args.Length - i < 2)
                            Usage();
                        if (!ParseDouble(args[++i], out rate) ||
                            rate < 0 || rate > UInt32.MaxValue * 1.0) {
                            Console.WriteLine("Bad rate: {0}", args[i]);
                            return -1;
                        }
                        bitsPerSecond = (UInt32)rate;
                        break;
                    case 't':
                        if (args.Length - i < 2)
                            Usage();
                        if (!ParseUInt32(args[++i], out duration)) {
                            Console.WriteLine("Bad duration: {0}", args[i]);
                            return -1;
                        }
                        break;
                    case 'v':
                        verbose = true;
                        break;
                }
                i++;
            }

            if (args.Length - i < 2) {
                Usage();
            }

            IPAddress address = ParseAddress(args[i]);
            if (address == IPAddress.Any) {
                Console.WriteLine("Invalid address: {0}", args[i]);
                return -1;
            }

            uint port;
            if (!ParseUInt32(args[++i], out port) || port > 0xffff) {
                Console.WriteLine("Bad port: {0}", args[i]);
                return -1;
            }

            VerboseLog("Sending to {0} {1}\n", address, port);
            VerboseLog("Packet size = {0} bytes\n", bytesPerPacket);
            VerboseLog("Send rate = {0} bps ({1} pps).\n",
                       bitsPerSecond, bitsPerSecond / (8 * bytesPerPacket));

            Socket s = new Socket(AddressFamily.InterNetwork,
                                  SocketType.Dgram,
                                  ProtocolType.Udp);
            s.Connect(new IPEndPoint(address, (ushort)port));
            Blast(s, bitsPerSecond, (int)bytesPerPacket, duration);

            return 0;
        }
    }
}
