/*
 * zTcpClient.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/zzTcpClient.h>
#include <z/zzSocket.h>
#include <stdlib.h>
#include <memory.h>

ZAPI_EXPORT(zStatus) zTcpClient_WaitAny(const zTcpClient* clients, z32u count, zBool* status, z64u timeout)
{   
    zSocket sockets[zSOCKET_MAX_WAIT_GROUP];
    z32u i;
    
    for (i = 0; i < count; i++)
    {
        zTcpClientClass* obj = zHANDLE_POINTER(clients[i]);
        sockets[i] = obj->socket;
    }
    
    return zSocket_WaitAny(sockets, count, status, timeout);
}

ZAPI_EXPORT(zStatus) zTcpClient_ConstructAttach(zTcpClient* client, zSocket* socket)
{
    zTcpClientClass* obj = zNULL; 

    zCHECK(!zIS_NIL(*socket)); 
    
    zCHECK(zCalloc(&obj, 1, sizeof(zTcpClientClass))); 
  
    obj->socket = *socket;
    *socket = zNIL; 
    zHANDLE(client, obj); 

    return zOK; 
}

ZAPI_EXPORT(zStatus) zTcpClient_Construct(zTcpClient* client)
{
    zSocket socket = zNIL; 
    zStatus exception; 

    zEXCEPT_TRY
    {
        zEXCEPT_CHECK(zSocket_Construct(&socket, zSOCKET_TYPE_TCP));
        zEXCEPT_CHECK(zSocket_Bind(socket, zIP_ADDRESS_ANY,zIP_PORT_ANY));
        zEXCEPT_CHECK(zTcpClient_ConstructAttach(client, &socket));
    }
    zEXCEPT_CATCH(&exception)
    {
        if (!zIS_NIL(socket))
        {
            zSocket_Destroy(socket); 
        }
        zEXCEPT_SET(exception); 
    }
    zEXCEPT_FINALLY
    {
        zEXCEPT_CLOSE(); 
    }
    
    return zOK; 
}

ZAPI_EXPORT(zStatus) zTcpClient_Destroy(zTcpClient client)
{
    zTcpClientClass* obj = zHANDLE_POINTER(client);
        
    if (!zIS_NULL(obj))
    {
        if (!zIS_NIL(obj->socket))          zCHECK(zSocket_Destroy(obj->socket));    
        if (!zIS_NULL(obj->readBuffer))     zCHECK(zFree(obj->readBuffer));   
        if (!zIS_NULL(obj->writeBuffer))    zCHECK(zFree(obj->writeBuffer));

        zCHECK(zFree(obj)); 
    }
    
    return zOK;
}

ZAPI_EXPORT(zStatus) zTcpClient_SetWriteBuffers(zTcpClient client, zIntPtr socketSize, zIntPtr clientSize)
{
    zTcpClientClass* obj = zHANDLE_POINTER(client);

    if (obj->writeEnd != 0)
    {
        return zERROR_STATE; 
    }

    if (socketSize >= 0)
    {
        zCHECK(zSocket_SetWriteBuffer(obj->socket, (zUIntPtr)socketSize));
    }

    if (clientSize >= 0)
    {
        if (!zIS_NULL(obj->writeBuffer))
        {
            zCHECK(zFree(obj->writeBuffer)); 
        }

        obj->writeBuffer = zNULL; 
        obj->writeCapacity = 0; 
        obj->writeEnd = 0; 

        if (clientSize > 0)
        {
            zCHECK(zMalloc(&obj->writeBuffer, (zUIntPtr)clientSize)); 
            obj->writeCapacity = clientSize; 
        }
    }
    
    return zOK;
}

ZAPI_EXPORT(zStatus) zTcpClient_SetReadBuffers(zTcpClient client, zIntPtr socketSize, zIntPtr clientSize)
{
    zTcpClientClass* obj = zHANDLE_POINTER(client);

    if ((obj->readEnd - obj->readBegin) != 0)
    {
        return zERROR_STATE; 
    }

    if (socketSize >= 0)
    {
        zCHECK(zSocket_SetReadBuffer(obj->socket, (zUIntPtr)socketSize));
    }

    if (clientSize >= 0)
    {
        if (!zIS_NULL(obj->readBuffer))
        {
            zCHECK(zFree(obj->readBuffer)); 
        }

        obj->readBuffer = zNULL; 
        obj->readCapacity = 0; 
        obj->readBegin = 0; 
        obj->readEnd = 0; 

        if (clientSize > 0)
        {
            zCHECK(zMalloc(&obj->readBuffer, (zUIntPtr)clientSize));                 
            obj->readCapacity = clientSize; 
        }
    }
    
    return zOK;
}

ZAPI_EXPORT(zStatus) zTcpClient_SetWriteTimeout(zTcpClient client, z64u timeout)
{
    zTcpClientClass* obj = zHANDLE_POINTER(client);
        
    return zSocket_SetWriteTimeout(obj->socket, timeout);
}

ZAPI_EXPORT(zStatus) zTcpClient_SetReadTimeout(zTcpClient client, z64u timeout)
{
    zTcpClientClass* obj = zHANDLE_POINTER(client);
    
    return zSocket_SetReadTimeout(obj->socket, timeout);
}

ZAPI_EXPORT(zStatus) zTcpClient_Connect(zTcpClient client, zIPAddress address, z32u port, z64u timeout)
{
    zTcpClientClass* obj = zHANDLE_POINTER(client);
   
    return zSocket_Connect(obj->socket, address, port, timeout);
}

ZAPI_EXPORT(zStatus) zTcpClient_Wait(zTcpClient client, z64u timeout)
{
    zTcpClientClass* obj = zHANDLE_POINTER(client);
    
    if (zTcpClient_Available(client) > 0)
    {
        return zOK;
    }
    else
    {
        return zSocket_Wait(obj->socket, timeout);    
    }
}

ZAPI_EXPORT(zStatus) zTcpClient_Flush(zTcpClient client)
{
    zTcpClientClass* obj = zHANDLE_POINTER(client);
        
    if (obj->writeEnd > 0)
    {
        zCHECK(zSocket_Write(obj->socket, obj->writeBuffer, obj->writeEnd));
        obj->writeEnd = 0; 
    }
            
    return zOK;
}

ZAPI_EXPORT(zStatus) zTcpClient_Read(zTcpClient client, void* buffer, zUIntPtr size)
{
    zTcpClientClass* obj = zHANDLE_POINTER(client);
    zByte* dest = buffer;  
    zUIntPtr destEnd = 0; 
    zUIntPtr destCapacity = size; 
    zUIntPtr copyCount, bytesRead; 
  
    copyCount = zMATH_MIN(destCapacity, obj->readEnd - obj->readBegin);
    memcpy(&dest[destEnd], &obj->readBuffer[obj->readBegin], copyCount);     
    obj->readBegin += copyCount; 
    destEnd += copyCount; 

    if ((destCapacity - destEnd) > 0)
    {
        if ((destCapacity - destEnd) >= obj->readCapacity)
        {            
            zCHECK(zTcpClient_ReadAtLeast(client, &dest[destEnd], destCapacity-destEnd, destCapacity-destEnd, &bytesRead));
        }
        else
        {
            obj->readBegin = 0; 
            obj->readEnd = 0; 

            zCHECK(zTcpClient_ReadAtLeast(client, &obj->readBuffer[obj->readBegin], destCapacity-destEnd, obj->readCapacity-obj->readBegin, &bytesRead));                
            obj->readEnd += bytesRead; 

            memcpy(&dest[destEnd], &obj->readBuffer[obj->readBegin], destCapacity-destEnd);     
            obj->readBegin += destCapacity-destEnd; 
        }
    }

    return zOK; 
}

ZAPI_EXPORT(zStatus) zTcpClient_ReadAtLeast(zTcpClient client, zByte* buffer, zUIntPtr minCount, zUIntPtr maxCount, zUIntPtr* bytesRead)
{
    zTcpClientClass* obj = zHANDLE_POINTER(client);
    zUIntPtr totalRead = 0; 
    zUIntPtr read; 

    while (totalRead < minCount)
    {
        zCHECK(zSocket_Read(obj->socket, &buffer[totalRead], maxCount-totalRead, &read));         
        totalRead += read; 
    }

    *bytesRead = totalRead; 

    return zOK; 
}

ZAPI_EXPORT(zStatus) zTcpClient_Write(zTcpClient client, const void* buffer, zUIntPtr size)
{
    zTcpClientClass* obj = zHANDLE_POINTER(client);
    const zByte* src = buffer; 
    zUIntPtr srcBegin = 0; 
    zUIntPtr srcCapacity = size; 
    zUIntPtr copyCount; 
    
    if (obj->writeEnd > 0)
    {
        copyCount = zMATH_MIN(srcCapacity-srcBegin, obj->writeCapacity-obj->writeEnd); 
        memcpy(&obj->writeBuffer[obj->writeEnd], &src[srcBegin], copyCount); 
        srcBegin += copyCount; 
        obj->writeEnd += copyCount; 

        if (obj->writeEnd == obj->writeCapacity)
        {
            zCHECK(zTcpClient_Flush(client)); 
        }
    }

    if ((srcCapacity - srcBegin) > 0)
    {
        if ((srcCapacity - srcBegin) >= obj->writeCapacity)
        {
            zCHECK(zSocket_Write(obj->socket, &src[srcBegin], srcCapacity-srcBegin)); 
        }
        else
        {
            memcpy(&obj->writeBuffer[obj->writeEnd], &src[srcBegin], srcCapacity-srcBegin);  
            obj->writeEnd += srcCapacity-srcBegin; 
        }
    }
  
    return zOK; 
}

ZAPI_EXPORT(zSocket) zTcpClient_Socket(zTcpClient client)
{ 
    zTcpClientClass* obj = zHANDLE_POINTER(client);

    return obj->socket; 
}

ZAPI_EXPORT(zUIntPtr) zTcpClient_Available(zTcpClient client)
{
    zTcpClientClass* obj = zHANDLE_POINTER(client);

    return obj->readEnd - obj->readBegin;
}

ZAPI_EXPORT(zStatus) zTcpClient_LocalEndPoint(zTcpClient client, zIPEndPoint* endPoint)
{
    zTcpClientClass* obj = zHANDLE_POINTER(client);
        
    return zSocket_LocalEndPoint(obj->socket, endPoint);
}

ZAPI_EXPORT(zStatus) zTcpClient_RemoteEndPoint(zTcpClient client, zIPEndPoint* endPoint)
{
    zTcpClientClass* obj = zHANDLE_POINTER(client);

    return zSocket_RemoteEndPoint(obj->socket, endPoint);
}
