/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
/*****************************************************************************
*
*   $/Plasma20/Sources/Plasma/NucleusLib/pnAsyncCoreExe/Private/W9x/pnAceW9xSocket.cpp
*   
***/

#include "../../Pch.h"
#pragma hdrstop

#include "pnAceW9xInt.h"


namespace W9x {

/*****************************************************************************
*
*   Private data
*
***/

static HWND s_window;


/****************************************************************************
*
*   CSocketMapper
*
***/

class CSocket;

class CSocketMapper {
private:
    struct Map {
        AsyncCancelId           sequence;
        SOCKET                  sock;
        UINT                    message;
        CSocket *               object;
        FAsyncNotifySocketProc  notifyProc;
    };

    ARRAY(Map) m_mapTable;

public:
    void Add (
        AsyncCancelId           sequence,
        CSocket *               object,
        UINT                    message,
        SOCKET                  sock,
        FAsyncNotifySocketProc  notifyProc
    );
    void Delete (CSocket * object);
    CSocket * Find (AsyncCancelId sequence) const;
    CSocket * Find (CSocket * object) const;
    CSocket * Find (UINT message, SOCKET sock) const;
    CSocket * Find (FAsyncNotifySocketProc notifyProc, unsigned index) const;

};

//===========================================================================
void CSocketMapper::Add (
    AsyncCancelId           sequence,
    CSocket *               object,
    UINT                    message,
    SOCKET                  sock,
    FAsyncNotifySocketProc  notifyProc
) {
    Map * mapping = m_mapTable.New();
    mapping->sequence   = sequence;
    mapping->object     = object;
    mapping->message    = message;
    mapping->sock       = sock;
    mapping->notifyProc = notifyProc;
}

//===========================================================================
void CSocketMapper::Delete (CSocket * object) {
    for (unsigned index = m_mapTable.Count() - 1; index < m_mapTable.Count(); )
        if (m_mapTable[index].object == object)
            if (index + 1 < m_mapTable.Count())
                m_mapTable[index] = m_mapTable.Pop();
            else
                m_mapTable.Pop();
        else
            --index;
}

//===========================================================================
CSocket * CSocketMapper::Find (AsyncCancelId sequence) const {
    for (const Map * curr = m_mapTable.Ptr(), * term = m_mapTable.Term();
         curr != term;
         ++curr)
        if (curr->sequence == sequence)
            return curr->object;
    return nil;
}

//===========================================================================
CSocket * CSocketMapper::Find (CSocket * object) const {
    for (const Map * curr = m_mapTable.Ptr(), * term = m_mapTable.Term();
         curr != term;
         ++curr)
        if (curr->object == object)
            return curr->object;
    return nil;
}

//===========================================================================
CSocket * CSocketMapper::Find (UINT message, SOCKET sock) const {
    for (const Map * curr = m_mapTable.Ptr(), * term = m_mapTable.Term();
         curr != term;
         ++curr)
        if ((curr->message == message) && (curr->sock == sock))
            return curr->object;
    return nil;
}

//===========================================================================
CSocket * CSocketMapper::Find (FAsyncNotifySocketProc notifyProc, unsigned index) const {
    for (const Map * curr = m_mapTable.Ptr(), * term = m_mapTable.Term();
         curr != term;
         ++curr)
        if ((curr->notifyProc == notifyProc) && !index--)
            return curr->object;
    return nil;
}

static CSocketMapper s_mapper;


/****************************************************************************
*
*   CSocket
*
***/

class CSocket {
private:

    // These constants are used to track whether a socket has been connected,
    // and whether it has been disconnected. We do not track whether we have
    // dispatched a connection failure notification, because socket objects 
    // are deleted immediately after dispatching that notification.
    enum { kDispatchedConnectSuccess = (1 << 0) };
    enum { kDispatchedDisconnect     = (1 << 1) };

    struct Command {
        LINK(Command) link;
        enum Code {
            CONNECT,
            WRITE,
        } code;
        void *       param;
        union {
            struct {
                uint8_t connType;
            } connect;
            struct {
                const void * data;  // pointer to application's data
                unsigned     bytes;
            } write;
        };
    };

    // These variables are protected by the critical section
    CCritSect               m_critSect;
    LISTDECL(Command, link) m_commandList;
    ARRAY(uint8_t)             m_sendQueue;

    // These variables are never modified outside the constructor and
    // destructor
    UINT                    m_message;
    FAsyncNotifySocketProc  m_notifyProc;
    AsyncCancelId           m_sequence;
    SOCKET                  m_sock;

    // These variables are only ever touched during a callback from the
    // window procedure, which is single threaded
    unsigned                m_dispatched;
    uint8_t                    m_readBuffer[1460 * 2];
    unsigned                m_readBytes;
    void *                  m_userState;

public:
    CSocket (
        AsyncCancelId           sequence,
        UINT                    message,
        SOCKET                  sock,
        FAsyncNotifySocketProc  notifyProc
    );
    ~CSocket ();

    void EnableNagling (bool enable);
    void Disconnect (bool hardClose);

    inline AsyncCancelId GetSequence () const;
    inline bool IsConnected () const;
    inline bool IsDisconnected () const;

    void OnClose ();
    void OnConnect ();
    void OnReadReady ();
    void OnWriteReady ();

    void ProcessQueue ();
    void QueueConnect (
        void * param,
        uint8_t   connType
    );
    void QueueWrite (
        void *       param,
        const void * data, 
        unsigned     bytes
    );

    bool Send (  // returns false if disconnected
        const void * data,
        unsigned     bytes
    );

};

//===========================================================================
CSocket::CSocket (
    AsyncCancelId           sequence,
    UINT                    message,
    SOCKET                  sock,
    FAsyncNotifySocketProc  notifyProc
) :
    m_dispatched(0),
    m_notifyProc(notifyProc),
    m_readBytes(0),
    m_sequence(sequence),
    m_message(message),
    m_sock(sock),
    m_userState(nil)
{

    // Create a mapping for this socket
    s_mapper.Add(sequence, this, message, sock, notifyProc);

}

//===========================================================================
CSocket::~CSocket () {

    // If we dispatched a connect notification, verify that we also 
    // dispatched a disconnect notification
    ASSERT(IsDisconnected() || !IsConnected());

    // Delete the mapping for this socket
    s_mapper.Delete(this);

    // Close the socket
    closesocket(m_sock);
    m_sock = INVALID_SOCKET;

    // Free memory
    m_commandList.Clear();

}

//===========================================================================
void CSocket::EnableNagling (bool enable) {
    BOOL arg = !enable;
    int result = setsockopt(
        m_sock,
        IPPROTO_TCP,
        TCP_NODELAY,
        (const char *)&arg,
        sizeof(arg)
    );
    if (result)
        LogMsg(kLogFatal, "failed: setsockopt");
}

//===========================================================================
void CSocket::Disconnect (bool hardClose) {

    // This function is called outside the critical section, which is
    // necessary to avoid deadlocks, so it must not modify the object state.
    // The purpose of this function is to cause WinSock to generate a 
    // FD_CLOSE notification, which will initiate synchronous destruction
    // of the socket.

    if (m_sock == INVALID_SOCKET)
        return;

    // Shutdown the socket    
//    const unsigned SD_BOTH = 2;
    shutdown(m_sock, SD_BOTH);

    // Windows always waits for an acknowledgement back from the other end
    // of the connection, so we post our own FD_CLOSE notification if we need
    // to terminate this socket immediately. Our notification processing code
    // is robust against receiving the eventual FD_CLOSE from Windows even
    // if another socket has been allocated in the interrim with the same
    // socket handle.
    if (hardClose)
        PostMessage(s_window, m_message, m_sock, MAKELONG(FD_CLOSE, 0));

}

//===========================================================================
AsyncCancelId CSocket::GetSequence () const {
    return m_sequence;
}

//===========================================================================
bool CSocket::IsConnected () const {
    return (m_dispatched & kDispatchedConnectSuccess) != 0;
}

//===========================================================================
bool CSocket::IsDisconnected () const {
    return (m_dispatched & kDispatchedDisconnect) != 0;
}

//===========================================================================
void CSocket::OnClose () {

    // If we haven't yet dispatched a connection notification, then
    // dispatch a failure to connect rather than dispatching a disconnect
    // notification
    if (!IsConnected()) {
        OnConnect();
        return;
    }

    // Verify that we haven't already dispatched a disconnect notification
    if (IsDisconnected())
        return;

    // Process any remaining queued commands
    ProcessQueue();

    // Remove the mapping for this object so that we will never dispatch 
    // another event to it
    s_mapper.Delete(this);

    // Dispatch the disconnection notificaton
    m_dispatched |= kDispatchedDisconnect;
    m_notifyProc(
        (AsyncSocket)this,
        kNotifySocketDisconnect,
        nil,  // notify
        &m_userState
    );

    // This function must not perform any processing after returning from
    // the notification, because the application may have deleted the socket
    // out from under us

}

//===========================================================================
void CSocket::OnConnect () {

    // Verify that we haven't already dispatched a connection success
    // message. We know that we haven't already dispatched a connection
    // failure message, because the socket object would have already been
    // deleted in that case.
    if (IsConnected())
        return;

    // Verify that a connect command is queued
    m_critSect.Enter();
    Command * command = m_commandList.Head();
    if (command && (command->code == command->CONNECT))
        m_commandList.Unlink(command);
    else
        command = nil;
    m_critSect.Leave();
    if (!command)
        return;

    // Check for an error
    int error;
    int errorLen = sizeof(error);
    int result   = getsockopt(
        m_sock,
        SOL_SOCKET,
        SO_ERROR,
        (char *)&error,
        &errorLen
    );
    if (result)
        LogMsg(kLogFatal, "failed: getsockopt");

    // Get addresses for the connection notification
    AsyncNotifySocketConnect notify;
    memset(&notify.localAddr, 0, sizeof(notify.localAddr));
    memset(&notify.remoteAddr, 0, sizeof(notify.remoteAddr));
    int nameLen = sizeof(notify.localAddr);
    if (getsockname(m_sock, (sockaddr *)&notify.localAddr, &nameLen))
        if (GetLastError() == WSAENOTCONN)
            error = WSAENOTCONN;
        else
            LogMsg(kLogFatal, "failed: getsockname");
    nameLen = sizeof(notify.remoteAddr);
    if (getpeername(m_sock, (sockaddr *)&notify.remoteAddr, &nameLen))
        if (GetLastError() == WSAENOTCONN)
            error = WSAENOTCONN;
        else
            LogMsg(kLogFatal, "failed: getpeername");

    // Dispatch the connection notification
    notify.param    = command->param;
    notify.asyncId  = 0;
    notify.connType = command->connect.connType;
    bool notifyResult = m_notifyProc(
        error ? nil : (AsyncSocket)this,
        error ? kNotifySocketConnectFailed : kNotifySocketConnectSuccess,
        &notify,
        &m_userState
    );

    // Delete the connect command
    delete command;

    // Handle failure to connect
    if (error) {

        // Destroy the socket
        delete this;

    }

    // Handle a successful connection
    else {

        // Mark the socket as connected
        m_dispatched |= kDispatchedConnectSuccess;

        // If the application requested the socket closed, disconnect it
        if (!notifyResult) {
            Disconnect(true);
            OnClose();
        }

    }

}

//===========================================================================
void CSocket::OnReadReady () {
    for (;;) {

        // Issue the read
        int recvResult = recv(
            m_sock,
            (char *)(m_readBuffer + m_readBytes),
            sizeof(m_readBuffer) - m_readBytes,
            0  // flags
        );
        if (recvResult == SOCKET_ERROR)
            if (GetLastError() == WSAEWOULDBLOCK)
                return;
            else
                LogMsg(kLogFatal, "failed: recv");

        // If the socket reported disconnection, close it
        if ((recvResult == SOCKET_ERROR) || !recvResult) {
            Disconnect(true);
            OnClose();
            return;
        }

        // Update the read buffer state
        m_readBytes += recvResult;

        // Dispatch a read notification
        AsyncNotifySocketRead notify;
        notify.param          = nil;
        notify.asyncId        = 0;
        notify.buffer         = m_readBuffer;
        notify.bytes          = m_readBytes;
        notify.bytesProcessed = 0;
        bool notifyResult = m_notifyProc(
            (AsyncSocket)this,
            kNotifySocketRead,
            &notify,
            &m_userState
        );

        // If the application processed data, remove it from the read buffer
        if (notify.bytesProcessed >= m_readBytes)
            m_readBytes = 0;
        else if (notify.bytesProcessed) {
            memmove(
                &m_readBuffer[0],
                &m_readBuffer[notify.bytesProcessed],
                m_readBytes - notify.bytesProcessed
            );
            m_readBytes -= notify.bytesProcessed;
        }

        // If the application returned false from its notification procedure,
        // disconnect
        if (!notifyResult) {
            Disconnect(false);
            return;
        }

    }
}

//===========================================================================
void CSocket::OnWriteReady () {

    // Verify that the socket is ready to send data
    if (IsDisconnected() || !IsConnected())
        return;

    // Enter the critical section and verify that data is queued
    m_critSect.Enter();
    if (!m_sendQueue.Bytes()) {
        m_critSect.Leave();
        return;
    }

    // Attempt to send queued data
    int result = send(
        m_sock,
        (const char *)m_sendQueue.Ptr(),
        m_sendQueue.Bytes(),
        0
    );
    if (result == SOCKET_ERROR)
        if (GetLastError() == WSAEWOULDBLOCK)
            result = 0;
        else
            LogMsg(kLogFatal, "failed: send");

    // Dequeue sent bytes
    if (result != SOCKET_ERROR)
        if ((unsigned)result == m_sendQueue.Bytes())
            m_sendQueue.Clear();
        else if (result) {
            memmove(
                &m_sendQueue[0],
                &m_sendQueue[result],
                m_sendQueue.Bytes() - result
            );
            COMPILER_ASSERT(sizeof(m_sendQueue[0]) == sizeof(uint8_t));
            m_sendQueue.SetCount(m_sendQueue.Count() - result);
        }
    
    // Leave the critical section
    m_critSect.Leave();

    // If the send failed, close the socket
    if (result == SOCKET_ERROR) {
        Disconnect(true);
        OnClose();
    }


}

//===========================================================================
void CSocket::ProcessQueue () {

    // Verify that the socket is connected
    if (IsDisconnected() || !IsConnected())
        return;

    // Dispatch each command in the queue
    for (;;) {

        // Remove the next command from the queue
        m_critSect.Enter();
        Command * command = m_commandList.Head();
        if (command)
            m_commandList.Unlink(command);
        m_critSect.Leave();
        if (!command)
            break;

        // Dispatch it
        switch (command->code) {

            case Command::WRITE: {
                AsyncNotifySocketWrite notify;
                notify.param          = command->param;
                notify.asyncId        = 0;
                notify.buffer         = (uint8_t *)command->write.data;
                notify.bytes          = command->write.bytes;
                notify.bytesProcessed = 0;
                bool notifyResult = m_notifyProc(
                    (AsyncSocket)this,
                    kNotifySocketWrite,
                    &notify,
                    &m_userState
                );
                if (!notifyResult)
                    Disconnect(false);
            }
            break;

            DEFAULT_FATAL(command->code);
        }

        // Delete the command
        delete command;

    }
    
}

//===========================================================================
void CSocket::QueueConnect (
    void * param,
    uint8_t   connType
) {
    ASSERT(!IsConnected() && !IsDisconnected());

    Command * command = new Command;
    command->code             = command->CONNECT;
    command->param            = param;
    command->connect.connType = connType;
    m_commandList.Link(command);
}

//===========================================================================
void CSocket::QueueWrite (
    void *       param,
    const void * data, 
    unsigned     bytes
) {
    ASSERT(!IsDisconnected());

    Command * command = new Command;
    command->code        = command->CONNECT;
    command->param       = param;
    command->write.data  = data;
    command->write.bytes = bytes;
    m_commandList.Link(command);
}

//===========================================================================
bool CSocket::Send (  // returns false if disconnected
    const void * data,
    unsigned     bytes
) {

    // Verify that the socket has not been disconnected
    if (IsDisconnected())
        return false;

    // Enter the critical section
    m_critSect.Enter();

    // Attempt to send the data immediately
    int result;
    if (IsConnected() && !m_sendQueue.Bytes()) {
        result = send(
            m_sock,
            (const char *)data,
            bytes,
            0
        );
        if ((result == SOCKET_ERROR) && (GetLastError() != WSAEWOULDBLOCK))
            LogMsg(kLogFatal, "failed: send");
    }
    else
        result = 0;

    // If we were unable to send the entire message, queue the unsent portion
    if ((unsigned)result < bytes) {
        m_sendQueue.Add(
            (const uint8_t *)data + result,
            bytes - result
        );
    }
    
    // Leave the critical section
    m_critSect.Leave();

    return true;
}


/****************************************************************************
*
*   Window procedure
*
***/

#define  CLASS_NAME         "AsyncSelectWindow"

#define  WM_CANCEL_CONNECT  (WM_USER + 0)
#define  WM_PROCESS_QUEUE   (WM_USER + 1)

static CCritSect s_critSect;
static HANDLE    s_readyEvent;
static HANDLE    s_thread;

static unsigned THREADCALL W9xSocketThreadProc (AsyncThread *);

static LRESULT CALLBACK WndProc (
    HWND   window,
    UINT   message,
    WPARAM wParam,
    LPARAM lParam
);

//===========================================================================
static void OnCancelConnect (
    AsyncCancelId sequence
) {

    // Find the associated object
    s_critSect.Enter();
    CSocket * object = s_mapper.Find(sequence);
    s_critSect.Leave();

    // Force the object to dispatch connect success or failure. We must not
    // reference this object after the call to OnConnect() returns, because
    // it may have been deleted.
    if (object)
        object->OnConnect();

}

//===========================================================================
static void OnProcessQueue (
    AsyncCancelId sequence
) {

    // Find the associated object
    s_critSect.Enter();
    CSocket * object = s_mapper.Find(sequence);
    s_critSect.Leave();

    // Process the object's dispatch queue
    if (object)
        object->ProcessQueue();

}

//===========================================================================
static void OnSocketEvent (
    UINT   message,
    SOCKET sock,
    long   event
) {

    // Enter the critical section
    s_critSect.Enter();

    // Find the associated object
    CSocket * object = s_mapper.Find(message, sock);

    // Verify that the object has not been disconnected. An object that
    // has been disconnected can be deleted at any time, and therefore
    // can't be referenced outside the critical section. However, the
    // object cannot possibly have been disconnected here because objects
    // once connected are only disconnected during a call to OnClose(), 
    // which is the last event we process for an object.
    ASSERT(!object || !object->IsDisconnected());

    // Leave the critical section
    s_critSect.Leave();

    // Dispatch the event
    if (object)
        switch (event) {
            case FD_CLOSE:   object->OnClose();       break;
            case FD_CONNECT: object->OnConnect();     break;
            case FD_READ:    object->OnReadReady();   break;
            case FD_WRITE:   object->OnWriteReady();  break;
        }

    // We must not reference the object after dispatching the event,
    // because the event processor may have deleted it

}

//===========================================================================
static void PostCancelConnect (AsyncCancelId sequence) {
    s_critSect.Enter();
    if (s_window)
        PostMessage(s_window, WM_CANCEL_CONNECT, 0, (LPARAM)sequence);
    s_critSect.Leave();
}

//===========================================================================
static void PostProcessQueue (AsyncCancelId sequence) {
    s_critSect.Enter();
    if (s_window)
        PostMessage(s_window, WM_PROCESS_QUEUE, 0, (LPARAM)sequence);
    s_critSect.Leave();
}

//===========================================================================
static void ShutdownWindow () {
    s_critSect.Enter();

    // Close the window
    if (s_window) {
        PostMessage(s_window, WM_CLOSE, 0, 0);
        s_window = 0;
    }

    // Close the thread
    if (s_thread) {
        HANDLE thread = s_thread;
        s_thread = 0;
        s_critSect.Leave();
        WaitForSingleObject(thread, INFINITE);
        s_critSect.Enter();
    }

    // Close the ready event
    if (s_readyEvent) {
        CloseHandle(s_readyEvent);
        s_readyEvent = 0;
    }

    s_critSect.Leave();
}

//===========================================================================
static HWND StartupWindow () {

    s_critSect.Enter();

    // Check whether we need to create the window
    if (!s_window) {

        // Check whether we need to create the thread
        if (!s_thread) {

            // Create an object which the thread can use to signal creation
            // of the window
            ASSERT(!s_readyEvent);
            s_readyEvent = CreateEvent(nil, TRUE, FALSE, nil);

            // Create a thread which will create the window and run its
            // message loop
            s_thread = (HANDLE) AsyncThreadCreate(
                W9xSocketThreadProc,
                nil,
                L"W9xSockThread"
            );

        }

        // Wait for the thread to create the window
        HANDLE event = s_readyEvent;
        s_critSect.Leave();
        WaitForSingleObject(event, INFINITE);
        s_critSect.Enter();

    }

    // Return the window handle
    HWND window = s_window;
    s_critSect.Leave();
    return window;

}

//===========================================================================
static unsigned THREADCALL W9xSocketThreadProc (AsyncThread *) {
    ASSERT(!s_window);
    
    // Enter the critical section
    s_critSect.Enter();

    // Register the window class
    HINSTANCE instance = (HINSTANCE)GetModuleHandle(nil);
    WNDCLASS  wndClass;
    memset(&wndClass, 0, sizeof(wndClass));
    wndClass.lpfnWndProc   = WndProc;
    wndClass.hInstance     = instance;
    wndClass.lpszClassName = CLASS_NAME;
    RegisterClass(&wndClass);

    // Create the window
    s_window = CreateWindow(
        CLASS_NAME,
        CLASS_NAME,
        WS_OVERLAPPED,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        0,    // parent window
        0,    // menu
        instance,
        nil  // param
    );

    // Leave the critical section
    s_critSect.Leave();

    // Trigger an event to allow anyone blocking on window creation to proceed
    SetEvent(s_readyEvent);

    // Dispatch messages
    MSG msg;
    while (GetMessage(&msg, nil, 0, 0))
        if (msg.message == WM_CLOSE)
            break;
        else {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

    // Destroy the window
    s_critSect.Enter();
    if (s_window) {
        DestroyWindow(s_window);
        s_window = 0;
    }
    s_critSect.Leave();

    return 0;
}

//===========================================================================
static LRESULT CALLBACK WndProc (
    HWND   window,
    UINT   message,
    WPARAM wParam,
    LPARAM lParam
) {
    switch (message) {

        case WM_CANCEL_CONNECT:
            OnCancelConnect((AsyncCancelId)lParam);
        return 0;

        case WM_PROCESS_QUEUE:
            OnProcessQueue((AsyncCancelId)lParam);
        return 0;

        default:
            if ((message >= WM_APP) && (message < 0xc000))
                OnSocketEvent(message, (SOCKET)wParam, WSAGETSELECTEVENT(lParam));
            else
                return DefWindowProc(window, message, wParam, lParam);
        break;

    }
    return 0;
}


/****************************************************************************
*
*   Exported functions
*
***/

//===========================================================================
void W9xSocketConnect (
    AsyncCancelId *         cancelId,
    const NetAddress &      netAddr,
    FAsyncNotifySocketProc  notifyProc,
    void *                  param,
    const void *            sendData,
    unsigned                sendBytes,
    unsigned                connectMs,
    unsigned                localPort
) {
    // If necessary, startup the window and message queue
    HWND window = StartupWindow();

    // Create a socket
    SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (!sock)
        LogMsg(kLogFatal, "failed: socket");

    // Assign a sequence number
    static unsigned s_sequence;
    AsyncCancelId sequence = (AsyncCancelId)++s_sequence;
    if (!sequence)
        sequence = (AsyncCancelId)++s_sequence;
    *cancelId = sequence;

    // Assign a message id
    static UINT s_message;
    UINT message = (s_message++ & 0x3fff) | WM_APP;  // range 0x8000 - 0xbfff

    // Create a socket object
    CSocket * object = new CSocket(
        sequence,
        message,
        sock,
        notifyProc
    );

    // Queue a connect notification for the socket
    object->QueueConnect(
        param,
        sendBytes ? ((const uint8_t *)sendData)[0] : (uint8_t)0
    );

    // Queue sending data
    if (sendBytes)
        object->Send(sendData, sendBytes);

    // Setup notifications for the socket
    int result = WSAAsyncSelect(
        sock, 
        window, 
        message,
        FD_CLOSE | FD_CONNECT | FD_READ | FD_WRITE
    );
    if (result)
        LogMsg(kLogFatal, "failed: WSAAsyncSelect");

    // Start the connection
    if ( connect(sock, (const sockaddr *)&netAddr, sizeof(sockaddr)) &&
         (GetLastError() != WSAEWOULDBLOCK) )
        LogMsg(kLogFatal, "failed: connect. err=%u", GetLastError());

}

//===========================================================================
void W9xSocketConnectCancel (
    FAsyncNotifySocketProc  notifyProc,
    AsyncCancelId           cancelId
) {

    // Enter the critical section
    s_critSect.Enter();

    // If a cancel id was given, cancel that one connection
    if (cancelId)
        PostCancelConnect(cancelId);

    // Otherwise, cancel all connections using the specified notification
    // procedure
    else
        for (unsigned index = 0; ; ++index) {

            // Find the next socket object
            CSocket * object = s_mapper.Find(notifyProc, index);
            if (!object)
                break;

            // If the object exists and is not yet connected, cancel it
            if (object)
                PostCancelConnect(object->GetSequence());

        }

    // Leave the critical section
    s_critSect.Leave();

}

//===========================================================================
void W9xSocketDelete (
    AsyncSocket sock
) {

    // Dereference the object
    CSocket * object = (CSocket *)sock;

    // Delete the object
    s_critSect.Enter();
    delete object;
    s_critSect.Leave();

}

//===========================================================================
void W9xSocketDestroy () {

    // Shutdown the window and message queue
    ShutdownWindow();

}

//===========================================================================
void W9xSocketEnableNagling (
    AsyncSocket sock,
    bool        enable
) {

    // Dereference the object
    CSocket * object = (CSocket *)sock;

    // set nagling state
    object->EnableNagling(enable);

}

//===========================================================================
void W9xSocketDisconnect (
    AsyncSocket sock, 
    bool        hardClose
) {

    // Dereference the object
    CSocket * object = (CSocket *)sock;

    // Disconnect
    object->Disconnect(hardClose);

}

//===========================================================================
unsigned W9xSocketStartListening (
    const NetAddress &      listenAddr,
    FAsyncNotifySocketProc  notifyProc
) {
    return 0;

}

//===========================================================================
void W9xSocketStopListening (
    const NetAddress &      listenAddr,
    FAsyncNotifySocketProc  notifyProc
) {
}

//===========================================================================
bool W9xSocketSend (
    AsyncSocket  sock, 
    const void * data, 
    unsigned     bytes
) {

    // Dereference the object
    CSocket * object = (CSocket *)sock;

    // Send the data
    return object->Send(data, bytes);

}

//===========================================================================
void W9xSocketSetNotifyProc (
    AsyncSocket             sock, 
    FAsyncNotifySocketProc  notifyProc
) {
    // This provider does not allow changing the notification procedure
    FATAL("SocketSetNotifyProc");
}

//===========================================================================
void W9xSocketSetBacklogAlloc (
    AsyncSocket sock, 
    unsigned    bufferSize
) {

    // This provider does not limit the maximum backlog allocation

}

//===========================================================================
bool W9xSocketWrite (
    AsyncSocket  sock, 
    const void * data, 
    unsigned     bytes, 
    void *       param
) {

    // Dereference the object
    CSocket * object = (CSocket *)sock;

    // Send the data
    if (!object->Send(data, bytes))
        return false;

    // Queue a completion notification
    object->QueueWrite(param, data, bytes);

    // Trigger processing the queue
    PostProcessQueue(object->GetSequence());

    return true;
}


}  // namespace W9x
