/*********************************************************************
 *
 * AUTHORIZATION TO USE AND DISTRIBUTE
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that: 
 *
 * (1) source code distributions retain this paragraph in its entirety, 
 *  
 * (2) distributions including binary code include this paragraph in
 *     its entirety in the documentation or other materials provided 
 *     with the distribution, and 
 *
 * (3) all advertising materials mentioning features or use of this 
 *     software display the following acknowledgment:
 * 
 *      "This product includes software written and developed 
 *       by Brian Adamson and Joe Macker of the Naval Research 
 *       Laboratory (NRL)." 
 *         
 *  The name of NRL, the name(s) of NRL  employee(s), or any entity
 *  of the United States Government may not be used to endorse or
 *  promote  products derived from this software, nor does the 
 *  inclusion of the NRL written and developed software  directly or
 *  indirectly suggest NRL or United States  Government endorsement
 *  of this product.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ********************************************************************/

#ifndef _EVENT_DISPATCHER
#define _EVENT_DISPATCHER

#include "protocolTimer.h"
#include "udpSocket.h"

// Asynchronous event dispatcher class
class EventDispatcher
{
    public:
        // Asynchronous event types
        enum EventType {EVENT_NULL, EVENT_INPUT, EVENT_OUTPUT, EVENT_DETACH};
    
#ifdef WIN32
        typedef HANDLE Descriptor;  // WIN32 uses "HANDLE" type for descriptors
        typedef DWORD WaitStatus;
#endif // WIN32
#ifdef UNIX
        typedef int Descriptor;      // UNIX uses "int" type for descriptors
        typedef int WaitStatus;
#endif // UNIX
        static const Descriptor INVALID_DESCRIPTOR;

        // For arbitrary asynchronous file/device descriptor monitoring
        typedef void (*Callback)(Descriptor descriptor, EventType theEvent, const void* userData);

                
    // Construction
        EventDispatcher();
        ~EventDispatcher();
        void Destroy();
        
    // Methods to add remove inputs, timers, etc
        bool AddGenericInput(Descriptor descriptor, 
                             EventDispatcher::Callback inputCallback, 
                             const void* clientData)
        {
            return AddGenericItem(Item::INPUT, descriptor, inputCallback, clientData);   
        }  
        void RemoveGenericInput(Descriptor descriptor);
        
        bool AddGenericOutput(Descriptor descriptor, 
                              EventDispatcher::Callback inputCallback, 
                              const void* clientData)
        {
            return AddGenericItem(Item::OUTPUT, descriptor, inputCallback, clientData);   
        }      
        void RemoveGenericOutput(Descriptor descriptor);
        
        bool AddSocketInput(UdpSocket* theSocket);
        void RemoveSocketInput(UdpSocket* theSocket);
        
        void InstallTimer(ProtocolTimer* theTimer)
            {timer_mgr.InstallTimer(theTimer);}
        void RemoveTimer(ProtocolTimer* theTimer)
        {
            ASSERT(theTimer->IsActive());
            theTimer->Deactivate();   
        }

        // If this optional socket installer is used instead of
        // explicit calls to "AddSocketInput()", the UdpSocket "installData"
        // should be inited to a pointer to the EventDispatcher instance.
        static bool SocketInstaller(UdpSocketCmd  cmd,
                                    UdpSocket*    theSocket,
                                    const void*   installData);

        // A public static TimerInstaller so EventDispatcher users can use to init 
        // their own ProtocolTimerMgrs if desired.  As with the SocketInstaller
        // above, the "installData" should be a pointer to the EventDispatcher
        // instance.
        static bool TimerInstaller(ProtocolTimerInstallCmd cmd, 
                                   double                  delay,
                                   ProtocolTimerMgr*       timerMgr, 
                                   const void*             installData);
        
        
        // Methods to set up and control EventDispatcher execution
        
        // Are there any pending timeouts/descriptors
        bool IsPending() {return ((NULL != top_item) || (delay >= 0.0));}
        
        // Block until next timeout or descriptor ready
        EventDispatcher::WaitStatus WaitForNextEvent();
        
        // Dispatch events based on "WaitForNextEvent()" result
        void DispatchEvents(EventDispatcher::WaitStatus waitStatus);
        
        
        // This "main loop" can be used.
        int Run();
        void Stop(int exitCode) 
        {
            exit_code = exitCode;
            run = false;
            // (TBD) Set an "event" (WIN32) or somehow 
            // interrupt the select() (UNIX) call so that
            // when Stop() is called from a signal handler
            // or another thread, Run() exits immediately.
            // (Meanwhile PostMessage() will do it for WIN32
            // if Win32Init() was called.
            
#ifdef WIN32
            if (msg_window)
                PostMessage(msg_window, WM_DESTROY, NULL, NULL);
#endif // WIN32
        }

#ifdef WIN32
        // WIN32 apps can call this to create a window if desired
        bool Win32Init();
        void Win32Cleanup();
#endif // WIN32 
                   
    private:
        class Item
        {
            friend class EventDispatcher;
            
            public:
                //EventDispatcher::Descriptor Descriptor() {return descriptor;}
                
            private:
                void* operator new(size_t objSize, Item* item);
                void operator delete(void* ptr) {::delete (int*)ptr;}
                void operator delete(void* ptr, Item* item) {::delete (int*)ptr;}
                Item(EventDispatcher& theDispatcher);
                Item(EventDispatcher& theDispatcher, Descriptor theDescriptor, 
                     Callback func, const void* userData);
                Item(EventDispatcher& theDispatcher, UdpSocket* theSocket);
                Item(EventDispatcher& theDispatcher, ProtocolTimerMgr* timerMgr);
                ~Item();
                
                void OnInputEvent();
                void OnOutputEvent();
            
                enum Type {GENERIC, SOCKET, TIMER};
                enum Flag {NONE = 0x00, INPUT = 0x01, OUTPUT = 0x02, EXCEPTION = 0x04};
                
                bool IsActive() {return (0 != flags);}
                bool IsInput() {return (0 != (flags & INPUT));}
                bool IsOutput() {return (0 != (flags & OUTPUT));}
                void SetFlag(Flag f) {flags |= f;}
                void UnsetFlag(Flag f) {flags &= ~f;}
                bool FlagIsSet(Flag f) {return (0 != (f & flags));}
                ProtocolTimer* Timer() {return &timer;}
                bool OnTimeout();
                void SetTimeout(double theDelay);
                void DeactivateTimer() {timer.Deactivate();}
                
                EventDispatcher&             dispatcher;
                Type                         type;
                int                          flags;
                Descriptor                   descriptor;
                const void*                  data;
                Callback                     callback;
                ProtocolTimer                timer;
                
                Item*      prev;
                Item*      next;
        };  // end class EventDispatcher::Item

        friend class Item;

    // Methods
        bool AddGenericItem(EventDispatcher::Item::Flag flag,
                            Descriptor                  descriptor, 
                            EventDispatcher::Callback   callbackFunc, 
                            const void*                 clientData);  
        void ReduceItemStatus(Item *item, Item::Flag theFlag);
        void RemoveInput(EventDispatcher::Item* item)
            {ReduceItemStatus(item, Item::INPUT);}
        void RemoveOutput(EventDispatcher::Item* item)
            {ReduceItemStatus(item, Item::OUTPUT);}
        void RemoveItem(EventDispatcher::Item* theItem);
        Item* GetItemFromPool();
        void ReturnItemToPool(Item* item);
        EventDispatcher::Descriptor FindMaxDescriptor();
        Item* FindItemByDescriptor(EventDispatcher::Descriptor theDescriptor);
        Item* FindItemByData(const void* data);
        void SetTimeout(double value) {delay = value;}
        bool InstallTimerMgr(ProtocolTimerMgr* timerMgr, double delay);
        bool ModifyTimerMgr(ProtocolTimerMgr* timerMgr, double delay);
        bool RemoveTimerMgr(ProtocolTimerMgr* timerMgr);
        static bool PrivateTimerInstaller(ProtocolTimerInstallCmd   cmd, 
                                          double                    theDelay,
                                          ProtocolTimerMgr*         timerMgr,
                                          const void*               installData);
#ifdef WIN32
        static LRESULT CALLBACK MessageHandler(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
        bool Win32SocketInstall(EventDispatcher::Item* item, HANDLE eventHandle);
#endif // WIN32

    // Members
        ProtocolTimerMgr            timer_mgr;
        double                      delay;
        Item*                       top_item;
        Item*                       item_pool;
        Descriptor                  max_descriptor;
        bool                        run;
        int                         exit_code;
#ifdef UNIX
        fd_set                      input_set;
        fd_set                      output_set;
#endif // UNIX
#ifdef WIN32
        DWORD                       object_count;
        HANDLE*                     object_handles;
        Item**                      object_items;
        HWND                        msg_window;
#endif // WIN32
};  // end class EventDispatcher

#endif // _EVENT_DISPATCHER
