// ==++==
//
//   Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ==--==

// #define DEBUG_TCP

using System;
using System.Net.IP;
using System.Threading;

using RJBlack;

using Drivers.Net;

using NetStack.NetDrivers;

namespace NetStack.Runtime
{
    // This provides the basic dispatching of three types of events
    // 1. Internal events.  These are when one piece of code requests
    // that another piece of code is called as soon as it is done, but
    // it cannot be called immediately because of a re-entrancy issue.
    // 2. External events.  These events occur when some component
    // external to the stack (such as a client or a device driver)
    // indicate that some action is pending.
    // 3. Timers.  These events occur as a result of an external
    // activity, but once triggered are handled as internal events
    //
    // Internal events have priority over external events.  The
    // rationale for this may be found in the design documents.

    // XXX This code needs refactoring into platform specific
    // and generic components.
    public class Dispatcher
    {
        // Public interfaces by which we interact with components
        public delegate NetStatus Callback(CallbackArgs ca);
        public interface CallbackArgs  {}

        private uint swapCount = 0;
        private bool stopped = false;

        // Linked list of internal events
        private class Internal
        {
            public Callback fun;
            public CallbackArgs arg;
            public Internal next;
        }

        private Internal internalEventsHead;
        private Internal internalEventsTail;

        // This event is used to wake up the main dispatcher
        // thread, typically because we have changed the
        // list of events or timeouts to be serviced.
        // It lives in the first slot of eHandles.
        private AutoResetEvent dispatcherStrobe;

        // Timers have arguments too
        private Timer.TimerWheel! timers;
        private class Timer : RJBlack.Timer
        {
            public Callback fun;
            public CallbackArgs arg;
            public Timer(Callback fun, CallbackArgs arg, ulong time) : base(time)
            {
                this.fun = fun;
                this.arg = arg;
            }
        }

        // External events. eHandles[0] is special and
        // holds our internal strobe (dispatcherStrobe).
        private Callback [] eFuns;
        private CallbackArgs [] eArgs;
        private WaitHandle []! eHandles;

        // Public Methods
        public void AddCallback(Callback fun, CallbackArgs arg)
        {
            Internal internalEvent = new Internal();
            internalEvent.fun = fun;
            internalEvent.arg = arg;
            if (internalEventsHead == null) {
                internalEventsHead = internalEvent;
            }
            else {
                internalEventsTail.next = internalEvent;
            }
            internalEventsTail = internalEvent;

            // Make sure to wake up and notice this
            dispatcherStrobe.Set();
        }

        internal RJBlack.Timer! AddCallback(Callback fun, CallbackArgs args, ulong time)
        {
            Timer t = new Timer(fun, args, time);

            lock (timers) {
                timers.Add(t);
            }

            // Make sure to wake up and notice this
            dispatcherStrobe.Set();
            return t;
        }

        public void AddCallback(Callback fun, CallbackArgs arg, WaitHandle wh)
        {
            int n = eHandles.Length;
            WaitHandle [] newHandles = new WaitHandle[n + 1];
            Callback [] newFuns = new Callback[n + 1];
            CallbackArgs [] newArgs = new CallbackArgs[n + 1];

            if (n > 0) {
                // Lock to defend against the main loop shuffling the array
                // while we are copying it
                lock (eHandles.SyncRoot) {
                    eHandles.CopyTo(newHandles, 0);
                    eFuns.CopyTo(newFuns, 0);
                    eArgs.CopyTo(newArgs, 0);
                }
            }

            // Add the new callback to the end...
            newHandles[n] = wh;
            newFuns[n] = fun;
            newArgs[n] = arg;

            // Swap in the new arrays
            eHandles = newHandles;
            eFuns = newFuns;
            eArgs = newArgs;

            // Make sure to wake up and notice this
            dispatcherStrobe.Set();
        }

        // CALLER MUST HOLD A LOCK that ensures that
        // the eHandles, eFuns and eArgs array won't
        // change during this function call!
        private void RemoveWaitHandleCallback(int index)
        {
            int newLength = eHandles.Length - 1;

            if (newLength == 0) {
                eHandles = new WaitHandle[0];
                eFuns    = null;
                eArgs    = null;
                return;
            }

            int lhLength = index;
            int rhLength = newLength - index;

            // Allocate the replacement arrays
            WaitHandle []   newHandles = new WaitHandle[newLength];
            Callback []     newFuns    = new Callback[newLength];
            CallbackArgs [] newArgs    = new CallbackArgs[newLength];

            // Copy before the removed item...
            if (lhLength > 0) {
                Array.Copy(eHandles, 0, newHandles, 0, lhLength);
                Array.Copy(eFuns, 0, newFuns, 0, lhLength);
                Array.Copy(eArgs, 0, newArgs, 0, lhLength);
            }

            // Copy after the removed item...
            if (rhLength > 0) {
                Array.Copy(eHandles, index + 1, newHandles, index, rhLength);
                Array.Copy(eFuns, index + 1, newFuns, index, rhLength);
                Array.Copy(eArgs, index + 1, newArgs, index, rhLength);
            }

            // Swap in the replacement array
            eHandles = newHandles;
            eFuns    = newFuns;
            eArgs    = newArgs;

            // Make sure to wake up and notice this
            dispatcherStrobe.Set();
        }

        public void RemoveCallback(WaitHandle wh)
        {
            lock (eHandles.SyncRoot) {
                for (int i = 0; i < eHandles.Length; i++) {
                    if (((!)eHandles[i]).Equals(wh)) {
                        RemoveWaitHandleCallback(i);
                        return;
                    }
                }
            }
        }

        public bool RemoveTimeoutCallback(RJBlack.Timer! fun)
        {
            lock (timers) {
                return timers.Remove(fun);
            }
        }


        public void Stop()
        {
            stopped = true;

            // Make sure to wake up and notice this
            dispatcherStrobe.Set();
        }

        // Execute forever
        public void Execute()
        {
            while (stopped == false) {
                // Internal events first
                if (internalEventsHead != null) {
                    Internal internalEvent = internalEventsHead;
                    internalEventsHead = internalEvent.next;
#if DEBUG_TCP
                    Core.Log("Dispatching Internal Event at " +
                             DateTime.UtcNow.ToString() +
                             " calling '" + internalEvent.fun.ToString() +
                             "'.");
#endif
                    internalEvent.fun(internalEvent.arg);
                    continue;
                }

                // One read of NOW
                ulong now = (ulong)DateTime.UtcNow.Ticks;

                // Deal with timers
                TimeSpan waitTime = TimeSpan.MaxValue;

                lock (timers) {
                    Timer timer = (Timer)timers.Advance(now);

                    if (timer != null) {
                        // This timer has expired; service
                        // it immediately and start over
#if DEBUG_TCP
                        Core.Log("Timer Expiration at " +
                                 (new DateTime((long) now)).ToString() +
                                 " calling '" + timer.fun.ToString() +
                                 "'.");
#endif
                        timer.fun(timer.arg);
                        continue;
                    }

                    timer = (Timer)timers.GetSoonest();

                    if (timer != null) {
                        ulong soonest = timer.time;
                        if (soonest < now) {
                            Core.Log("Negative WaitTime");
                            waitTime = new TimeSpan(0L);
                        }
                        else {
                            waitTime = new TimeSpan((long)(soonest - now));
                        }
                    }
                }

                // Grab a copy of the external-event pointers so
                // we're not affected by changes to them on other
                // threads
                Callback[] funs = eFuns;
                CallbackArgs[] args = eArgs;
                WaitHandle[] handles = eHandles;

                // Shuffle the array values so the same tasks aren't
                // always preferred to others (WaitAny() will unblock
                // against the lowest-index WaitHandle in the array)
                lock (handles.SyncRoot) {
                    int n = handles.Length;

                    for (int i = 0; i < n - 1; ++i) {
                        // Swap the current element of the list with
                        // an entry that follow it. Instead of an
                        // expensive real random-number generator,
                        // generate a pseudo-random number by multiplying
                        // a largish prime with an increasing count
                        uint swapWith = (uint)(i + ((unchecked(53533511 * swapCount++)) % (n - i)));

                        if (swapWith != i) {
                            Callback swapFun = funs[swapWith];
                            CallbackArgs swapArgs = args[swapWith];
                            WaitHandle swapHandle = handles[swapWith];

                            funs[swapWith] = funs[i];
                            args[swapWith] = args[i];
                            handles[swapWith] = handles[i];

                            funs[i] = swapFun;
                            args[i] = swapArgs;
                            handles[i] = swapHandle;
                        }
                    }
                }

                int rc = WaitHandle.WaitAny(handles, waitTime);

                if (rc == WaitHandle.WaitTimeout)
                    continue;

                if (rc < 0 || rc >= handles.Length)
                    throw new ApplicationException("Invalid WaitAny");

                Callback cb = funs[rc];
                if (cb != null) {
                    NetStatus res = cb(args[rc]);

                    if (!NetStatus.SUCCEEDED(res))
                        Core.Panic("Error handling internal event.");
                }
            }

            Core.Log("NetStack dispatcher exiting...\n");
        }

        // Constructor
        public Dispatcher()
        {
            this.timers = new RJBlack.Timer.TimerWheel(10000, 16, (ulong)DateTime.UtcNow.Ticks);

            CallbackArgs[]! eArgs = this.eArgs = new CallbackArgs[1];

            Callback[]! eFuns = this.eFuns = new Callback[1];

            AutoResetEvent! dispatcherStrobe = this.dispatcherStrobe = new AutoResetEvent(false);
            WaitHandle[]! eHandles = this.eHandles = new WaitHandle[1];

            base();

            // The first eHandles element is always our private strobe
            eHandles[0] = dispatcherStrobe;
            eArgs[0] = null;
            eFuns[0] = null;
        }

    } // Dispatcher

} // NetStack
// End
