 /*
 * zSocket.c
 * 
 * The Zen API 
 * Copyright (C) 2008-2009 by LMI Technologies Inc.
 * 
 * Licensed under The MIT License.
 * Redistributions of files must retain the above copyright notice.
 */
#include <z/zzSocket.h>
#include <z/zzNetwork.h>
#include <stdio.h>

ZAPI_EXPORT(zStatus) zSocket_WaitAny(const zSocket* sockts, z32u count, zBool* status, z64u timeout)
{    
    struct timeval tv;
    fd_set readSet;
    zSOCKET_HANDLE highest = 0;
    int result;
    z32u i;    

    if (count > FD_SETSIZE)
    {
        return zERROR_PARAMETER;
    }

    FD_ZERO(&readSet);

    for (i = 0; i < count; i++)
    {
        zSocketClass* obj = zHANDLE_POINTER(sockts[i]);

        if (obj->sockt > highest)
        {
            highest = obj->sockt;
        }
        FD_SET(obj->sockt, &readSet);
    }

    result = select((int) (highest + 1), &readSet, NULL, NULL, zSocket_FormatTimeVal(&tv, timeout));

    if (result == 0) 
    {
        return zERROR_TIMEOUT;
    }
    else if (result < 0)
    {
        return zERROR;
    }

    for (i = 0; i < count; i++)
    {
        zSocketClass* obj = zHANDLE_POINTER(sockts[i]);
        status[i] = FD_ISSET(obj->sockt, &readSet); 
    }

    return zOK;
}

ZAPI_EXPORT(struct timeval*) zSocket_FormatTimeVal(struct timeval* tv, z64u timeout)
{
    if (timeout == zINFINITE)
    {
        return zNULL; 
    }
  
    tv->tv_sec = (long) timeout / 1000000;
    tv->tv_usec = (long) (timeout % 1000000);
   
    return tv;  
}

ZAPI_EXPORT(zStatus) zSocket_Construct(zSocket* sockt, zSocketType type)
{
    zSOCKET_HANDLE sock = socket(AF_INET, (type == zSOCKET_TYPE_TCP) ? SOCK_STREAM : SOCK_DGRAM, 0);
    zStatus status = zOK; 
    
    if (!zSUCCESS(status = zSocket_ConstructAttach(sockt, &sock)))
    {
        if (sock != zSOCKET_INVALID_SOCKET)
        {
            zSocket_Close(sockt); 
        }
    }

    return status; 
}

ZAPI_EXPORT(zStatus) zSocket_ConstructAttach(zSocket* sockt, zSOCKET_HANDLE* socktHandle)
{
    zSocketClass* obj = zNULL;  

    zCHECK(*socktHandle != zSOCKET_INVALID_SOCKET); 

    zCHECK(zCalloc(&obj, 1, sizeof(zSocketClass))); 
    
    obj->sockt = *socktHandle; 
    obj->status = zOK;

    *socktHandle = zSOCKET_INVALID_SOCKET; 

    zHANDLE(sockt, obj); 

    return zOK; 
}

ZAPI_EXPORT(zStatus) zSocket_Destroy(zSocket socket)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);
    
    if (!zIS_NULL(obj))
    {
        if (zSUCCESS(obj->status))
        {
            shutdown(obj->sockt, zSOCKET_SHUTDOWN_BOTH); 
        }

        zSocket_Close(socket); 
     
        zFree(obj);
    }    

    return zOK;
}

ZAPI_EXPORT(zStatus) zSocket_Bind(zSocket socket, zIPAddress address, z32u port)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);     
    struct sockaddr_in sockAddr;

    zCHECK(obj->status);
    
    zCHECK(zIPAddress_SockAddr(address, port, &sockAddr));           
    zCHECK(bind(obj->sockt, (struct sockaddr*)&sockAddr, sizeof(sockAddr)) == 0); 

    return zOK; 
}

ZAPI_EXPORT(zStatus) zSocket_Connect(zSocket socket, zIPAddress address, z32u port, z64u timeout)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);            
    struct sockaddr_in sockAddr;
    fd_set writeSet; 
    struct timeval tv; 
    zStatus exception;

    zCHECK(obj->status);

    zEXCEPT_TRY
    {     
        zEXCEPT_CHECK(zSocket_SetBlocking(socket, zFALSE));
        zEXCEPT_CHECK(zIPAddress_SockAddr(address, port, &sockAddr));
    
        if (connect(obj->sockt, (struct sockaddr*)&sockAddr, sizeof(sockAddr)) == zSOCKET_ERROR)
        {   
            if (zSocket_GetLastError() == zSOCKET_WOULD_BLOCK || zSocket_GetLastError() == zSOCKET_IN_PROGRESS )
            {
                FD_ZERO(&writeSet); 
                FD_SET(obj->sockt, &writeSet); 

                switch (select((int)(obj->sockt+1), NULL, &writeSet, NULL, zSocket_FormatTimeVal(&tv, timeout)))
                {
                    case 1:    
                        {
                            int errorCode; 
                            socklen_t errorSize = sizeof(int); 

                            zEXCEPT_CHECK(getsockopt(obj->sockt, SOL_SOCKET, SO_ERROR, (void*)&errorCode, &errorSize) == 0); 

                            if (errorCode != 0)
                            {
                                zEXCEPT_THROW(zERROR); 
                            }

                            break;
                        }
                    case 0:     
                        {
                            zEXCEPT_THROW(zERROR_TIMEOUT); 
                        }
                    default:    
                        {
                            zEXCEPT_THROW(zERROR); 
                        }
                }
            }
            else
            {
                zEXCEPT_THROW(zERROR);
            }
        }   
    
        zEXCEPT_CHECK(zSocket_SetBlocking(socket, zTRUE));
    }
    zEXCEPT_CATCH(&exception)
    {
        obj->status = exception;
        zEXCEPT_SET(exception); 
    }
    zEXCEPT_FINALLY
    {
        zEXCEPT_CLOSE(); 
    }

    return zOK;
}

ZAPI_EXPORT(zStatus) zSocket_Accept(zSocket socket, zSocket* connection)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);        
    zSOCKET_HANDLE sock = zSOCKET_INVALID_SOCKET; 
    zStatus status = zOK; 

    zCHECK(obj->status);
        
    if ((sock = accept(obj->sockt, 0, 0)) == zSOCKET_INVALID_SOCKET)
    {
        *connection = zNIL; 
    }
    else if (!zSUCCESS(status = zSocket_ConstructAttach(connection, &sock)))
    {
       zSocket_Close(socket);         
    }

    return status; 
}

ZAPI_EXPORT(zStatus) zSocket_Listen(zSocket socket, z32u backlog)
{ 
    zSocketClass* obj = zHANDLE_POINTER(socket);        

    zCHECK(obj->status);

    zCHECK(listen(obj->sockt, (int) backlog) != zSOCKET_ERROR); 

    return zOK;
}

ZAPI_EXPORT(zStatus) zSocket_Wait(zSocket socket, z64u timeout)
{      
    zSocketClass* obj = zHANDLE_POINTER(socket);
    struct timeval tv;
    fd_set readSet; 

    zCHECK(obj->status);

    FD_ZERO(&readSet);
    FD_SET(obj->sockt, &readSet);

    switch (select((int)(obj->sockt+1), &readSet, NULL, NULL, zSocket_FormatTimeVal(&tv, timeout)))
    {
        case 1:     return zOK; 
        case 0:     return zERROR_TIMEOUT; 
        default:    
        {  
            obj->status = zERROR_STREAM; 
            return obj->status;          
        }
    }
}

ZAPI_EXPORT(zStatus) zSocket_Read(zSocket socket, void* buffer, zUIntPtr size, zUIntPtr* read)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);
    zIntPtr result; 

    zCHECK(obj->status);

    if (size == 0)
    {
        return zOK; 
    }

    result = zSocket_Recv(socket, buffer, size);
    
    if (result > 0)
    {
        if (!zIS_NULL(read))
        {
            *read = (zUIntPtr) result; 
        }
    }
    else if (result == 0)
    {
        obj->status = zERROR_CLOSED; 
    }
    else if (zSocket_GetLastError() == zSOCKET_TIMEDOUT)
    {
        obj->status = zERROR_TIMEOUT; 
    }
    else
    {
        obj->status = zERROR_CLOSED; 
    }

    return obj->status;
}

ZAPI_EXPORT(zStatus) zSocket_ReadFrom(zSocket socket, zIPEndPoint *endPoint, void* buffer, zUIntPtr size, zUIntPtr* read)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);
    struct sockaddr_in sockAddr;
    zIntPtr result; 
    
    zCHECK(obj->status);

    result = zSocket_RecvFrom(socket, buffer, size, &sockAddr);
    
    if (result > 0)
    {
        if (!zIS_NULL(endPoint))
        {
            zCHECK(zSockAddr_IPAddress(sockAddr, &endPoint->address, &endPoint->port));
        }
        if (!zIS_NULL(read))
        {
            *read = (zUIntPtr) result; 
        }
    }
    else if (result == 0)
    {
        obj->status = zERROR_CLOSED; 
    }
    else
    {
        switch (zSocket_GetLastError())
        {
            case zSOCKET_TIMEDOUT: 
                obj->status = zERROR_TIMEOUT; 
                break;
            case zSOCKET_MSGSIZE: 
                obj->status = zERROR_INCOMPLETE; 
                break;
            default:
                obj->status = zERROR; 
                break; 
        }
    }
    
    return obj->status; 
}

ZAPI_EXPORT(zStatus) zSocket_Write(zSocket socket, const void* buffer, zUIntPtr size)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);
    const zByte* data = buffer; 
    zIntPtr total = (zIntPtr) size; 
    zIntPtr written = 0; 

    zCHECK(obj->status);

    while (written < total)
    {
        zIntPtr result = zSocket_Send(socket, &data[written], total-written);

        if (result > 0)
        {
            written += result; 
        }
        else
        {
            if (zSocket_GetLastError() == zSOCKET_TIMEDOUT)
            {
                obj->status = zERROR_TIMEOUT; 
            }
            else
            {
                obj->status = zERROR; 
            }

            return obj->status; 
        }
    }

    return zOK; 
}

ZAPI_EXPORT(zStatus) zSocket_WriteTo(zSocket socket, zIPAddress address, z32u port, const void* buffer, zUIntPtr size)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);
    struct sockaddr_in sockAddr;
    
    zCHECK(obj->status);

    zCHECK(zIPAddress_SockAddr(address, port, &sockAddr));

    if (zSocket_SendTo(socket, buffer, size, &sockAddr) != (zIntPtr)size)
    {
        switch (zSocket_GetLastError())
        {
            case zSOCKET_TIMEDOUT: 
                obj->status = zERROR_TIMEOUT; 
                break;
            case zSOCKET_MSGSIZE: 
                obj->status = zERROR_INCOMPLETE; 
                break;
            default:
                obj->status = zERROR; 
                break; 
        }
    }

    return obj->status; 
}

ZAPI_EXPORT(zStatus) zSocket_SetWriteBuffer(zSocket sock, zUIntPtr size)
{
    zSocketClass* obj = (zSocketClass*) sock;     
    int value = (int) zMATH_MIN(size, zSOCKET_MAX_BUFFER); 

    zCHECK(obj->status);
        
    zCHECK(setsockopt(obj->sockt, SOL_SOCKET, SO_SNDBUF, (char*)&value, sizeof(int)) != zSOCKET_ERROR); 
    
    return zOK;
}

ZAPI_EXPORT(zStatus) zSocket_SetReadBuffer(zSocket sock, zUIntPtr size)
{
    zSocketClass* obj = (zSocketClass*) sock;     
    int value = (int) zMATH_MIN(size, zSOCKET_MAX_BUFFER); 

    zCHECK(obj->status);

    zCHECK(setsockopt(obj->sockt, SOL_SOCKET, SO_RCVBUF, (char*)&value, sizeof(int)) != zSOCKET_ERROR); 
    
    return zOK;  
}

ZAPI_EXPORT(zStatus) zSocket_EnableBroadcast(zSocket sock, zBool broadcast)
{
    zSocketClass* obj = (zSocketClass*) sock;     
    zBool value = (broadcast) ? zTRUE : zFALSE;
    
    zCHECK(obj->status);

    zCHECK(setsockopt(obj->sockt, SOL_SOCKET, SO_BROADCAST, (char*)&value, sizeof(zBool)) != zSOCKET_ERROR); 

    return zOK;   
}

ZAPI_EXPORT(zStatus) zSocket_EnableReuseAddress(zSocket sock, zBool reuse)
{
    zSocketClass* obj = (zSocketClass*) sock;     
    zBool value = (reuse) ? zTRUE : zFALSE;
    
    zCHECK(obj->status);

    zCHECK(setsockopt(obj->sockt, SOL_SOCKET, SO_REUSEADDR, (char*)&value, sizeof(zBool)) != zSOCKET_ERROR);
    return zOK;   
}

ZAPI_EXPORT(zStatus) zSocket_SetNoDelay(zSocket sock, zBool noDelay)
{
    zSocketClass* obj = (zSocketClass*) sock;    
    zBool value = (noDelay) ? zTRUE : zFALSE; 
    
    zCHECK(obj->status);

    zCHECK(setsockopt(obj->sockt, IPPROTO_TCP, TCP_NODELAY, (char*)&value, sizeof(zBool)) != zSOCKET_ERROR); 
    return zOK;   
}

ZAPI_EXPORT(zStatus) zSocket_SetLingerTime(zSocket sock, z64u lingerTime)
{
    zSocketClass* obj = (zSocketClass*) sock;     
    struct linger linger;

    zCHECK(obj->status);

    linger.l_onoff = 1;
    linger.l_linger = (unsigned short) ((lingerTime + 999999)/1000000);
    zCHECK(setsockopt(obj->sockt, SOL_SOCKET, SO_LINGER, (char*) &linger, sizeof(struct linger)) != zSOCKET_ERROR); 
   
    return zOK;   
}

ZAPI_EXPORT(zHandle) zSocket_Handle(zSocket sock)
{
    zSocketClass* obj = (zSocketClass*) sock;

    return (zHandle)(zUIntPtr) obj->sockt; 
}

ZAPI_EXPORT(zStatus) zSocket_LocalEndPoint(zSocket sock, zIPEndPoint* endPoint)
{
    zSocketClass* obj = (zSocketClass*) sock;   
    struct sockaddr_in sockAddr;
    socklen_t sockAddrLength = (socklen_t) sizeof(sockAddr);
    
    zCHECK(obj->status);

    zCHECK(getsockname(obj->sockt, (struct sockaddr*)&sockAddr, &sockAddrLength) != zSOCKET_ERROR); 
    zCHECK(zSockAddr_IPAddress(sockAddr,&endPoint->address, &endPoint->port));
 
    return zOK;
}

ZAPI_EXPORT(zStatus) zSocket_RemoteEndPoint(zSocket sock, zIPEndPoint* endPoint)
{
    zSocketClass* obj = (zSocketClass*) sock;    
    struct sockaddr_in sockAddr;
    socklen_t sockAddrLength = (socklen_t) sizeof(sockAddr);
     
    zCHECK(obj->status);
 
    zCHECK(getpeername(obj->sockt, (struct sockaddr*)&sockAddr, &sockAddrLength) == 0); 
    zCHECK(zSockAddr_IPAddress(sockAddr,&endPoint->address, &endPoint->port));
    
    return zOK;
}

#if defined(ZAPI_WINDOWS)

ZAPI_EXPORT(zIntPtr) zSocket_Recv(zSocket socket, void* buffer, zUIntPtr size)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);	
	
    return recv(obj->sockt, buffer, (int) zMATH_MIN(size, zSOCKET_MAX_IO_SIZE), 0); 
}

ZAPI_EXPORT(zIntPtr) zSocket_RecvFrom(zSocket socket, void* buffer, zUIntPtr size, struct sockaddr_in* from)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);
    socklen_t sockAddrLength = (socklen_t) sizeof(struct sockaddr_in);

    return recvfrom(obj->sockt, buffer, (int) zMATH_MIN(size, zSOCKET_MAX_IO_SIZE), 0, (struct sockaddr*)from, &sockAddrLength); 
}

ZAPI_EXPORT(zIntPtr) zSocket_Send(zSocket socket, const void* buffer, zUIntPtr size)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);	

    return send(obj->sockt, buffer, (int) zMATH_MIN(size, zSOCKET_MAX_IO_SIZE), 0); 
}

ZAPI_EXPORT(zIntPtr) zSocket_SendTo(zSocket socket, const void* buffer, zUIntPtr size, const struct sockaddr_in* to)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);	
    socklen_t sockAddrLength = (socklen_t) sizeof(struct sockaddr_in);
	
    return sendto(obj->sockt, buffer, (int) zMATH_MIN(size, zSOCKET_MAX_IO_SIZE), 0, (const struct sockaddr*)to, sockAddrLength); 	
}

ZAPI_EXPORT(z32u) zSocket_GetLastError()
{
    return WSAGetLastError();
}

ZAPI_EXPORT(zStatus) zSocket_Close(zSocket socket)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);
    
    zCHECK(closesocket(obj->sockt) == 0); 
    obj->sockt = zSOCKET_INVALID_SOCKET; 

    return zOK;
}

ZAPI_EXPORT(zStatus) zSocket_SetBlocking(zSocket socket, zBool isBlocking)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);
    unsigned long nonBlocking = !isBlocking;
       
    zCHECK(ioctlsocket(obj->sockt, FIONBIO, &nonBlocking) == 0);
    
    return zOK;
}

ZAPI_EXPORT(zStatus) zSocket_SetWriteTimeout(zSocket sock, z64u timeout)
{
    zSocketClass* obj = (zSocketClass*) sock;     
    int value = (int) (timeout + 999)/1000;

    zCHECK(setsockopt(obj->sockt, SOL_SOCKET, SO_SNDTIMEO, (char*)&value, sizeof(int)) != zSOCKET_ERROR); 

    return zOK;  
} 

ZAPI_EXPORT(zStatus) zSocket_SetReadTimeout(zSocket sock, z64u timeout)
{
    zSocketClass* obj = (zSocketClass*) sock;     
    int value = (int) (timeout + 999)/1000;
    
    zCHECK(setsockopt(obj->sockt, SOL_SOCKET, SO_RCVTIMEO, (char*)&value, sizeof(int)) != zSOCKET_ERROR); 

    return zOK;  
}

#elif defined (ZAPI_LINUX)

ZAPI_EXPORT(zIntPtr) zSocket_Recv(zSocket socket, void* buffer, zUIntPtr size)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);	
    return recv(obj->sockt, buffer, size, 0); 
}

ZAPI_EXPORT(zIntPtr) zSocket_RecvFrom(zSocket socket, void* buffer, zUIntPtr size, struct sockaddr_in* from)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);
    socklen_t sockAddrLength = (socklen_t) sizeof(struct sockaddr_in);

    return recvfrom(obj->sockt, buffer, size, 0, (struct sockaddr*)from, &sockAddrLength); 
}

ZAPI_EXPORT(zIntPtr) zSocket_Send(zSocket socket, const void* buffer, zUIntPtr size)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);	

    return send(obj->sockt, buffer, size, 0); 
}

ZAPI_EXPORT(zIntPtr) zSocket_SendTo(zSocket socket, const void* buffer, zUIntPtr size, const struct sockaddr_in* to)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);	
    socklen_t sockAddrLength = (socklen_t) sizeof(struct sockaddr_in);
	
    return sendto(obj->sockt, buffer, size, 0, (const struct sockaddr*)to, sockAddrLength); 	
}

ZAPI_EXPORT(z32u) zSocket_GetLastError()
{
    return errno;
}

ZAPI_EXPORT(zStatus) zSocket_Close(zSocket socket)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);

    close(obj->sockt);
    obj->sockt = zSOCKET_INVALID_SOCKET;

    return zOK;
}

ZAPI_EXPORT(zStatus) zSocket_SetBlocking(zSocket socket, zBool isBlocking)
{
    zSocketClass* obj = zHANDLE_POINTER(socket);
    int flags = fcntl(obj->sockt, F_GETFL);

    if (isBlocking)
    {
        fcntl(obj->sockt, F_SETFL, flags &~O_NONBLOCK);
    }
    else
    {
        fcntl(obj->sockt, F_SETFL, O_NONBLOCK);
    }

    return zOK;
}

ZAPI_EXPORT(zStatus) zSocket_SetWriteTimeout(zSocket sock, z64u timeout)
{
    zSocketClass* obj = (zSocketClass*) sock;     
    struct timeval tv; 

    zCHECK(setsockopt(obj->sockt, SOL_SOCKET, SO_SNDTIMEO, zSocket_FormatTimeVal(&tv, timeout), sizeof(tv)) != zSOCKET_ERROR);
    
    return zOK;  
} 

ZAPI_EXPORT(zStatus) zSocket_SetReadTimeout(zSocket sock, z64u timeout)
{
    zSocketClass* obj = (zSocketClass*) sock;     
    struct timeval tv;

    zCHECK(setsockopt(obj->sockt, SOL_SOCKET, SO_RCVTIMEO, zSocket_FormatTimeVal(&tv, timeout), sizeof(tv)) != zSOCKET_ERROR);
    
    return zOK;  
}

#endif
