#include "DX_VOS_BaseSocket.h"
#include "VOS_API/DX_VOS_Time.h"
#include "VOS_API/DX_VOS_Mem.h"
#include "VOS_API/DX_VOS_File.h"
#include "VOS_API/DX_VOS_Stdio.h"

#if defined(_DEBUG) && !defined(DX_NO_SOCKET_LOG) && !defined(DX_SOCKET_LOG)
#define DX_SOCKET_LOG
#endif

DxBool g_SocketLogEnabled = DX_FALSE;

struct _DxVosSocket
{
    void* m_Socket;
    DxVosFile m_File;
    const DxChar* m_Protocol;
};

static const DxChar* g_DxProtocolNames[] = {	"IP", "IPV6", "TCP", "UDP", "RAW"};

static void DxOpenSocketLogFile(DxVosSocket aSocket, const DxIpAddress* aIPAddress)
{
#ifdef DX_SOCKET_LOG
    DxChar fileName[DX_VOS_MAX_PATH];
    if (g_SocketLogEnabled)
    {
        DX_VOS_SPrintf(fileName, sizeof(fileName), "SocketsLog" DX_VOS_PATH_DIVIDER "%%s_%s_%d.%d.%d.%d_%d_%d.DxSock", 
            aSocket->m_Protocol, aIPAddress->m_Address[0], aIPAddress->m_Address[1], aIPAddress->m_Address[2], 
            aIPAddress->m_Address[3], aIPAddress->m_Port, DX_VOS_GetTickCount());

        DX_VOS_FileOpenWithTimeStamp(&aSocket->m_File, fileName, DX_FILE_CREATE_NEW, DX_SHARE_READ);
    }
#else
    aSocket = aSocket;
    aIPAddress = aIPAddress;
#endif
}

static void DxLogSocketPacket(DxVosSocket aSocket, DxBool isTx, const void* data, DxUint32 dataLen)
{
#ifdef DX_SOCKET_LOG
    DxUint32 currTick = DX_VOS_GetTickCount();
    if (g_SocketLogEnabled)
    {
        DX_VOS_FWrite(aSocket->m_File, &currTick, sizeof(currTick));
        DX_VOS_FWrite(aSocket->m_File, &isTx, sizeof(isTx));
        DX_VOS_FWrite(aSocket->m_File, &dataLen, sizeof(dataLen));
        DX_VOS_FWrite(aSocket->m_File, data, dataLen);
    }
#else
    aSocket = aSocket;
    isTx = isTx;
    data = data;
    dataLen = dataLen;
#endif
}

DxBool DX_VOS_EnableSocketLog(DxBool newValue)
{
    DxBool oldValue = g_SocketLogEnabled;
    g_SocketLogEnabled = newValue;
    DX_RETURN(oldValue);
}

DxStatus DX_VOS_SocketCreate(DxVosSocket *aSocket, EDxAddressFamily addressFamily,
										  EDxSocketType aSockType, EDxProtocol protocol)
{
    DX_DECLARE(DxStatus, result, DX_SUCCESS);
    void* tmpSocket = DX_NULL;
    DX_ASSERT_PARAM(aSocket != DX_NULL);
    DX_ASSERT_PARAM(addressFamily < DX_NUMBER_OF_ADDRESS_FAMILIES);
    DX_ASSERT_PARAM(aSockType < DX_NUMBER_OF_SOCKET_TYPES);
    DX_ASSERT_PARAM(protocol < DX_NUMBER_OF_PROTOCOLS);
	
    result = DX_VOS_BaseSocketCreate(&tmpSocket, DxAddressFamiliesTable[addressFamily],
                DxSocketTypesTable[aSockType], DxProtocolsTable[protocol]);
    if (result != DX_SUCCESS)
    {
        DX_VOS_BaseLogSocketResult();
        RETURN_OLD_ERROR(result);
    }
   
    *aSocket = (DxVosSocket)DX_VOS_MemMalloc(sizeof(struct _DxVosSocket));
    RETURN_IF_ALLOC_FAILED(*aSocket);

    (*aSocket)->m_Socket = tmpSocket;
    (*aSocket)->m_File = DX_NULL;
    (*aSocket)->m_Protocol = g_DxProtocolNames[protocol];
    DX_DBG_LOG_OBJECT_CREATION(DxVosSocket, *aSocket);

    DX_RETURN(DX_SUCCESS);
}

DxStatus DX_VOS_SocketListen(DxVosSocket aSocket, DxInt aBackLog)
{
    DX_DECLARE(DxStatus, result, DX_SUCCESS);
    DX_ASSERT_PARAM(aSocket != DX_INVALID_SOCKET);

	result =  DX_VOS_BaseSocketListen(aSocket->m_Socket, aBackLog);
    if (result != DX_SUCCESS)
        DX_VOS_BaseLogSocketResult();

    RETURN_OLD_ERROR(result);
}

DxStatus DX_VOS_SocketConnect(DxVosSocket aSocket, const DxIpAddress* aIPAddress)
{
    DX_DECLARE(DxStatus, result, DX_SUCCESS);

    DX_ASSERT_PARAM(aSocket != DX_INVALID_SOCKET);
    DX_ASSERT_PARAM(aIPAddress != DX_NULL);

    result = DX_VOS_BaseSocketConnect(aSocket->m_Socket, aIPAddress);
    if (result != DX_SUCCESS)
    {
        DX_VOS_BaseLogSocketResult();
        RETURN_OLD_ERROR(result);
    }
    
    DxOpenSocketLogFile(aSocket, aIPAddress);
    DX_RETURN(DX_SUCCESS);    
}

DxStatus DX_VOS_SocketClose (DxVosSocket* aSocket)
{
    DX_DECLARE(DxStatus, result, DX_SUCCESS);
    if (aSocket == DX_NULL)
        DX_RETURN(DX_SUCCESS);
    
    if (*aSocket == DX_INVALID_SOCKET)
		DX_RETURN(DX_SUCCESS);

	result = DX_VOS_BaseSocketClose((*aSocket)->m_Socket);
    if (result != DX_SUCCESS)
    {
        DX_VOS_BaseLogSocketResult();
        RETURN_OLD_ERROR(result);
    }

#ifdef DX_SOCKET_LOG   
    DX_VOS_FileClose(&(*aSocket)->m_File);
#endif
    DX_VOS_MemFree(*aSocket);
    DX_DBG_LOG_OBJECT_DESTRUCTION(DxVosSocket, *aSocket);
    *aSocket = DX_INVALID_SOCKET;
    
    DX_RETURN(DX_SUCCESS);
}


DxStatus DX_VOS_SocketWaitForData(DxVosSocket aSocket, DxUint32 aTimeout)
{
    DX_DECLARE(DxStatus, result, DX_SUCCESS);
    DX_ASSERT_PARAM(aSocket != DX_INVALID_SOCKET);

	result = DX_VOS_BaseSocketWaitForData(aSocket->m_Socket, aTimeout);
    if (result != DX_SUCCESS && result != DX_TIMEOUT_EXPIRED)
    {
        DX_VOS_BaseLogSocketResult();
        RETURN_OLD_ERROR(result);
    }
    DX_RETURN(result);
}

DxStatus DX_VOS_SocketRecv(DxVosSocket aSocket, void *aBuf, DxUint32 aBufLen, DxUint32* bytesRead, DxUint32 timeout)
{
	DX_DECLARE(DxStatus, result, DX_SUCCESS);
    DxUint32 localBytesRead = 0;

    if (bytesRead == DX_NULL)
        bytesRead = &localBytesRead;

    *bytesRead = 0;

	DX_ASSERT_PARAM(aSocket != DX_INVALID_SOCKET);
    DX_ASSERT_PARAM(aBuf != DX_NULL);

	result = DX_VOS_SocketWaitForData(aSocket, timeout);
	if (result == DX_TIMEOUT_EXPIRED)
		DX_RETURN(DX_SUCCESS);

	if (result == DX_SUCCESS)
		result = DX_VOS_BaseSocketRecv(aSocket->m_Socket, aBuf, aBufLen, bytesRead);

    DxLogSocketPacket(aSocket, DX_FALSE, aBuf, *bytesRead);

    if (result != DX_SUCCESS && result != DX_VOS_SOCKET_CLOSED)
        DX_VOS_BaseLogSocketResult();

    if (result == DX_SUCCESS && bytesRead == &localBytesRead && *bytesRead != aBufLen)
        RETURN_NEW_ERROR(DX_TIMEOUT_EXPIRED);

    if (result != DX_SUCCESS)
    	RETURN_OLD_ERROR(result);
    DX_RETURN(DX_SUCCESS);
}

DxStatus DX_VOS_SocketRead(DxVosSocket aSocket, void *aBuf, DxUint32 aBufLen, DxUint32* bytesRead, DxUint32 timeout)
{
	DX_DECLARE(DxStatus, result, DX_SUCCESS);
	DxUint32 localBytesRead = 0;
	DxUint32 startTime = 0;
    DxUint32 currTime = 0;
	if (bytesRead == DX_NULL)
		bytesRead = &localBytesRead;
	
    *bytesRead = 0;

	if (aBufLen == 0)
		DX_RETURN(DX_SUCCESS);

    DX_ASSERT_PARAM(aSocket != DX_INVALID_SOCKET);
    DX_ASSERT_PARAM(aBuf != DX_NULL);
	startTime = DX_VOS_GetTickCount();
    currTime = startTime;
	do {
		DxUint32 dataToRead = aBufLen - *bytesRead;
        DxUint32 timeLeft = DX_INFINITE;
        if (timeout != DX_INFINITE)
            timeLeft = startTime + timeout - currTime;
        result = DX_VOS_SocketRecv(aSocket, (DxUint8*)aBuf + *bytesRead, dataToRead, &dataToRead, timeLeft);
		if (result != DX_SUCCESS)
        {
            if (result != DX_VOS_SOCKET_CLOSED)
                DX_VOS_BaseLogSocketResult();

            RETURN_OLD_ERROR(result);
        }

        *bytesRead += dataToRead;
		if (*bytesRead == aBufLen)
			DX_RETURN(DX_SUCCESS);
        currTime = DX_VOS_GetTickCount();
	} while (currTime - startTime < timeout);

	RETURN_NEW_ERROR(DX_TIMEOUT_EXPIRED);
}

DxStatus DX_VOS_SocketRecvFrom(DxVosSocket aSocket, void *aBuf, DxUint32 aBufLen, DxUint32* bytesRead, DxIpAddress* aFrom, DxUint32 timeout)
{
	DX_DECLARE(DxStatus, result, DX_SUCCESS);
    DxUint32 localBytesRead = 0;

    if (bytesRead == DX_NULL)
        bytesRead = &localBytesRead;

    *bytesRead = 0;
   
    if (aFrom != DX_NULL)
	    DX_VOS_MemSetZero(aFrom, sizeof(*aFrom));

    DX_ASSERT_PARAM(aSocket != DX_INVALID_SOCKET);
    DX_ASSERT_PARAM(aBuf != DX_NULL);

	result = DX_VOS_SocketWaitForData(aSocket, timeout);
	if (result == DX_TIMEOUT_EXPIRED)
		DX_RETURN(DX_SUCCESS);
    
	if (result == DX_SUCCESS)
		result = DX_VOS_BaseSocketRecvFrom(aSocket->m_Socket, aBuf, aBufLen, bytesRead, aFrom);

    DxLogSocketPacket(aSocket, DX_FALSE, aBuf, *bytesRead);

    if (result != DX_SUCCESS && result != DX_VOS_SOCKET_CLOSED)
    {
        DX_VOS_BaseLogSocketResult();
        RETURN_OLD_ERROR(result);
    }

    if (result == DX_SUCCESS && bytesRead == &localBytesRead && *bytesRead != aBufLen)
        RETURN_NEW_ERROR(DX_TIMEOUT_EXPIRED);

    if (result != DX_SUCCESS)
    	RETURN_OLD_ERROR(result);

    DX_RETURN(DX_SUCCESS);
}

DxStatus DX_VOS_SocketSend (DxVosSocket aSocket, const void *aBuf, DxUint32 aBufLen, DxUint32* bytesWritten)
{
    DX_DECLARE(DxStatus, result, DX_SUCCESS);
    DxUint32 localBytesWritten = 0;

    if (bytesWritten == DX_NULL)
        bytesWritten = &localBytesWritten;

    *bytesWritten = 0;

    DX_ASSERT_PARAM(aSocket != DX_INVALID_SOCKET);
    DX_ASSERT_PARAM(aBuf != DX_NULL);

	result = DX_VOS_BaseSocketSend(aSocket->m_Socket, aBuf, aBufLen, bytesWritten);
    if (result != DX_SUCCESS)
    {
        DX_VOS_BaseLogSocketResult();
        RETURN_OLD_ERROR(result);
    }

    DxLogSocketPacket(aSocket, DX_TRUE, aBuf, *bytesWritten);

    if (bytesWritten != &localBytesWritten && *bytesWritten != aBufLen)
        RETURN_NEW_ERROR(DX_VOS_SOCKET_ERROR);

    DX_RETURN(DX_SUCCESS);
}

DxStatus DX_VOS_SocketWrite (DxVosSocket aSocket, const void *aBuf, DxUint32 aBufLen, DxUint32* bytesWritten, DxUint32 timeout)
{
	DX_DECLARE(DxStatus, result, DX_SUCCESS);
	DxUint32 localBytesWritten = 0;
	DxUint32 startTime = 0;

	if (bytesWritten == DX_NULL)
		bytesWritten = &localBytesWritten;
	*bytesWritten = 0;

	if (aBufLen == 0)
		DX_RETURN(DX_SUCCESS);

    DX_ASSERT_PARAM(aSocket != DX_INVALID_SOCKET);
    DX_ASSERT_PARAM(aBuf != DX_NULL);

	startTime = DX_VOS_GetTickCount();
	do {
		DxUint32 dataToWrite = aBufLen - *bytesWritten;
		result = DX_VOS_SocketSend(aSocket, (DxUint8*)aBuf + *bytesWritten, dataToWrite, &dataToWrite);
		if (result != DX_SUCCESS)
			RETURN_OLD_ERROR(result);

        *bytesWritten += dataToWrite;
		if (*bytesWritten == aBufLen)
			DX_RETURN(DX_SUCCESS);
	} while (DX_VOS_GetTickCount() - startTime < timeout);
	
    RETURN_NEW_ERROR(DX_TIMEOUT_EXPIRED);
}

DxStatus DX_VOS_SocketSendTo (DxVosSocket aSocket, const void *aBuf, DxUint32 aBufLen, DxUint32* bytesWritten, const DxIpAddress* aTo)
{
    DX_DECLARE(DxStatus, result, DX_SUCCESS);
    DxUint32 localBytesWritten = 0;

    if (bytesWritten == DX_NULL)
        bytesWritten = &localBytesWritten;

    *bytesWritten = 0;

    DX_ASSERT_PARAM(aSocket != DX_INVALID_SOCKET);
    DX_ASSERT_PARAM(aBuf != DX_NULL);
    DX_ASSERT_PARAM(aTo != DX_NULL);

	result = DX_VOS_BaseSocketSendTo(aSocket->m_Socket, aBuf, aBufLen, bytesWritten, aTo);
    if (result != DX_SUCCESS)
    {
        DX_VOS_BaseLogSocketResult();
        RETURN_OLD_ERROR(result);
    }
    if (aSocket->m_File == DX_NULL)
        DxOpenSocketLogFile(aSocket, aTo);
    DxLogSocketPacket(aSocket, DX_TRUE, aBuf, *bytesWritten);

    if (bytesWritten != &localBytesWritten && *bytesWritten != aBufLen)
        RETURN_NEW_ERROR(DX_VOS_SOCKET_ERROR);
    
    DX_RETURN(DX_SUCCESS);
}


DxStatus DX_VOS_SocketBind (DxVosSocket aSocket, const DxIpAddress* aBindAddr)
{
    DX_DECLARE(DxStatus, result, DX_SUCCESS);
    DX_ASSERT_PARAM(aSocket != DX_INVALID_SOCKET);
    DX_ASSERT_PARAM(aBindAddr != DX_NULL);

	result = DX_VOS_BaseSocketBind(aSocket->m_Socket, aBindAddr);
    if (result != DX_SUCCESS)
    {
        DX_VOS_BaseLogSocketResult();
        RETURN_OLD_ERROR(result);
    }

    DxOpenSocketLogFile(aSocket, aBindAddr);

    DX_RETURN(DX_SUCCESS);
}

DxStatus DX_VOS_SocketAccept(DxVosSocket aSocket, DxVosSocket *aAcceptedSocket, DxIpAddress* aAcceptedSockAddr)
{
    DX_DECLARE(DxStatus, result, DX_SUCCESS);
	DxIpAddress acceptedSockAddr;
    void* acceptedSocket = DX_NULL;
    DX_ASSERT_PARAM(aSocket != DX_INVALID_SOCKET);
    DX_ASSERT_PARAM(aAcceptedSocket != DX_NULL);

	if (aAcceptedSockAddr == DX_NULL)
		aAcceptedSockAddr = &acceptedSockAddr;

    *aAcceptedSocket = DX_NULL;

	result = DX_VOS_BaseSocketAccept(aSocket->m_Socket, &acceptedSocket, aAcceptedSockAddr);
    if (result != DX_SUCCESS)
    {
        DX_VOS_BaseLogSocketResult();
        RETURN_OLD_ERROR(result);
    }

    *aAcceptedSocket = (DxVosSocket)DX_VOS_MemMalloc(sizeof(struct _DxVosSocket));
    RETURN_IF_ALLOC_FAILED(*aAcceptedSocket);
    (*aAcceptedSocket)->m_Socket = acceptedSocket;
    (*aAcceptedSocket)->m_File = DX_NULL;
    (*aAcceptedSocket)->m_Protocol = g_DxProtocolNames[DX_IPPROTO_TCP];
    DxOpenSocketLogFile(*aAcceptedSocket, aAcceptedSockAddr);

    DX_DBG_LOG_OBJECT_CREATION(DxVosSocket, *aAcceptedSocket);
    DX_RETURN(DX_SUCCESS);
}

void DX_VOS_SetIPAddress(DxIpAddress* IpAddress, DxUint8 b1, DxUint8 b2, DxUint8 b3, DxUint8 b4, DxUint16 port)
{
	IpAddress->m_Address[0] = b1;
	IpAddress->m_Address[1] = b2;
	IpAddress->m_Address[2] = b3;
	IpAddress->m_Address[3] = b4;
	IpAddress->m_Port = port;
}

#define DX_MAX_SOCKETS_IN_SELECT 64 
DxStatus DX_VOS_SocketSelect(DxVosSocket* socketsToCheck, DxUint32* numOfSockets, DxVosSocket* readableSockets, DxUint32 aTimeout)
{
    DX_DECLARE(DxStatus, result, DX_SUCCESS);
    DxUint32 i = 0;
    DxUint32 j = 0;
    DxUint32 orgNumOfSockets = *numOfSockets;

    void* inSocketArray[DX_MAX_SOCKETS_IN_SELECT];
    void* outSocketArray[DX_MAX_SOCKETS_IN_SELECT];

    DX_ASSERT_PARAM(socketsToCheck != DX_NULL);
    DX_ASSERT_PARAM(numOfSockets != DX_NULL);
    DX_ASSERT_PARAM(readableSockets != DX_NULL);
    
    for (i = 0; i < *numOfSockets; i++)
        inSocketArray[i] = socketsToCheck[i]->m_Socket;

    result = DX_VOS_BaseSocketSelect(inSocketArray, numOfSockets, outSocketArray, aTimeout);
    if (result == DX_TIMEOUT_EXPIRED)
        DX_RETURN(DX_TIMEOUT_EXPIRED);
    
    if (result != DX_SUCCESS)
    {
        DX_VOS_BaseLogSocketResult();
        RETURN_OLD_ERROR(result);
    }

    for (i = 0; i < *numOfSockets; i++)
    {
        for (j = 0; j < orgNumOfSockets; j++)
        {
            if (outSocketArray[i] == socketsToCheck[j]->m_Socket)
            {
                readableSockets[i] = socketsToCheck[j];
                break;
            }
        }
    }

    DX_RETURN(DX_SUCCESS);
}

DxStatus DX_VOS_BuildIpAddress(DxIpAddress* ipAddress, const DxChar* serverName, DxUint16 port)
{
    DX_DECLARE(DxStatus, result, DX_SUCCESS);

    result = DX_VOS_GetHostByName(serverName, ipAddress);
    if (result != DX_SUCCESS)
        RETURN_OLD_ERROR(result);
    ipAddress->m_Port = port;

    DX_RETURN(DX_SUCCESS);
}
