/******************************************************************************
    Project:    MicroMacro
    Author:     SolarStrike Software
    URL:        www.solarstrike.net
    License:    Modified BSD (see license.txt)
******************************************************************************/

#ifdef NETWORKING_ENABLED

#include "socket_lua.h"
#include "error.h"
#include "macro.h"
#include "types.h"
#include "strl.h"
#include "logger.h"
#include "debugmessages.h"

extern "C"
{
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}

#include <ws2tcpip.h>
#include "macro.h"
#include "settings.h"

#define DEFAULT_LOCK_TIMEOUT        1000

using MicroMacro::Socket;
using MicroMacro::Event;
using MicroMacro::Mutex;
using MicroMacro::EventData;

const char *LuaType::metatable_socket = "socket";

Mutex Socket_lua::socketListLock;
std::vector<Socket *> Socket_lua::socketList;

DWORD WINAPI Socket_lua::tcpThread(Socket *pSocket)
{
    size_t buffSize = Macro::instance()->getSettings()->getInt(CONFVAR_NETWORK_BUFFER_SIZE);
    char *readBuff = new char[buffSize + 1];

    size_t maxRecvQueueSize = Macro::instance()->getSettings()->getInt(CONFVAR_RECV_QUEUE_SIZE);
    while(true)
    {
        int result = 0;
        result = ::recv(pSocket->socket, readBuff, buffSize, 0);

        if( result > 0 )
        {   // Data received
            // Copy the received data into an event; push it.
            readBuff[result] = 0; // Enforce NULL-terminator
            Event e;
            e.type = MicroMacro::EVENT_SOCKETRECEIVED;

            // Push the socket ID
            EventData ced;
            ced.setValue((int)pSocket->socket);
            e.data.push_back(ced);

            // Push the received data
            ced.setValue(readBuff, result);
            e.data.push_back(ced);

            if( pSocket->mutex.lock(DEFAULT_LOCK_TIMEOUT, __FUNCTION__) )
            {
                while( pSocket->recvQueue.size() > (maxRecvQueueSize + 1) )
                    pSocket->recvQueue.pop();

                pSocket->eventQueue.push(e);
                pSocket->recvQueue.push(std::string(readBuff, result));
                pSocket->mutex.unlock(__FUNCTION__);
            }
        }
        else if( result == 0 )
        {   // Connection closed (probably by remote)
            Event e;
            e.type = MicroMacro::EVENT_SOCKETDISCONNECTED;
            EventData ced;
            ced.setValue((int)pSocket->socket);
            e.data.push_back(ced);

            if( pSocket->mutex.lock(DEFAULT_LOCK_TIMEOUT, __FUNCTION__) )
            {
                pSocket->eventQueue.push(e);
                pSocket->mutex.unlock(__FUNCTION__);
            }
            break;
        }
        else
        {   // Error occurred
            if( pSocket->mutex.lock(DEFAULT_LOCK_TIMEOUT, __FUNCTION__) )
            {
                int errCode = WSAGetLastError();

                switch(errCode)
                {
                    case WSAENOTSOCK: // "Not a socket"; we closed the socket in the main thread, so this signals we should shut down this thread
                    case WSAECONNRESET:
                        {
                            Event e;
                            e.type = MicroMacro::EVENT_SOCKETDISCONNECTED;
                            EventData ced;
                            ced.setValue((int)pSocket->socket);
                            e.data.push_back(ced);

                            if( pSocket->open )
                            {
                                pSocket->eventQueue.push(e);

                            }
                        }
                        break;

                    case WSAECONNABORTED:   // Software caused connection abort. (your machine); we should close the socket
                        {
                            Event e;
                            e.type = MicroMacro::EVENT_SOCKETERROR;
                            EventData ced;
                            ced.setValue((int)pSocket->socket);
                            e.data.push_back(ced);

                            ced.setValue((int)errCode);
                            e.data.push_back(ced);

                            if( pSocket->open )
                            {
                                pSocket->eventQueue.push(e);
                            }
                        }
                        break;

                    default:
                        #ifdef DISPLAY_DEBUG_MESSAGES
                        fprintf(stderr, "Socket error occurred. Code: %d, socket: 0x%p\n", errCode, pSocket->socket);
                        #endif
                        {
                            Event e;
                            e.type = MicroMacro::EVENT_SOCKETERROR;

                            EventData ced;
                            ced.setValue((int)pSocket->socket);
                            e.data.push_back(ced);

                            ced.setValue((int)errCode);
                            e.data.push_back(ced);

                            if( pSocket->open )
                            {
                                pSocket->eventQueue.push(e);
                            }
                        }
                        break;
                }

                //printf("Set delete flag for 0x%x (%d)\n", pSocket, pSocket->socket);
                closesocket(pSocket->socket);
                pSocket->open       =   false;
                //pSocket->hThread  =   NULL;
                pSocket->connected  =   false;
                pSocket->socket     =   INVALID_SOCKET;
                pSocket->mutex.unlock(__FUNCTION__);
                break; // Break from while
            } // End of: ( pSocket->mutex.lock() )
        } // End of: else
    } // End of while(true)

    delete []readBuff;

    if( pSocket->mutex.lock(INFINITE, __FUNCTION__) )
    {
        if( pSocket->socket != INVALID_SOCKET )
            closesocket(pSocket->socket);
        pSocket->socket     =   INVALID_SOCKET;
        pSocket->hThread    =   NULL;
        //pSocket->deleteMe =   true;
        pSocket->mutex.unlock(__FUNCTION__);
    }

    return 0;
}

DWORD WINAPI Socket_lua::listenThread(Socket *pSocket)
{
    // Set the socket to listen mode
    ::listen(pSocket->socket, LISTEN_BUFFER);

    SOCKET new_socket;
    struct sockaddr_in client;

    while(true)
    {
        int addrlen = sizeof(struct sockaddr_in);
        new_socket = accept(pSocket->socket, (struct sockaddr *)&client, &addrlen);

        if( new_socket == INVALID_SOCKET )
        {
            int errCode = WSAGetLastError();
            pSocket->connected = false;
            switch(errCode)
            {
                case WSAENOTSOCK: // "Not a socket"; we closed the socket in the main thread, so this signals we should shut down this thread
                case WSAECONNRESET: // CONNRESET; remote side closed the connection forcibly
                    {
                        Event e;
                        e.type = MicroMacro::EVENT_SOCKETDISCONNECTED;

                        EventData ced;
                        ced.setValue((int)pSocket->socket);
                        e.data.push_back(ced);

                        if( pSocket->mutex.lock(DEFAULT_LOCK_TIMEOUT, __FUNCTION__) )
                        {
                            pSocket->eventQueue.push(e);
                            pSocket->mutex.unlock(__FUNCTION__);
                        }
                    }
                    break;

                case WSAECONNABORTED:   // Software caused connection abort. (your machine); we should close the socket
                    {
                        Event e;
                        e.type = MicroMacro::EVENT_SOCKETERROR;

                        EventData ced;
                        ced.setValue((int)pSocket->socket);
                        e.data.push_back(ced);

                        ced.setValue((int)errCode);
                        e.data.push_back(ced);

                        if( pSocket->mutex.lock(DEFAULT_LOCK_TIMEOUT, __FUNCTION__) )
                        {
                            pSocket->eventQueue.push(e);
                            closesocket(pSocket->socket);
                            pSocket->socket     =   INVALID_SOCKET;
                            pSocket->connected  =   true;
                            pSocket->mutex.unlock(__FUNCTION__);
                        }
                    }
                    break;

                case WSAEINTR: // Interrupted blocking function call (probably terminated a listen thread on script end)
                    break;

                default:
                    #ifdef DISPLAY_DEBUG_MESSAGES
                    fprintf(stderr, "Socket error occurred. Code: %d, listen socket: 0x%p\n", errCode, pSocket->socket);
                    #endif
                    {
                        Event e;
                        e.type = MicroMacro::EVENT_SOCKETERROR;

                        EventData ced;
                        ced.setValue((int)pSocket->socket);
                        e.data.push_back(ced);

                        ced.setValue((int)errCode);
                        e.data.push_back(ced);

                        if( pSocket->mutex.lock(DEFAULT_LOCK_TIMEOUT, __FUNCTION__) )
                        {
                            pSocket->eventQueue.push(e);
                            pSocket->mutex.unlock(__FUNCTION__);
                        }
                    }
                    break;
            }

            pSocket->socket     =   INVALID_SOCKET;
            pSocket->open       =   false;
            pSocket->hThread    =   NULL;
            pSocket->connected  =   false;

            break; // Break while(true)
        }
        else
        {   // Successfully accepted new client
            Socket *npSocket = new Socket;

            // Push the event
            Event e;
            e.type      =   MicroMacro::EVENT_SOCKETCONNECTED;

            // Push the new socket
            EventData ced;
            ced.setValue(npSocket);
            e.data.push_back(ced);

            // And the listen socket ID
            ced.setValue((int)pSocket->socket);
            e.data.push_back(ced);

            npSocket->eventQueue.push(e);

            /* Now we can record some more info and start the new thread
                Note: We don't need to mutex this because it cannot be accessed
                until after we create the thread.
            */
            npSocket->socket    =   new_socket;
            npSocket->protocol  =   AF_INET;
            npSocket->connected =   true;
            npSocket->open      =   true;
            npSocket->deleteMe  =   false;

            npSocket->hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)tcpThread, (PVOID)npSocket, 0, NULL);

            if( socketListLock.lock(INFINITE, __FUNCTION__) )
            {
                socketList.push_back(npSocket);
                socketListLock.unlock(__FUNCTION__);
            }
        }
    } // End of: while(true)

    return 0;
}

DWORD WINAPI Socket_lua::udpThread(Socket *pSocket)
{
    size_t buffSize = Macro::instance()->getSettings()->getInt(CONFVAR_NETWORK_BUFFER_SIZE);
    char *readBuff = new char[buffSize + 1];
    size_t maxRecvQueueSize = Macro::instance()->getSettings()->getInt(CONFVAR_RECV_QUEUE_SIZE);
    struct sockaddr_in remoteAddr;

    while(true)
    {
        int addrlen = sizeof(struct sockaddr_in);
        int result = recvfrom(pSocket->socket, readBuff, buffSize, 0, (struct sockaddr *)&remoteAddr, &addrlen);

        if( result == SOCKET_ERROR )
        {
            int errCode = WSAGetLastError();
            //pSocket->connected = false;
            switch(errCode)
            {
                case WSAENOTSOCK: // "Not a socket"; we closed the socket in the main thread, so this signals we should shut down this thread
                    {
                        pSocket->socket     =   INVALID_SOCKET;
                    }
                    break;

                /*
                                case WSAECONNRESET: // CONNRESET; remote side closed the connection forcibly
                                {
                                    Event e;
                                    e.type = MicroMacro::EVENT_SOCKETDISCONNECTED;

                                    EventData ced;
                                    ced.setValue((int)pSocket->socket);
                                    e.data.push_back(ced);

                                    if( pSocket->mutex.lock(DEFAULT_LOCK_TIMEOUT, __FUNCTION__) )
                                    {
                                        pSocket->eventQueue.push(e);
                                        pSocket->mutex.unlock(__FUNCTION__);
                                    }
                                }
                                break;
                */

                case WSAECONNABORTED:   // Software caused connection abort. (your machine); we should close the socket
                    {
                        Event e;
                        e.type = MicroMacro::EVENT_SOCKETERROR;

                        EventData ced;
                        ced.setValue((int)pSocket->socket);
                        e.data.push_back(ced);

                        ced.setValue((int)errCode);
                        e.data.push_back(ced);

                        if( pSocket->mutex.lock(DEFAULT_LOCK_TIMEOUT, __FUNCTION__) )
                        {
                            pSocket->eventQueue.push(e);
                            closesocket(pSocket->socket);
                            pSocket->socket     =   INVALID_SOCKET;
                            pSocket->connected  =   true;
                            pSocket->mutex.unlock(__FUNCTION__);
                        }
                    }
                    break;

                case WSAEINTR: // Interrupted blocking function call (probably terminated a listen thread on script end)
                    pSocket->socket     =   INVALID_SOCKET;
                    break;

                default:
                    #ifdef DISPLAY_DEBUG_MESSAGES
                    fprintf(stderr, "Socket error occurred. Code: %d, listen socket: 0x%p\n", errCode, pSocket->socket);
                    #endif
                    {
                        Event e;
                        e.type = MicroMacro::EVENT_SOCKETERROR;

                        EventData ced;
                        ced.setValue((int)pSocket->socket);
                        e.data.push_back(ced);

                        ced.setValue((int)errCode);
                        e.data.push_back(ced);

                        if( pSocket->mutex.lock(DEFAULT_LOCK_TIMEOUT, __FUNCTION__) )
                        {
                            pSocket->eventQueue.push(e);
                            pSocket->mutex.unlock(__FUNCTION__);
                        }
                    }
                    break;
            }


            if( pSocket->socket == INVALID_SOCKET )
            {
                pSocket->open       =   false;
                pSocket->hThread    =   NULL;
                pSocket->connected  =   false;
                pSocket->deleteMe   =   true;

                break; // Break while(true)
            }
        }
        else if( result == 0 )
        {
            // Closed connection
        }
        else
        {   // Received data
            readBuff[result] = 0; // Enforce NULL-terminator
            Event e;
            e.type = MicroMacro::EVENT_SOCKETRECEIVED;

            // Push the socket address (sockaddr)
            EventData ced;
            ced.setValue(&remoteAddr);
            e.data.push_back(ced);

            // Push the received data
            ced.setValue(readBuff, result);
            e.data.push_back(ced);

            if( pSocket->mutex.lock(DEFAULT_LOCK_TIMEOUT, __FUNCTION__) )
            {
                while( pSocket->recvQueue.size() > (maxRecvQueueSize + 1) )
                    pSocket->recvQueue.pop();

                pSocket->eventQueue.push(e);
                pSocket->recvQueue.push(std::string(readBuff, result));
                pSocket->mutex.unlock(__FUNCTION__);
            }
        }
    } // End of: while(true)

    return 0;
}

int Socket_lua::regmod(lua_State *L)
{
    const luaL_Reg meta[] = {
        {"__gc", gc},
        {"__tostring", tostring},
        {NULL, NULL}
    };

    const luaL_Reg methods[] = {
        {"connect", connect},
        {"listen", listen},
        {"send", send},
        {"sendto", sendto},
        {"recv", recv},
        {"flushRecvQueue", flushRecvQueue},
        {"getRecvQueueSize", getRecvQueueSize},
        {"close", close},
        {"id", id},
        {"ip", ip},
        {"port", port},
        {"remoteIp", remoteIp},
        {NULL, NULL}
    };

    if( Macro::instance()->getSettings()->getInt(CONFVAR_NETWORK_ENABLED, CONFDEFAULT_NETWORK_ENABLED) )
    {
        luaL_newmetatable(L, LuaType::metatable_socket);
        luaL_setfuncs(L, meta, 0);
        luaL_newlib(L, methods);
        lua_setfield(L, -2, "__index");

        lua_pop(L, 1); // Pop table
    }

    return MicroMacro::ERR_OK;
}

int Socket_lua::cleanup()
{
    if( socketListLock.lock(INFINITE, __FUNCTION__) )
    {
        // Step 1: Close the sockets; give the threads time to terminate; Do *not* delete yet!
        for(SocketListIterator i = socketList.begin(); i != socketList.end(); ++i)
        {
            Socket *pSocket = *i;
            if( pSocket->mutex.lock(INFINITE, __FUNCTION__) )
            {
                // Close the socket if needed
                if( pSocket->open )
                    closesocket(pSocket->socket);

                // Just in case anything still tries to access it (waiting for Lua GC to kick in?)
                pSocket->open = false;
                pSocket->connected = false;

                pSocket->mutex.unlock(__FUNCTION__); // Let the thread do its part
            }
        }

        // Step 2: Now we can actually delete the data.
        while( !socketList.empty() )
        {
            Socket *pSocket = socketList.front();

            if( (pSocket->inLua && !pSocket->deleteMe) || pSocket->hThread )
            {   // Make sure we aren't trying to erase it before Lua has shut down
                Sleep(1);
                continue;
            }
            socketList.erase(socketList.begin());   // Erase from the list
            delete pSocket;                         // Free memory
            pSocket = NULL;
        }
        socketListLock.unlock(__FUNCTION__);
    }

    return MicroMacro::ERR_OK;
}

// TODO: This is completely broken...
bool Socket_lua::isIP(const char *ip)
{
    struct addrinfo *result;
    int success = getaddrinfo(ip, NULL, NULL, &result);

    return (success != 0);
}

int Socket_lua::connect(lua_State *L)
{
    int top = lua_gettop(L);
    if( top != 3 )
        wrongArgs(L);
    checkType(L, LT_STRING, 2);
    checkType(L, LT_NUMBER, 3);

    Socket *pSocket = *static_cast<Socket **>(lua_touserdata(L, 1));
    const char *host = lua_tostring(L, 2);
    int port = lua_tointeger(L, 3);

    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(port);

    /*  if( isIP(host) )
        {
            printf("Use raw IP\n");
            server.sin_addr.s_addr = inet_addr(host);
        }
        else*/
    {
        HOSTENT *pHostent;
        pHostent = gethostbyname(host);
        if( pHostent == NULL )
        {   // Invalid host
            char errbuff[2048];
            slprintf(errbuff, sizeof(errbuff), "Could not retrieve host information for %s\n", host);

            lua_pushboolean(L, false);
            lua_pushstring(L, errbuff);
            return 2;
        }
        server.sin_addr = *((LPIN_ADDR) * pHostent->h_addr_list);
    }

    if( !pSocket->mutex.lock(INFINITE, __FUNCTION__) )
    {
        lua_pushboolean(L, false);
        lua_pushstring(L, "Could not lock socket mutex.");
        return 2;
    }

    if( pSocket->connected || pSocket->open )
    {   // Socket already in use; cannot do this.
        pSocket->mutex.unlock(__FUNCTION__);
        lua_pushboolean(L, false);
        lua_pushstring(L, "Socket already connected; cannot open a new connection. Use a new socket.\n");
        return 2;
    }


    int success = (::connect(pSocket->socket, (struct sockaddr *)&server, sizeof(struct sockaddr)) >= 0);

    if( !success )
    {
        pSocket->mutex.unlock(__FUNCTION__);
        char errbuff[2048];
        slprintf(errbuff, sizeof(errbuff), "Connection failed. Err code %d\n", WSAGetLastError());
        lua_pushboolean(L, false);
        lua_pushstring(L, errbuff);
        return 2;
    }

    // Start a thread for this socket
    pSocket->hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)tcpThread, (PVOID)pSocket, 0, NULL);
    pSocket->connected = true;
    pSocket->open = true;

    pSocket->mutex.unlock(__FUNCTION__);

    // Lets make a record of it in our list
    if( socketListLock.lock(DEFAULT_LOCK_TIMEOUT, __FUNCTION__) )
    {
        socketList.push_back(pSocket);
        socketListLock.unlock(__FUNCTION__);
    }

    lua_pushboolean(L, true);
    return 1;
}

int Socket_lua::listen(lua_State *L)
{
    int top = lua_gettop(L);
    if( top != 3 )
        wrongArgs(L);
    checkType(L, LT_STRING, 2);
    checkType(L, LT_NUMBER, 3);

    Socket *pSocket = *static_cast<Socket **>(lua_touserdata(L, 1));
    const char *host = lua_tostring(L, 2);
    int port = lua_tointeger(L, 3);

    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(port);

    /*if( isIP(host) )
    {
        printf("Use raw IP\n");
        server.sin_addr.s_addr = inet_addr(host);
    }*/
    if( strlen(host) == 0 )
    {
        server.sin_addr.s_addr = INADDR_ANY;
    }
    else
    {
        HOSTENT *pHostent;
        pHostent = gethostbyname(host);
        server.sin_addr = *((LPIN_ADDR) * pHostent->h_addr_list);
    }

    if( !pSocket->mutex.lock(INFINITE, __FUNCTION__) )
    {
        lua_pushboolean(L, false);
        lua_pushstring(L, "Could not lock socket mutex.");
        return 2;
    }

    if( pSocket->connected || pSocket->open )
    {   // Socket already in use; cannot do this.
        pSocket->mutex.unlock(__FUNCTION__);
        lua_pushboolean(L, false);
        lua_pushstring(L, "Socket already connected; cannot set to listen. Use a new socket.\n");
        return 2;
    }

    int success = (::bind(pSocket->socket, (struct sockaddr *)&server, sizeof(struct sockaddr)) >= 0);

    if( !success )
    {
        pSocket->mutex.unlock(__FUNCTION__);
        char errbuff[2048];
        slprintf(errbuff, sizeof(errbuff), "Bind failed. Err code %d\n", WSAGetLastError());
        lua_pushboolean(L, false);
        lua_pushstring(L, errbuff);
        return 2;
    }

    // Start a thread for this socket
    pSocket->connected = true;
    pSocket->open = true;

    if( pSocket->protocol == IPPROTO_TCP )
        pSocket->hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)listenThread, (PVOID)pSocket, 0, NULL);
    else
        pSocket->hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)udpThread, (PVOID)pSocket, 0, NULL);

    pSocket->mutex.unlock(__FUNCTION__);

    // Lets make a record of it in our list
    if( socketListLock.lock(DEFAULT_LOCK_TIMEOUT, __FUNCTION__) )
    {
        socketList.push_back(pSocket);
        socketListLock.unlock(__FUNCTION__);
    }

    lua_pushboolean(L, true);
    return 1;
}

int Socket_lua::send(lua_State *L)
{
    int top = lua_gettop(L);
    if( top != 2 )
        wrongArgs(L);
    checkType(L, LT_NUMBER | LT_STRING, 2);

    Socket *pSocket = *static_cast<Socket **>(lua_touserdata(L, 1));
    size_t len;
    const char *msg = lua_tolstring(L, 2, &len);

    if( !pSocket->mutex.lock(INFINITE, __FUNCTION__) )
    {
        lua_pushboolean(L, false);
        return 1;
    }

    if( !pSocket->connected || !pSocket->open )
    {   // Cannot send on a closed socket.
        pSocket->mutex.unlock(__FUNCTION__);
        lua_pushboolean(L, false);
        return 1;
    }

    int success = ::send(pSocket->socket, msg, len, 0);
    pSocket->mutex.unlock(__FUNCTION__);
    if( success == SOCKET_ERROR )
    {
        int errCode = WSAGetLastError();
        lua_pushboolean(L, false);
        return 1;
    }

    lua_pushboolean(L, true);
    return 1;
}

int Socket_lua::sendto(lua_State *L)
{
    int top = lua_gettop(L);
    if( top != 3 && top != 4 )
        wrongArgs(L);

    struct sockaddr_in remoteAddr;
    memset((char*)&remoteAddr, 0, sizeof(remoteAddr));
    size_t len;
    int msgIndex    =   3;

    // If we've been given a sockaddr
    if( top == 3 )
    {
        checkType(L, LT_USERDATA, 2);
        checkType(L, LT_NUMBER | LT_STRING, 3);
        msgIndex    =   3; // 'msg' is at index 3 because no IP/port given

        struct sockaddr_in *inRemoteAddr = (sockaddr_in *)lua_touserdata(L, 2);
        memcpy(&remoteAddr, inRemoteAddr, sizeof(struct sockaddr_in));
    }
    else // If we've been given the IP & port
    {
        checkType(L, LT_NUMBER | LT_STRING, 2);
        checkType(L, LT_NUMBER, 3);
        checkType(L, LT_NUMBER | LT_STRING, 4);

        msgIndex                =   4; // 'msg' is at index 4 because IP/port given
        remoteAddr.sin_family   =   AF_INET;
        remoteAddr.sin_port     =   htons(lua_tointeger(L, 3));

        const char *host = lua_tostring(L, 2);
        {
            HOSTENT *pHostent;
            pHostent = gethostbyname(host);
            remoteAddr.sin_addr = *((LPIN_ADDR) * pHostent->h_addr_list);
        }
    }

    Socket *pSocket = *static_cast<Socket **>(lua_touserdata(L, 1));
    const char *msg = lua_tolstring(L, msgIndex, &len);

    if( !pSocket->mutex.lock(INFINITE, __FUNCTION__) )
    {
        lua_pushboolean(L, false);
        return 1;
    }

    int success = ::sendto(pSocket->socket, msg, len, 0, (struct sockaddr *)&remoteAddr, sizeof(remoteAddr));
    pSocket->mutex.unlock(__FUNCTION__);
    if( success == SOCKET_ERROR )
    {
        int errCode = WSAGetLastError();
        lua_pushboolean(L, false);
        return 1;
    }

    if( !pSocket->connected )
    {   // We called sendto before recvfrom, it has now become bound so we can launch our udpThread
        pSocket->connected  =   true;
        pSocket->open       =   true;
        pSocket->hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)udpThread, (PVOID)pSocket, 0, NULL);

        // Lets make a record of it in our list
        if( socketListLock.lock(DEFAULT_LOCK_TIMEOUT, __FUNCTION__) )
        {
            socketList.push_back(pSocket);
            socketListLock.unlock(__FUNCTION__);
        }
    }

    lua_pushboolean(L, true);
    return 1;
}

int Socket_lua::recv(lua_State *L)
{
    int top = lua_gettop(L);
    if( top != 1 )
        wrongArgs(L);
    checkType(L, LT_USERDATA, 1);

    Socket *pSocket = *static_cast<Socket **>(lua_touserdata(L, 1));
    int retVal = 0;
    if( pSocket->mutex.lock(DEFAULT_LOCK_TIMEOUT, __FUNCTION__) )
    {
        if( !pSocket->recvQueue.empty() )
        {
            lua_pushlstring(L, pSocket->recvQueue.front().c_str(), pSocket->recvQueue.front().size());
            pSocket->recvQueue.pop();
            retVal = 1;
        }

        pSocket->mutex.unlock(__FUNCTION__);
    }

    return retVal;
}

int Socket_lua::flushRecvQueue(lua_State *L)
{
    int top = lua_gettop(L);
    if( top != 1 )
        wrongArgs(L);
    checkType(L, LT_USERDATA, 1);

    Socket *pSocket = *static_cast<Socket **>(lua_touserdata(L, 1));
    if( pSocket->mutex.lock(DEFAULT_LOCK_TIMEOUT, __FUNCTION__) )
    {   // We use 'swap' instead of just popping elements for performance reasons
        std::queue<std::string> emptyQueue;
        swap(pSocket->recvQueue, emptyQueue);
        pSocket->mutex.unlock(__FUNCTION__);
    }
    return 0;
}

int Socket_lua::getRecvQueueSize(lua_State *L)
{
    int top = lua_gettop(L);
    if( top != 1 )
        wrongArgs(L);
    checkType(L, LT_USERDATA, 1);

    size_t size = 0;
    Socket *pSocket = *static_cast<Socket **>(lua_touserdata(L, 1));
    if( pSocket->mutex.lock(DEFAULT_LOCK_TIMEOUT, __FUNCTION__) )
    {
        size = pSocket->recvQueue.size();
        pSocket->mutex.unlock(__FUNCTION__);
    }

    lua_pushinteger(L, size);
    return 1;
}

int Socket_lua::id(lua_State *L)
{
    int top = lua_gettop(L);
    if( top != 1 )
        wrongArgs(L);
    checkType(L, LT_USERDATA, 1);

    Socket *pSocket = *static_cast<Socket **>(lua_touserdata(L, 1));
    lua_pushinteger(L, pSocket->socket);
    return 1;
}

int Socket_lua::ip(lua_State *L)
{
    int top = lua_gettop(L);
    if( top != 1 )
        wrongArgs(L);
    checkType(L, LT_USERDATA, 1);

    Socket *pSocket = *static_cast<Socket **>(lua_touserdata(L, 1));

    struct sockaddr_in name;
    int namelen =   sizeof(name);
    getsockname(pSocket->socket, (struct sockaddr *)&name, &namelen);
    char clienthost[/*128*/NI_MAXHOST];
    char clientservice[NI_MAXSERV];
    getnameinfo((sockaddr *)&name, sizeof(name), clienthost, sizeof(clienthost),
                clientservice, sizeof(clientservice), NI_NUMERICHOST | NI_NUMERICSERV);

    /*DWORD dwNamelen   =   sizeof(name);
    DWORD outputLen =   sizeof(buffer); // Initialize with our original buffer's size
    WSAAddressToString((sockaddr*)&name, dwNamelen, NULL, buffer, &outputLen);*/

    lua_pushstring(L, clienthost);
    return 1;
}

int Socket_lua::port(lua_State *L)
{
    int top = lua_gettop(L);
    if( top != 1 )
        wrongArgs(L);
    checkType(L, LT_USERDATA, 1);

    Socket *pSocket = *static_cast<Socket **>(lua_touserdata(L, 1));

    struct sockaddr_in name;
    int namelen =   sizeof(name);
    getsockname(pSocket->socket, (struct sockaddr *)&name, &namelen);

    /*char buffer[128];
    DWORD dwNamelen =   sizeof(name);
    DWORD outputLen =   sizeof(buffer); // Initialize with our original buffer's size
    WSAAddressToString((sockaddr*)&name, dwNamelen, NULL, buffer, &outputLen);
    */

    lua_pushinteger(L, ntohs(name.sin_port));

    //lua_pushstring(L, buffer);
    return 1;
}

int Socket_lua::remoteIp(lua_State *L)
{
    int top = lua_gettop(L);
    if( top != 1 )
        wrongArgs(L);
    checkType(L, LT_USERDATA, 1);

    Socket *pSocket = *static_cast<Socket **>(lua_touserdata(L, 1));

    struct sockaddr_in name;
    int namelen =   sizeof(name);
    getpeername(pSocket->socket, (struct sockaddr *)&name, &namelen);

    char buffer[128];
    DWORD dwNamelen =   sizeof(name);
    DWORD outputLen =   sizeof(buffer); // Initialize with our original buffer's size
    WSAAddressToString((sockaddr*)&name, dwNamelen, NULL, buffer, &outputLen);

    lua_pushstring(L, buffer);
    return 1;
}

int Socket_lua::close(lua_State *L)
{
    int top = lua_gettop(L);
    if( top < 1 )
        wrongArgs(L);

    Socket *pSocket = *static_cast<Socket **>(lua_touserdata(L, 1));
    if( pSocket->hThread && pSocket->open && pSocket->mutex.lock(INFINITE, __FUNCTION__) )
    {
        closesocket(pSocket->socket);
        pSocket->socket     =   INVALID_SOCKET;
        pSocket->connected  =   false;
        pSocket->open       =   false;
        pSocket->mutex.unlock(__FUNCTION__);
    }


    return 0;
}

int Socket_lua::gc(lua_State *L)
{
    Socket *pSocket = *static_cast<Socket **>(lua_touserdata(L, 1));

    // Mark the socket for deletion.
    if( pSocket->mutex.lock(INFINITE, __FUNCTION__) )
    {
        if( pSocket->hThread )
            close(L); // We only need to close() it if our thread is alive

        pSocket->deleteMe   =   true;
        pSocket->mutex.unlock(__FUNCTION__);
    }

    return 0;
}

int Socket_lua::tostring(lua_State *L)
{
    Socket *pSocket = *static_cast<Socket **>(lua_touserdata(L, 1));
    char buffer[64];
    slprintf(buffer, sizeof(buffer), "Socket (0x%p)", pSocket->socket);

    lua_pushstring(L, buffer);
    return 1;
}

#endif
