/*********************************************************************
 *
 * 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.
 ********************************************************************/
 

#include "protoDispatcher.h"

#include <stdio.h>
#include <string.h>

#ifdef WIN32
#ifndef _WIN32_WCE
#include <process.h>  // for _beginthreadex() and _endthreadex()
#endif // _WIN32_WCE
const ProtoDispatcher::Descriptor ProtoDispatcher::INVALID_DESCRIPTOR = (HANDLE)INVALID_SOCKET;
#else
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>
const ProtoDispatcher::Descriptor ProtoDispatcher::INVALID_DESCRIPTOR = -1;
#endif  // if/else WIN32/UNIX


ProtoDispatcher::Stream::Stream(Type theType)
 :  type(theType), flags(0), 
#ifdef WIN32
    index(-1),
#endif // WIN32
    prev(NULL), next(NULL)
{
}

ProtoDispatcher::SocketStream::SocketStream(ProtoSocket& theSocket)
 : Stream(SOCKET), socket(&theSocket)
{
}

ProtoDispatcher::GenericStream::GenericStream(Descriptor theDescriptor)
 : Stream(GENERIC), descriptor(theDescriptor), callback(NULL), client_data(NULL)
{
}
            
ProtoDispatcher::ProtoDispatcher()
    : socket_stream_pool(NULL), socket_stream_list(NULL),
      generic_stream_pool(NULL), generic_stream_list(NULL),  
      run(false), wait_status(-1), exit_code(0), 
      thread_handle((ThreadHandle)(NULL)), thread_started(false),
      controller(NULL)
#ifdef WIN32
      ,stream_array_size(DEFAULT_ITEM_ARRAY_SIZE), 
      stream_handles_array(stream_handles_default), 
      stream_ptrs_array(stream_ptrs_default), 
      stream_count(0), msg_window(NULL),
      break_event(NULL), socket_output_pending(false)
{
#else
{    
    break_pipe_fd[0] = break_pipe_fd[1] = INVALID_DESCRIPTOR;
#endif // if/else WIN32/UNIX
  
}

ProtoDispatcher::~ProtoDispatcher()
{
    Destroy();
}

void ProtoDispatcher::Destroy()
{
    Stop();
    while (socket_stream_list) 
    {
        socket_stream_list->GetSocket().SetNotifier(NULL);
        ReleaseSocketStream(socket_stream_list);
    }
    while (socket_stream_pool)
    {
        SocketStream* socketStream = socket_stream_pool;
        socket_stream_pool = (SocketStream*)socketStream->GetNext();
        delete socketStream;   
    }   
    while (generic_stream_list) RemoveGenericStream(generic_stream_list);    
    while (generic_stream_pool)
    {
        GenericStream* stream = (GenericStream*)generic_stream_pool;
        generic_stream_pool = (GenericStream*)stream->GetNext();
        delete stream;   
    }
#ifdef WIN32
    if (DEFAULT_ITEM_ARRAY_SIZE != stream_array_size)
    {
        delete[] stream_handles_array;
        stream_handles_array = stream_handles_default;
        delete[] stream_ptrs_array;
        stream_ptrs_array = stream_ptrs_default;
        stream_array_size = DEFAULT_ITEM_ARRAY_SIZE;
    }
    stream_count = 0;
    Win32Cleanup();
#endif // WIN32
}  // end ProtoDispatcher::Destroy()


bool ProtoDispatcher::UpdateSocketNotification(ProtoSocket& theSocket, 
                                               int          notifyFlags)
{
    SuspendThread();
    SocketStream* socketStream = GetSocketStream(theSocket);
    if (socketStream)
    {
        if (notifyFlags)
        {
#ifdef WIN32
            if (!ProtoAddress::Win32Startup())
            {
                DMSG(0, "ProtoDispatcher::UpdateSocketNotification() WSAStartup() error: %s\n",
                        ProtoSocket::GetErrorString());
                ResumeThread();
                return false;
            }
            if (!socketStream->HasFlags())
            {
                WSAEVENT eventHandle = WSACreateEvent();   
                if (eventHandle)
                {
                    if (!Win32AddStream(*socketStream, eventHandle))
                    {
                        WSACloseEvent(eventHandle); 
                        DMSG(0, "ProtoDispatcher::UpdateSocketNotification() error adding handle\n");
                        ReleaseSocketStream(socketStream);
                        ProtoAddress::Win32Cleanup();
                        ResumeThread();
                        return false;     
                    }
                }
                else
                {
                    DMSG(0, "ProtoDispatcher::UpdateSocketNotification() WSACreateEvent() error: %d\n",
                            WSAGetLastError());
                    ReleaseSocketStream(socketStream);
                    ProtoAddress::Win32Cleanup();
                    ResumeThread();
                    return false;   
                }      
            }
            long eventMask = 0;
            if (0 != (notifyFlags & ProtoSocket::NOTIFY_INPUT))
                eventMask |= (FD_READ | FD_ACCEPT | FD_CLOSE);
            if (0 != (notifyFlags & ProtoSocket::NOTIFY_OUTPUT))
                eventMask |= (FD_WRITE | FD_CONNECT | FD_CLOSE);
            int index = socketStream->GetIndex();
            if (0 != WSAEventSelect(theSocket.GetHandle(), stream_handles_array[index], eventMask))
            {
                WSACloseEvent(stream_handles_array[index]);
                Win32RemoveStream(*socketStream);
                ReleaseSocketStream(socketStream);
                DMSG(0, "ProtoDispatcher::UpdateSocketNotification() WSAEventSelect(0x%x) error: %s\n",
                        eventMask, ProtoSocket::GetErrorString());
                ProtoAddress::Win32Cleanup();
                ResumeThread();
                return false;
            }
            ProtoAddress::Win32Cleanup();
#endif // WIN32
            socketStream->SetFlags(notifyFlags);
        }
        else
        {
#ifdef WIN32
            if (socketStream->HasFlags())
            {
                if (!ProtoAddress::Win32Startup())
                {
                    DMSG(0, "ProtoDispatcher::UpdateSocketNotification() WSAStartup() error: %s\n",
                            ProtoSocket::GetErrorString());
                    ResumeThread();
                    return false;
                }
                WSAEVENT eventHandle = stream_handles_array[socketStream->GetIndex()];
                if (0 != WSAEventSelect(theSocket.GetHandle(), eventHandle, 0))
                    DMSG(12, "ProtoDispatcher::UpdateSocketNotification() WSAEventSelect(0) warning: %s\n",
                             ProtoSocket::GetErrorString());
                WSACloseEvent(eventHandle);
                Win32RemoveStream(*socketStream);
                ProtoAddress::Win32Cleanup();
            } 
#endif // WIN32  
            ReleaseSocketStream(socketStream);
        }
        ResumeThread();
        return true;
    }
    else
    {
        DMSG(0, "ProtoDispatcher::UpdateSocketNotification() new SocketStream error: %s\n",
                GetErrorString());
        ResumeThread();
        return false;   
    }
}  // end ProtoDispatcher::UpdateSocketNotification()

ProtoDispatcher::SocketStream* ProtoDispatcher::GetSocketStream(ProtoSocket& theSocket)
{
    // First, search our list of active sockets
    SocketStream* socketStream = socket_stream_list;
    while (socketStream)
    {
        if (&theSocket == &socketStream->GetSocket())
            break;
        else
            socketStream = (SocketStream*)socketStream->GetNext();  
    }
    if (!socketStream)
    {
        // Get one from the pool or create a new one
        socketStream = socket_stream_pool;
        if (socketStream)
        {
            socket_stream_pool = (SocketStream*)socketStream->GetNext();
            socketStream->ClearFlags();
            socketStream->SetSocket(theSocket);
        }
        else
        {
            if (!(socketStream = new SocketStream(theSocket)))
            {
                DMSG(0, "ProtoDispatcher::GetSocketStream() new SocketStream error: %s\n", GetErrorString());
                return NULL;   
            }
        }
        // Prepend to "active" socket stream list
        socketStream->SetPrev(NULL);
        socketStream->SetNext(socket_stream_list);
        if (socket_stream_list) 
            socket_stream_list->SetPrev(socketStream);
        socket_stream_list = socketStream;
    }
    return socketStream;
}  // end ProtoDispatcher::GetSocketStream()

void ProtoDispatcher::ReleaseSocketStream(SocketStream* socketStream)
{
    socketStream->ClearFlags();
    SocketStream* prevStream = (SocketStream*)socketStream->GetPrev();
    SocketStream* nextStream = (SocketStream*)socketStream->GetNext();
    if (prevStream)
        prevStream->SetNext(nextStream);
    else
        socket_stream_list = nextStream;
    if (nextStream) nextStream->SetPrev(prevStream);
    socketStream->SetNext(socket_stream_pool);
    socket_stream_pool = socketStream;
}  // end ProtoDispatcher::ReleaseSocketStream()

bool ProtoDispatcher::InstallGenericStream(ProtoDispatcher::Descriptor descriptor, 
                                           Callback*                   callback, 
                                           const void*                 userData,
                                           Stream::Flag                flag)
{
    
    GenericStream* stream = GetGenericStream(descriptor);
    if (stream)
    {
#ifdef WIN32
        if (!stream->HasFlags())
        {
            if (!Win32AddStream(*stream, descriptor))
            {
                DMSG(0, "ProtoDispatcher::InstallGenericStream() error adding stream\n");
                ReleaseGenericStream(stream);
                return false;   
            }   
        }
#endif // WIN32
        stream->SetCallback(callback, userData);
        stream->SetFlag(flag);
        return true;
    }
    else
    {
        DMSG(0, "ProtoDispatcher::InstallGenericStream() error getting GenericStream\n");
        return false;
    }
}  // end ProtoDispatcher::InstallGenericStream()

ProtoDispatcher::GenericStream* ProtoDispatcher::GetGenericStream(Descriptor descriptor)
{
    // First, search our list of active generic streams
    GenericStream* stream = generic_stream_list;
    while (stream)
    {
        if (descriptor == stream->GetDescriptor())
            break;
        else
            stream = (GenericStream*)stream->GetNext();   
    }
    if (!stream)
    {
        // Get one from the pool or create a new one
        stream = generic_stream_pool;
        if (stream)
        {
            generic_stream_pool = (GenericStream*)stream->GetNext();
            stream->ClearFlags();
            stream->SetDescriptor(descriptor);
        }
        else
        {
            if (!(stream = new GenericStream(descriptor)))
            {
                DMSG(0, "ProtoDispatcher::GetGenericStream() new GenericStream error: %s\n", GetErrorString());
                return NULL;   
            }
        }
        // Add to "active" generic stream list
        stream->SetPrev(NULL);
        stream->SetNext(generic_stream_list);
        if (generic_stream_list) generic_stream_list->SetPrev(stream);
        generic_stream_list = stream;
    }
    return stream;
}  // end ProtoDispatcher::GetGenericStream()

ProtoDispatcher::GenericStream* ProtoDispatcher::FindGenericStream(Descriptor descriptor) const
{
    GenericStream* next = generic_stream_list;
    while (next)
    {
        if (next->GetDescriptor() == descriptor)
            return next;
        else
            next = (GenericStream*)next->GetNext();
    }
    return NULL;
}  // end ProtoDispatcher::FindGenericStream()

void ProtoDispatcher::ReleaseGenericStream(GenericStream* stream)
{
    stream->ClearFlags();
    // Move from "active" generic stream list to generic stream "pool"
    GenericStream* prevStream = (GenericStream*)stream->GetPrev();
    GenericStream* nextStream = (GenericStream*)stream->GetNext();
    if (prevStream)
        prevStream->SetNext(nextStream);
    else
        generic_stream_list = nextStream;
    if (nextStream) nextStream->SetPrev(prevStream);
    stream->SetNext(generic_stream_pool);
    generic_stream_pool = stream;
}  // end ProtoDispatcher::ReleaseGenericStream()

// An ProtoDispatcher "main loop"
int ProtoDispatcher::Run()
{
    exit_code = 0;
    run = true;
    while (run)
    {     
        if (IsPending())
        {
            Wait();
            if (controller)
            {
                // Relinquish to controller thread
                Unlock(suspend_lock);
                controller->Dispatch();
                Lock(suspend_lock);   
            }
            else
            {
                // Do our own event dispatching
                Dispatch();
            }
        }
        else
        {
            DMSG(0, "ProtoDispatcher::Run() would be stuck with infinite timeout & no inputs!\n");
            break;
        }
    }  // end while(run)
    return exit_code;
}  // end ProtoDispatcher::Run()

void ProtoDispatcher::Stop(int exitCode)
{
    if (controller)
    {
        controller->OnThreadStop();
        controller = NULL;
    }
    SuspendThread();
    exit_code = run ? exitCode : exit_code;
    run = false;
#ifdef WIN32
    if (msg_window)
        PostMessage(msg_window, WM_DESTROY, NULL, NULL);
#endif // WIN32
    ResumeThread();
    DestroyThread();
}  // end ProtoDispatcher::Stop();


#ifdef WIN32
#ifdef _WIN32_WCE
DWORD WINAPI ProtoDispatcher::DoThreadStart(LPVOID param)
#else
unsigned int __stdcall ProtoDispatcher::DoThreadStart(void* param)
#endif // if/else _WIN32_WCE
#else
void* ProtoDispatcher::DoThreadStart(void* param)
#endif // if/else WIN32/UNIX
{
    ProtoDispatcher* dp = reinterpret_cast<ProtoDispatcher*>(param);
    ASSERT(dp);
    Init(dp->suspend_lock);
    Lock(dp->suspend_lock);
    dp->thread_started = true;
    ExitStatus result = (ExitStatus)dp->Run();
    Unlock(dp->suspend_lock);
    DoThreadExit(result);
    return result;
}  // end ProtoDispatcher::DoThreadStart()

void ProtoDispatcher::DoThreadBreak(ProtoDispatcher::Descriptor descriptor, 
                                    ProtoDispatcher::Event      /*theEvent*/, 
                                    const void*                 userData)
{
    ProtoDispatcher* dp = reinterpret_cast<ProtoDispatcher*>((void*)userData);
#ifdef UNIX
    char byte;
    read(dp->break_pipe_fd[0], &byte, 1);
#endif // UNIX
    Unlock(dp->suspend_lock);
    Lock(dp->signal_lock);
    Lock(dp->suspend_lock);
    Unlock(dp->signal_lock);
}  // end ProtoDispatcher::DoThreadBreak()

bool ProtoDispatcher::StartThread(ProtoDispatcher::Controller* theController)
{
    if (IsThreaded())
    {
        DMSG(0, "ProtoDispatcher::StartThread() error: thread already started\n");
        return false;
    }
    if (!InstallBreak())
    {
        DMSG(0, "ProtoDispatcher::StartThread() error: InstallBreak() failed\n");
        return false;
    }
    controller = theController;
    Init(signal_lock);
    Unlock(signal_lock);
#ifdef WIN32
    // Note: For WinCE, we need to use CreateThread() instead of _beginthreadex()
#ifdef _WIN32_WCE
    DWORD threadId;
    if (!(thread_handle = CreateThread(NULL, 0, DoThreadStart, this, 0, &threadId)))
#else
    unsigned int threadId;
    if (!(thread_handle = (HANDLE)_beginthreadex(NULL, 0, DoThreadStart, this, 0, &threadId)))
#endif // if/else _WIN32_WCE
#else
    if (0 != pthread_create(&thread_handle, NULL, DoThreadStart, this))    
#endif // if/else WIN32/UNIX
    {
        DMSG(0, "ProtoDispatcher::StartThread() create thread error: %s\n", GetErrorString());
        RemoveBreak();
        thread_handle = (ThreadHandle)NULL;
        Destroy(signal_lock);
        controller = NULL;
        return false;
    }
    return true;
}  // end ProtoDispatcher::StartThread()

bool ProtoDispatcher::SuspendThread()
{
    if (IsThreaded() && !IsMyself())
    {
        // First, signal dispatcher of our intention
        Lock(signal_lock);
        // 2) Explicitly signal the thread via a "break" signal
#ifdef WIN32
        if (0 == SetEvent(break_event))
        {
            Unlock(signal_lock);
            DMSG(0, "ProtoDispatcher::SuspendThread() SetEvent(break_event) error\n");
            return false;
        }
#else
        while (1)
        {
            char byte;
            int result = write(break_pipe_fd[1], &byte, 1);
            if (1 == result)
            {
                break;
            }
            else if (0 == result)
            {
                DMSG(0, "ProtoDispatcher::SuspendThread() warning: write() returned zero\n");
                continue;
            }
            else
            {
                if (EINTR == errno) continue;
                DMSG(0, "ProtoDispatcher::SuspendThread() write() error: %s\n",
                        GetErrorString());
                Unlock(signal_lock);
                return false;
            }
        }
#endif // if/else WIN32/UNIX
        // We use while() here to make sure the thread started
        // (TBD) use a spin_count to limit iterations as safeguard
        while (!thread_started);
        Lock(suspend_lock);
        Unlock(signal_lock);
    }
    return true;
}  // end ProtoDispatcher::SuspendThread()

void ProtoDispatcher::ResumeThread()
{
    if (IsThreaded() && !IsMyself())
        Unlock(suspend_lock);
}  // end ProtoDispatcher::ResumeThread()

void ProtoDispatcher::DestroyThread()
{
    if (IsThreaded())
    { 
        controller = NULL;
#ifdef WIN32
        if (!IsMyself()) WaitForSingleObject(thread_handle, INFINITE);
        thread_started = false;
#ifdef _WIN32_WCE
        CloseHandle(thread_handle);
#endif // _WIN32_WCE
#else
        if (!IsMyself()) pthread_join(thread_handle, NULL);
#endif // if/else WIN32/UNIX
        thread_handle = (ThreadHandle)NULL;
        RemoveBreak();
        Destroy(signal_lock);
        Destroy(suspend_lock);
    }
}  // end ProtoDispatcher::DestroyThread()

/////////////////////////////////////////////////////////////////////////
// Unix-specific methods and implementation
#ifdef UNIX

bool ProtoDispatcher::InstallBreak()
{
    if (0 != pipe(break_pipe_fd))
    {
        DMSG(0, "ProtoDispatcher::InstallBreak() pipe() error: %s\n",
                GetErrorString());
        return false;
    }
    if (!InstallGenericInput(break_pipe_fd[0], DoThreadBreak, this))
    {
        close(break_pipe_fd[0]);
        close(break_pipe_fd[1]);
        DMSG(0, "ProtoDispatcher::InstallBreak() InstallGenericInput() error\n");
        return false;   
    }
    return true;
}  // end ProtoDispatcher::InstallBreak()

void ProtoDispatcher::RemoveBreak()
{
    if (INVALID_DESCRIPTOR != break_pipe_fd[0])
    {
        RemoveGenericInput(break_pipe_fd[0]);
        close(break_pipe_fd[0]);
        close(break_pipe_fd[1]);
        break_pipe_fd[0] = INVALID_DESCRIPTOR;
    }
}  // end ProtoDispatcher::RemoveBreak()

// Warning! This may block indefinitely is !IsPending() ...
void ProtoDispatcher::Wait()
{
    // (TBD) We could put some code here to protect this from
    // being called by the wrong thread?
    struct timeval timeout;
    struct timeval* timeoutPtr;
    double timerDelay = ProtoTimerMgr::GetTimeRemaining();
    if (timerDelay < 0.0)
    {
        timeoutPtr = NULL;      
    }
    else
    {
        timeout.tv_sec = (unsigned long)timerDelay;
        timeout.tv_usec = 
            (unsigned long)(1.0e+06 * (timerDelay - (double)timeout.tv_sec));
        timeoutPtr = &timeout;
    }
    
    FD_ZERO(&input_set);
    FD_ZERO(&output_set);
    int maxDescriptor = -1;
    // Monitor socket streams ...
    SocketStream* nextSocket = socket_stream_list;
    while (nextSocket)
    {
        Descriptor descriptor = nextSocket->GetSocket().GetHandle();
        if (nextSocket->IsInput()) FD_SET(descriptor, &input_set);
        if (nextSocket->IsOutput()) FD_SET(descriptor, &output_set);
        if (descriptor > maxDescriptor) maxDescriptor = descriptor;
        nextSocket = (SocketStream*)nextSocket->GetNext();    
    }
    // Monitor generic streams ...
    GenericStream* nextStream = generic_stream_list;
    while (nextStream)
    {
        Descriptor descriptor = nextStream->GetDescriptor();
        if (nextStream->IsInput()) FD_SET(descriptor, &input_set);
        if (nextStream->IsOutput()) FD_SET(descriptor, &output_set);
        if (descriptor > maxDescriptor) maxDescriptor = descriptor;
        nextStream = (GenericStream*)nextStream->GetNext();    
    }
    
    // (TBD) It might be nice to use "pselect()" here someday
    wait_status = select(maxDescriptor+1, 
                         (fd_set*)&input_set, 
                         (fd_set*)&output_set, 
                         (fd_set*) NULL, 
                         timeoutPtr);
}  // end ProtoDispatcher::Wait()

void ProtoDispatcher::Dispatch()
{
    switch (wait_status)
    {
        case -1:
            if (EINTR != errno)
                DMSG(0, "ProtoDispatcher::Dispatch() select() error: %s\n", GetErrorString());
            break;

        case 0:
            // timeout only ?
            OnSystemTimeout();
            break;

        default:
            // (TBD) make this safer ...
            SocketStream* nextSocketStream = socket_stream_list;
            while (nextSocketStream)
            {
                ProtoSocket& theSocket = nextSocketStream->GetSocket();
                Descriptor descriptor = theSocket.GetHandle();
                if (nextSocketStream->IsInput() && FD_ISSET(descriptor, &input_set))
                {
                    theSocket.OnNotify(ProtoSocket::NOTIFY_INPUT);
                    //break;
                }
                if (nextSocketStream->IsOutput() && FD_ISSET(descriptor, &output_set))
                {
                    theSocket.OnNotify(ProtoSocket::NOTIFY_OUTPUT);
                    //break;
                }
                nextSocketStream = (SocketStream*)nextSocketStream->GetNext();
            }
            // Check generic streams
            GenericStream* nextStream = generic_stream_list;
            while (nextStream)
            {
                Descriptor descriptor = nextStream->GetDescriptor();
                if (nextStream->IsInput() && FD_ISSET(descriptor, &input_set))
                {
                    nextStream->OnEvent(EVENT_INPUT);
                    //break;
                }
                if (nextStream->IsOutput() && FD_ISSET(descriptor, &output_set))
                {
                    nextStream->OnEvent(EVENT_OUTPUT);
                    //break;
                }
                nextStream = (GenericStream*)nextStream->GetNext(); 
            }
            break;
    }  // end switch(status)
}  // end ProtoDispatcher::Dispatch()

#endif // UNIX


#ifdef WIN32

bool ProtoDispatcher::InstallBreak()
{
    // The break_event used to break the MsgWaitForMultipleObjectsEx() call
    if (!(break_event = CreateEvent(NULL, FALSE, FALSE, NULL)))
    {
        DMSG(0, "ProtoDispatcher::InstallBreak() CreateEvent() error\n");
        return false;
    }

    if (!InstallGenericInput(break_event, DoThreadBreak, this))
    {
        DMSG(0, "ProtoDispatcher::InstallBreak() InstallGenericInput() error\n");
        CloseHandle(break_event);
        break_event = NULL;
        return false;
    }
    return true;
}  // end ProtoDispatcher::InstallBreak()

void ProtoDispatcher::RemoveBreak()
{
    if (break_event)
    {
        RemoveGenericInput(break_event);
        CloseHandle(break_event);
        break_event = NULL;
    }
}  // end ProtoDispatcher::RemoveBreak()

bool ProtoDispatcher::Win32Init()
{
    HINSTANCE theInstance = GetModuleHandle(NULL);    
    // Register our msg_window class
    WNDCLASS cl;
    cl.style = CS_HREDRAW | CS_VREDRAW;
    cl.lpfnWndProc = MessageHandler;
    cl.cbClsExtra = 0;
    cl.cbWndExtra = 0;
    cl.hInstance = NULL;//theInstance;
    cl.hIcon = NULL;
    cl.hCursor = NULL;
    cl.hbrBackground = NULL;
    cl.lpszMenuName = NULL;
    cl.lpszClassName = (LPCTSTR)"ProtoDispatcher";
    if (!RegisterClass(&cl))
    {
        LPVOID lpMsgBuf;
        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | 
                      FORMAT_MESSAGE_FROM_SYSTEM | 
                      FORMAT_MESSAGE_IGNORE_INSERTS,
                      NULL, GetLastError(),
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
                      (LPTSTR) &lpMsgBuf, 0, NULL );
        // Display the string.
        MessageBox( NULL, (LPCTSTR)lpMsgBuf, (LPCTSTR)"Error", MB_OK | MB_ICONINFORMATION );
        // Free the buffer.
        LocalFree(lpMsgBuf);
        DMSG(0, "ProtoDispatcher::Win32Init() Error registering message window class!\n");
        return false;
    }

#ifndef _WIN32_WCE
    HWND parent = HWND_MESSAGE;    
#else
    HWND parent = NULL;
#endif // if/else !_WIN32_WCE

    // Create msg_window to receive event messages
    msg_window = CreateWindow((LPCTSTR)"ProtoDispatcher",  // registered class name
                              (LPCTSTR)"ProtoDispatcher",  // window name
                              WS_OVERLAPPED,             // window style
                              0,                // horizontal position of window
                              0,                // vertical position of window
                              0,              // window width
                              0,             // window height
                              parent,  // handle to parent or owner window
                              NULL,          // menu handle or child identifier
                              NULL,//theInstance,  // handle to application instance
                              this);        // window-creation data
    if (msg_window)
    {
        ShowWindow(msg_window, SW_HIDE);
        return true;
    }
    else
    {
        DMSG(0, "ProtoDispatcher::Win32Init() Error creating message window!\n");
        return false;
    }
}  // end ProtoDispatcher::Win32Init()

void ProtoDispatcher::Win32Cleanup()
{
    if (msg_window)
    {
        DestroyWindow(msg_window);
        msg_window = NULL;
    }
}  // end Win32Cleanup()

bool ProtoDispatcher::Win32AddStream(Stream& stream, HANDLE handle)
{
    unsigned int index = stream_count;
    if (stream_count >= stream_array_size)
    {
        if (!Win32IncreaseStreamArraySize())
        {
            DMSG(0, "ProtoDispatcher::AddStream() error increasing array size\n");
            return false;
        }   
    }
    stream_handles_array[index] = handle;
    stream_ptrs_array[index] = &stream;
    stream.SetIndex(index);
    stream_count++;        
    return true;
}  // end ProtoDispatcher::Win32AddStream()

void ProtoDispatcher::Win32RemoveStream(Stream& stream)
{ 
    unsigned int index = stream.GetIndex();
    ASSERT(index < stream_count);
    unsigned int moveCount = stream_count - index - 1;
    memmove(stream_handles_array+index, stream_handles_array+index+1, 
            moveCount*sizeof(HANDLE));
    memmove(stream_ptrs_array+index, stream_ptrs_array+index+1,
            moveCount*sizeof(Stream*));
    stream_count--;
    for (; index < stream_count; index++)
        stream_ptrs_array[index]->SetIndex(index);
}  // end ProtoDispatcher::Win32RemoveStream

bool ProtoDispatcher::Win32IncreaseStreamArraySize()
{
    unsigned int newSize = 2 * stream_array_size;
    HANDLE* hPtr = new HANDLE[newSize];
    Stream** iPtr = new Stream*[newSize];
    if (hPtr && iPtr)
    {
        memcpy(hPtr, stream_handles_array, stream_count*sizeof(HANDLE));
        memcpy(iPtr, stream_ptrs_array, stream_count*sizeof(Stream*));
        if (stream_handles_default != stream_handles_array)
            delete[] stream_handles_array;
        if (stream_ptrs_default != stream_ptrs_array)
            delete[] stream_ptrs_array;
        stream_handles_array = hPtr;
        stream_ptrs_array = iPtr;
        return true;
    }
    else
    {
        if (hPtr) delete[] hPtr;
        if (iPtr) delete[] iPtr;
        return false;    
    }        
}  // end ProtoDispatcher::Win32IncreaseStreamArraySize()

void ProtoDispatcher::Wait()
{
    double timerDelay = ProtoTimerMgr::GetTimeRemaining();
    DWORD msec = (timerDelay < 0.0) ? INFINITE : ((DWORD)(1000.0 * timerDelay));

    // Don't wait if any sockets are still "OutputReady()"
    // Monitor socket streams ...
    SocketStream* nextSocket = socket_stream_list;
    while (nextSocket)
    {
        if (nextSocket->IsOutput() && nextSocket->GetSocket().IsOutputReady()) 
        {
            msec = 0;
            socket_output_pending = true;
            break;
        }
        nextSocket = (SocketStream*)nextSocket->GetNext();    
    }

    wait_status =  MsgWaitForMultipleObjectsEx(stream_count,   // number of handles in array
                                                stream_handles_array, // object-handle array
                                                msec,           // time-out interval
                                                QS_ALLINPUT,   // input-event type
                                                MWMO_INPUTAVAILABLE 
#ifndef _WIN32_WCE
                                                | MWMO_ALERTABLE
#endif // !_WIN32_WCE
                                                );
    
}  // end ProtoDispatcher::Wait()
      
void ProtoDispatcher::Dispatch()
{ 
    switch (wait_status)
    {
        case -1:            // error status
        {
            char errorString[256];
            errorString[255] = '\0';
            FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | 
                          FORMAT_MESSAGE_IGNORE_INSERTS,
                          NULL,
                          GetLastError(),
                          MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
                          (LPTSTR) errorString, 255, NULL);
            DMSG(0, "ProtoDispatcher::Dispatch() MsgWaitForMultipleObjectsEx() error: %s\n", errorString);
            break;
        }
        
        case WAIT_TIMEOUT:  // timeout condition
            if (socket_output_pending)
            {
                SocketStream* nextSocket = socket_stream_list;
                while (nextSocket)
                {
                    if (nextSocket->IsOutput())
                    {
                        ProtoSocket& socket = nextSocket->GetSocket();
                        if (socket.IsOutputReady())
                            socket.OnNotify(ProtoSocket::NOTIFY_OUTPUT);
                    }
                    nextSocket = (SocketStream*)nextSocket->GetNext(); 
                }
                socket_output_pending = false;
            }
            break;
            
        default:
            if ((WAIT_OBJECT_0 <= wait_status) && (wait_status < (WAIT_OBJECT_0 + stream_count)))
            {
                unsigned int index = wait_status - WAIT_OBJECT_0;
                Stream* stream = stream_ptrs_array[index];
                if (Stream::GENERIC == stream->GetType())
                {
                    // (TBD) Can we test the handle for input/output readiness
                    if (stream->IsInput()) 
                        static_cast<GenericStream*>(stream)->OnEvent(EVENT_INPUT);
                    if (stream->IsOutput()) 
                        static_cast<GenericStream*>(stream)->OnEvent(EVENT_OUTPUT);
                }
                else
                {
                    ProtoSocket& socket = static_cast<SocketStream*>(stream)->GetSocket();
                    WSANETWORKEVENTS event;
                    if (0 == WSAEnumNetworkEvents(socket.GetHandle(), stream_handles_array[index], &event))
                    {
                        if (0 != (event.lNetworkEvents & (FD_READ | FD_ACCEPT))) 
                            socket.OnNotify(ProtoSocket::NOTIFY_INPUT);
                        if (0 != (event.lNetworkEvents & FD_WRITE)) 
                            socket.OnNotify(ProtoSocket::NOTIFY_OUTPUT);
                        if (0 != (event.lNetworkEvents & FD_CLOSE)) 
                            socket.OnNotify(ProtoSocket::NOTIFY_NONE);
                        if (0 != (event.lNetworkEvents & FD_CONNECT))
                        {
                            // Check connect event for error condition
                            if (0 == event.iErrorCode[FD_CONNECT_BIT])
                                socket.OnNotify(ProtoSocket::NOTIFY_OUTPUT);
                            else 
                                socket.OnNotify(ProtoSocket::NOTIFY_NONE);
                        }
                    }
                    else
                    {
                        DMSG(0, "ProtoDispatcher::Dispatch() WSAEnumNetworkEvents() error\n");
                    }
                }
            }
            else if ((WAIT_OBJECT_0 + stream_count) == wait_status)
            {
                // Windows GUI message event
                MSG msg;
                // Use PeekMessage to make sure it's not a false alarm
                if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
                {
                    //if (GetMessage(&msg, NULL, 0, 0 ))
                    if (WM_QUIT != msg.message)
                    {
                        TranslateMessage(&msg);
                        DispatchMessage(&msg);
                    }
                    else
                    {
                        exit_code = msg.wParam;
                        run = false;
                        break;  // we're done
                    }
                }
            }
            else
            {
                // (TBD) handle these conditions when needed
                // WAIT_ABANDONED or WAIT_IO_COMPLETION
            } 
            break;
    }  // end switch(status)
    OnSystemTimeout();
}  // end ProtoDispatcher::Dispatch() 

LRESULT CALLBACK ProtoDispatcher::MessageHandler(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message) 
    {
        case WM_CREATE:
        {
            CREATESTRUCT *info = (CREATESTRUCT *) lParam;
            ProtoDispatcher* dp = (ProtoDispatcher*)info->lpCreateParams;
            SetWindowLong(hwnd, GWL_USERDATA, (DWORD)dp);
            return 0;
        }    
        case WM_DESTROY:
        {
            ProtoDispatcher* dp = (ProtoDispatcher*)GetWindowLong(hwnd, GWL_USERDATA);
            dp->msg_window = NULL;
            PostQuitMessage(0);
            return 0;
        }
        case WM_QUIT:
        {
            ProtoDispatcher* dp = (ProtoDispatcher*)GetWindowLong(hwnd, GWL_USERDATA);
            dp->run = false;
            return 0;
        }
        default:
            break;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}  // end ProtoDispatcher::MessageHandler()

#endif // WIN32

// UNIX ProtoDispatcher::Controller implementation
ProtoDispatcher::Controller::Controller(ProtoDispatcher& theDispatcher)
 : dispatcher(theDispatcher), use_lock_a(true)
{
    ProtoDispatcher::Init(lock_a);
    ProtoDispatcher::Init(lock_b); 
    ProtoDispatcher::Lock(lock_a);   
    ProtoDispatcher::Unlock(lock_b);
}

ProtoDispatcher::Controller::~Controller()
{
    Unlock(lock_a);
    Unlock(lock_b);
    Destroy(lock_a);
    Destroy(lock_b);  
}

bool ProtoDispatcher::Controller::Dispatch()
{
    // (TBD) make sure !IsMyself() ???
    if (use_lock_a)
        ProtoDispatcher::Unlock(lock_b);
    else
        ProtoDispatcher::Unlock(lock_a);
    if (!SignalDispatchReady())
    {
        DMSG(0, "ProtoDispatcher::Controller::Dispatch()) SignalDispatchReady() error\n");
        return false;
    }
    if (use_lock_a)
    {
        ProtoDispatcher::Lock(lock_a);
        use_lock_a = false;
    }
    else
    {
        ProtoDispatcher::Lock(lock_b);
        use_lock_a = true;
    }
    return true;
}  // end ProtoDispatcher::Controller::Suspend()

void ProtoDispatcher::Controller::OnDispatch()
{
    dispatcher.Dispatch();
    if (use_lock_a)
    {
        ProtoDispatcher::Lock(lock_b);
        ProtoDispatcher::Unlock(lock_a);      
    }  
    else
    {
        ProtoDispatcher::Lock(lock_a);
        ProtoDispatcher::Unlock(lock_b);
    }
}  // end ProtoDispatcher::Controller::OnSuspend()

