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

//  #define DEBUG_LOOPBACK

using System;
using System.Collections;
using System.Diagnostics;
using System.Threading;

using Microsoft.Singularity;

using Drivers.Net;

using NetStack.Common;

namespace NetStack.NetDrivers
{
    public class LoopbackAdapter : IAdapter
    {
        public const int RingSize = 256;    // TODO: Why public?

        static int instantiations = 0;

        private EthernetAddress address;

        private AutoResetEvent writeEvent;
        private AutoResetEvent readEvent;

        private Queue rxBuffer;

        private int txDeliveryComplete;
        private int rxAvailable;

        public LoopbackAdapter()
        {
            LoopbackAdapter.instantiations++;

            //
            // Microsoft OUI 00:50:F2:xx:xx:xx
            // It doesn't really matter, but something other
            // than zero is preferable for testing purposes.
            //
            int x = 0xFFFFFF - instantiations;
            address = new EthernetAddress(0x00, 0x50, 0xF2,
                                          (byte)((x >> 16) & 0xFF),
                                          (byte)((x >>  8) & 0xFF),
                                          (byte)((x >>  0) & 0xFF));

            writeEvent = new AutoResetEvent(false);
            readEvent  = new AutoResetEvent(false);

            rxBuffer = new Queue(LoopbackAdapter.RingSize);
            txDeliveryComplete = 0;
            rxAvailable = 0;
        }

        public string DriverName
        {
            get { return "Loopback Adapter"; }
        }

        public string DriverVersion
        {
            get { return "0.1"; }
        }

        public EthernetAddress HardwareAddress
        {
            get { return address; }
        }

        public uint LinkSpeed
        {
            get { return 1000 * 1000 * 1000; }
        }

        public int TxSlotsFree
        {
            get {
                int txSlotsFree;
                lock (this) {
                    txSlotsFree = LoopbackAdapter.RingSize
                                - rxBuffer.Count
                                - txDeliveryComplete;
                    DebugStub.Assert(txSlotsFree >= 0);
                }
                return txSlotsFree;
            }
        }

        public void GetReceivedPackets(Queue! outQueue)
        {
            lock (this) {
                DebugStub.Assert(rxAvailable >= 0 &&
                                 rxAvailable <= LoopbackAdapter.RingSize);

                // Move Receive packets to 'outQueue'.
                Object packet;
                while (rxAvailable > 0 &&
                       (packet = rxBuffer.Dequeue()) != null) {
                    outQueue.Enqueue(packet);
                    txDeliveryComplete++;
                    rxAvailable--;
                }

                DebugStub.Assert(rxAvailable >= 0 &&
                                 rxAvailable <= LoopbackAdapter.RingSize);
                DebugStub.Assert(
                    txDeliveryComplete <= LoopbackAdapter.RingSize);
                DebugPrint("Loopback: GetReceivedPackets() -> {0}\n",
                           __arglist(outQueue.Count));
                DebugPrintStatus();

                readEvent.Reset();
                writeEvent.Set();
            }
        }

        [Conditional("DEBUG_LOOPBACK")]
        private void DebugPrintStatus()
        {
            DebugStub.Print("-- queued: {0} txComplete {1}\n",
                            __arglist(rxBuffer.Count,
                                      txDeliveryComplete));
        }

        [Conditional("DEBUG_LOOPBACK")]
        private static void DebugPrint(string format, __arglist)
        {
            DebugStub.Print(format, new ArgIterator(__arglist));
        }

        [Conditional("DEBUG_LOOPBACK")]
        private static void DebugPrint(string format)
        {
            DebugStub.Print(format);
        }

        public uint GetTransmittedPackets()
        {
            lock (this) {
                DebugPrint("Loopback: GetTransmittedPackets() -> {0}\n",
                           __arglist(txDeliveryComplete));
                DebugPrintStatus();

                uint count = (uint)txDeliveryComplete;
                txDeliveryComplete = 0;
                return count;
            }
        }

        public void PopulateRxRing(NetPacket! packet)
        {
            DebugPrint("Loopback: PopulateRxRing()\n");
            DebugPrintStatus();
            // Do nothing
        }

        public void PopulateTxRing(NetPacket[]! packets, uint count)
        {
            lock (this) {
                DebugPrint("Loopback: PopulateTxRing({0})\n",
                           __arglist(count));
                DebugPrintStatus();
                for (uint i = 0; i < count; i++) {
                    NetPacket! packet = (!)packets[i];
                    byte[]! txdata = (!)packet.ToContiguous();
                    rxBuffer.Enqueue(new NetPacket(txdata));
                    rxAvailable++;
                }

                DebugStub.Assert(rxBuffer.Count <= RingSize);

                writeEvent.Reset();
                if (rxBuffer.Count != 0) {
                    readEvent.Set();
                }
            }
        }

        public WaitHandle GetReadHandle()
        {
            return readEvent;
        }

        public WaitHandle GetWriteHandle()
        {
            return writeEvent;
        }
    }
}
