#include "VOS_API/DX_VOS_SocketMsgInterface.h"
#include "VOS_API/DX_VOS_Mem.h"
#include "VOS_API/DX_VOS_Stdio.h"
#include "VOS_API/DX_VOS_Time.h"
#include "VOS_API/DX_VOS_Thread.h"
#define DX_MIN_USABLE_PORT_NUMBER	1024
#define DX_PACKET_TIMEOUT			10000

static DxUint16 IdToPort(DxUint32 id)
{
	DxUint16 port = (DxUint16)id;
	if (port < DX_MIN_USABLE_PORT_NUMBER)
		port += DX_MIN_USABLE_PORT_NUMBER;
	DX_RETURN(port);
}

DxStatus SOCKET_MSGIF_Stop(DxVosMsgIFDriver* driver);

static DxStatus DxReadMessageFromSocket(DxVosSocket socket, DxVosMsg* msg, DxUint32 maxBuffSize)
{
	DX_DECLARE(DxStatus, result, DX_SUCCESS);

	result = DX_VOS_SocketRead(socket,&msg->m_Header.m_OpCode,sizeof(msg->m_Header.m_OpCode), DX_NULL, DX_PACKET_TIMEOUT);
	if (result != DX_SUCCESS)
		RETURN_OLD_ERROR(result);

	result = DX_VOS_SocketRead(socket,&msg->m_Header.m_BuffSize,sizeof(msg->m_Header.m_BuffSize), DX_NULL, DX_PACKET_TIMEOUT);
	if (result != DX_SUCCESS)
		RETURN_OLD_ERROR(result);

	if (msg->m_Header.m_BuffSize > maxBuffSize)
		RETURN_NEW_ERROR(DX_BUFFER_IS_NOT_BIG_ENOUGH);

	result = DX_VOS_SocketRead(socket,&(msg->m_Buffer),msg->m_Header.m_BuffSize, DX_NULL, DX_PACKET_TIMEOUT);
	if (result != DX_SUCCESS)
		RETURN_OLD_ERROR(result);

	DX_RETURN(DX_SUCCESS);
}

static void DxCloseOutgoingSocket(DxVosSocketMsgIFDriver* socketDriver)
{
	DX_VOS_SocketClose(&socketDriver->m_OutgoingSocket);
	socketDriver->m_RemoteIPAddress.m_Port = 0;
}

static DxStatus SOCKET_MSGIF_StartServer(DxVosMsgIFDriver* driver, DxUint32 serverId)
{
	DxVosSocketMsgIFDriver* socketDriver = (DxVosSocketMsgIFDriver*)driver;
	DX_DECLARE(DxStatus, result, DX_SUCCESS);
	DxUint i = 0;

    DX_ASSERT_PARAM(driver != DX_NULL);
    DX_ASSERT_PARAM(socketDriver->m_MaxIncoming != 0);

	socketDriver->m_ListenerSocket = DX_INVALID_SOCKET;

	result  = DX_VOS_SocketCreate(&(socketDriver->m_ListenerSocket), DX_AF_INET, DX_SOCK_STREAM, DX_IPPROTO_TCP);
	if (result != DX_SUCCESS)
		RETURN_OLD_ERROR(result);

	socketDriver->m_LocalIPAddress.m_Port = IdToPort(serverId);

	result = DX_VOS_SocketBind(socketDriver->m_ListenerSocket, &socketDriver->m_LocalIPAddress);
	if (result != DX_SUCCESS)
        GOTO_END_WITH_VAR_STATUS(result);

	result = DX_VOS_SocketListen(socketDriver->m_ListenerSocket, socketDriver->m_MaxIncoming);
	if (result != DX_SUCCESS)
        GOTO_END_WITH_VAR_STATUS(result);

	socketDriver->m_AcceptedSockets = (DxVosSocket*) DX_VOS_MemMalloc(sizeof(DxVosSocket) * socketDriver->m_MaxIncoming);
    GOTO_END_IF_ALLOC_FAILED(socketDriver->m_AcceptedSockets);

	for (i = 0; i < socketDriver->m_MaxIncoming; i++)
		socketDriver->m_AcceptedSockets[i] = DX_INVALID_SOCKET;

	DX_VOS_Printf("\r\nWaiting for connection...");
end:
	if (result != DX_SUCCESS)
		SOCKET_MSGIF_Stop(driver);
	RETURN_OLD_ERROR(result);
}


DxStatus SOCKET_MSGIF_Stop(DxVosMsgIFDriver* driver)
{
	DxUint i = 0;
	DxVosSocketMsgIFDriver* socketDriver = (DxVosSocketMsgIFDriver*)driver;

    DX_ASSERT_PARAM(driver != DX_NULL);
    if (socketDriver->m_AcceptedSockets != DX_NULL)
    {
	    for (i = 0; i < socketDriver->m_MaxIncoming; i++)
    		DX_VOS_SocketClose(&socketDriver->m_AcceptedSockets[i]);
        DX_VOS_MemFree(socketDriver->m_AcceptedSockets);
        socketDriver->m_AcceptedSockets = DX_NULL;
    }

	DX_VOS_SocketClose(&socketDriver->m_ListenerSocket);

	DxCloseOutgoingSocket(socketDriver);
	DX_RETURN(DX_SUCCESS);
}

static DxStatus SOCKET_MSGIF_SendRequest(DxVosMsgIFDriver* driver, DxUint32 destId, const DxVosMsg* msg, DxUint32 timeout)
{
	DX_DECLARE(DxStatus, result, DX_SUCCESS);
	DxVosSocketMsgIFDriver* socketDriver = (DxVosSocketMsgIFDriver*)driver;
	DxUint16 destPort = 0;

    DX_ASSERT_PARAM(driver != DX_NULL);
    DX_ASSERT_PARAM(msg != DX_NULL);

	destPort = IdToPort(destId);
	if (destPort != socketDriver->m_RemoteIPAddress.m_Port && socketDriver->m_RemoteIPAddress.m_Port != 0)
	{
		DX_VOS_SocketClose(&socketDriver->m_OutgoingSocket);
		socketDriver->m_RemoteIPAddress.m_Port = 0; 
	}
	if (socketDriver->m_OutgoingSocket == DX_INVALID_SOCKET)
	{
		socketDriver->m_RemoteIPAddress.m_Port = IdToPort(destId);
		result  = DX_VOS_SocketCreate(&(socketDriver->m_OutgoingSocket), DX_AF_INET, DX_SOCK_STREAM, DX_IPPROTO_TCP);
		if (result != DX_SUCCESS)
			RETURN_OLD_ERROR(result);

		result = DX_VOS_SocketConnect(socketDriver->m_OutgoingSocket, &socketDriver->m_RemoteIPAddress);
		if (result != DX_SUCCESS)
		{
			DxCloseOutgoingSocket(socketDriver);
			RETURN_OLD_ERROR(result);
		}
	}
	result = DX_VOS_SocketWrite(socketDriver->m_OutgoingSocket, msg,
		sizeof(msg->m_Header.m_OpCode) + sizeof(msg->m_Header.m_BuffSize) + msg->m_Header.m_BuffSize,
		DX_NULL, timeout);

	if (result != DX_SUCCESS)
		DxCloseOutgoingSocket(socketDriver);

	RETURN_OLD_ERROR(result);
}

static DxStatus SOCKET_MSGIF_WaitForRequest(DxVosMsgIFDriver* driver, DxUint32* sourceId, DxUint32 timeout)
{
	DxUint i = 0;
	DX_DECLARE(DxStatus, result, DX_SUCCESS);
	DxVosSocketMsgIFDriver* socketDriver = (DxVosSocketMsgIFDriver*)driver;
	DxUint32 startTime = DX_VOS_GetTickCount();

    DX_ASSERT_PARAM(driver != DX_NULL);
    DX_ASSERT_PARAM(sourceId != DX_NULL);
    DX_ASSERT_PARAM(socketDriver->m_ListenerSocket != DX_INVALID_SOCKET);

	do {
		result = DX_VOS_SocketWaitForData(socketDriver->m_ListenerSocket, 0);
		if (result == DX_SUCCESS)
		{
			DxVosSocket tempSocket = DX_INVALID_SOCKET;
			result = DX_VOS_SocketAccept(socketDriver->m_ListenerSocket,&tempSocket, DX_NULL);
			if (result == DX_SUCCESS)
			{
				for (i = 0; i < socketDriver->m_MaxIncoming; i++)
				{
					if (socketDriver->m_AcceptedSockets[i] == DX_INVALID_SOCKET)
						break;
				}
				if (i == socketDriver->m_MaxIncoming)
					DX_VOS_SocketClose(&tempSocket);
				else
					socketDriver->m_AcceptedSockets[i] = tempSocket;
			}
		}
		for (i = 0; i < socketDriver->m_MaxIncoming; i++)
		{
			if (socketDriver->m_AcceptedSockets[i] == DX_INVALID_SOCKET)
				continue;
			result = DX_VOS_SocketWaitForData(socketDriver->m_AcceptedSockets[i], 0);
			if (result == DX_SUCCESS)
			{
				*sourceId = i;
				DX_RETURN(DX_SUCCESS);
			}
			if (result != DX_TIMEOUT_EXPIRED)
				DX_VOS_SocketClose(&socketDriver->m_AcceptedSockets[i]);
		}
		if (timeout != 0)
			DX_VOS_Sleep(100);
	} while (DX_VOS_GetTickCount() - startTime < timeout);
	RETURN_CONST_WARNING(DX_TIMEOUT_EXPIRED);
}

static DxStatus SOCKET_MSGIF_SendResponse(DxVosMsgIFDriver* driver, DxUint32 destId, const DxVosMsg* msg, DxUint32 timeout)
{
	DX_DECLARE(DxStatus, result, DX_SUCCESS);
	DxVosSocketMsgIFDriver* socketDriver = (DxVosSocketMsgIFDriver*)driver;

    DX_ASSERT_PARAM(driver != DX_NULL);
    DX_ASSERT_PARAM(msg != DX_NULL);
    DX_ASSERT_PARAM(socketDriver->m_AcceptedSockets != DX_NULL);
    DX_ASSERT_PARAM(destId < socketDriver->m_MaxIncoming);


	result = DX_VOS_SocketWrite(socketDriver->m_AcceptedSockets[destId], msg,
		sizeof(msg->m_Header.m_OpCode) + sizeof(msg->m_Header.m_BuffSize) + msg->m_Header.m_BuffSize,
		DX_NULL, timeout);

	if (result != DX_SUCCESS)
		DX_VOS_SocketClose(&socketDriver->m_AcceptedSockets[destId]);

    RETURN_OLD_ERROR(result);
}


static DxStatus SOCKET_MSGIF_ReadRequest(DxVosMsgIFDriver* driver, DxVosMsg* msg, DxUint32 maxBuffSize, DxUint32* sourceId, DxUint32 timeout)
{
	DxVosSocketMsgIFDriver* socketDriver = (DxVosSocketMsgIFDriver*)driver;
    DX_DECLARE(DxStatus, result, DX_SUCCESS);

    DX_ASSERT_PARAM(driver != DX_NULL);

    result = SOCKET_MSGIF_WaitForRequest(driver, sourceId, timeout);
	if (result != DX_SUCCESS)
        RETURN_OLD_ERROR(result);

	result = DxReadMessageFromSocket(socketDriver->m_AcceptedSockets[*sourceId], 
		msg, maxBuffSize);
	if (result != DX_SUCCESS)
		DX_VOS_SocketClose(&socketDriver->m_AcceptedSockets[*sourceId]);

    RETURN_OLD_ERROR(result);
}

static DxStatus SOCKET_MSGIF_WaitForResponse(DxVosMsgIFDriver* driver, DxUint32 sourceId, DxUint32 timeout)
{
	DxVosSocketMsgIFDriver* socketDriver = (DxVosSocketMsgIFDriver*)driver;
	DX_DECLARE(DxStatus, result, DX_SUCCESS);

    DX_ASSERT_PARAM(driver != DX_NULL);

	DX_VERIFY_PARAM(IdToPort(sourceId) == socketDriver->m_RemoteIPAddress.m_Port);

	result = DX_VOS_SocketWaitForData(socketDriver->m_OutgoingSocket, timeout);
	if (result != DX_SUCCESS && result != DX_TIMEOUT_EXPIRED)
	{
		DX_VOS_SocketClose(&socketDriver->m_OutgoingSocket);
		socketDriver->m_RemoteIPAddress.m_Port = 0;
	}

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

    DX_RETURN(DX_SUCCESS);
}

static DxStatus SOCKET_MSGIF_ReadResponse(DxVosMsgIFDriver* driver, DxUint32 sourceId, DxVosMsg* msg, DxUint32 maxBuffSize, DxUint32 timeout)
{
	DX_DECLARE(DxStatus, result, DX_SUCCESS);
	DxVosSocketMsgIFDriver* socketDriver = (DxVosSocketMsgIFDriver*)driver;

    DX_ASSERT_PARAM(driver != DX_NULL);
    DX_ASSERT_PARAM(msg != DX_NULL);

	result = SOCKET_MSGIF_WaitForResponse(driver, sourceId, timeout);
	if (result != DX_SUCCESS)
        RETURN_OLD_ERROR(result);
	
	result = DxReadMessageFromSocket(socketDriver->m_OutgoingSocket, msg, maxBuffSize);
	if (result != DX_SUCCESS)
    {
		DxCloseOutgoingSocket(socketDriver);
    	RETURN_OLD_ERROR(result);
    }

    DX_RETURN(DX_SUCCESS);
}

DxStatus DX_VOS_SOCKET_MSGIF_Init(DxVosSocketMsgIFDriver* driver, const DxIpAddress* localIpAddress, 
								  const DxIpAddress* remoteIpAddress, DxUint maxIncoming)
{
    DX_ASSERT_PARAM(driver != DX_NULL);

	/* Virtual Table initialization */
	/* In this case requests & responses are treated the same */
	driver->m_Driver.StartServer = SOCKET_MSGIF_StartServer;
	driver->m_Driver.Stop = SOCKET_MSGIF_Stop;
	driver->m_Driver.ReadRequest = SOCKET_MSGIF_ReadRequest;
	driver->m_Driver.SendRequest = SOCKET_MSGIF_SendRequest;
	driver->m_Driver.WaitForRequest = SOCKET_MSGIF_WaitForRequest;
	driver->m_Driver.ReadResponse = SOCKET_MSGIF_ReadResponse;
	driver->m_Driver.SendResponse = SOCKET_MSGIF_SendResponse;
	driver->m_Driver.WaitForResponse = SOCKET_MSGIF_WaitForResponse;

	driver->m_ListenerSocket = DX_INVALID_SOCKET;
	driver->m_OutgoingSocket = DX_INVALID_SOCKET;
	driver->m_MaxIncoming = maxIncoming;
	driver->m_AcceptedSockets = DX_NULL;
	/* Data member initialization */
	if (localIpAddress != DX_NULL)
	{
		DX_VOS_FastMemCpy(&driver->m_LocalIPAddress, localIpAddress, sizeof(DxIpAddress));
		driver->m_LocalIPAddress.m_Port = 0;
	} else {
		DX_VOS_SetIPAddress(&driver->m_LocalIPAddress, 127, 0, 0, 1, 0);
	}
	if (remoteIpAddress != DX_NULL)
	{
		DX_VOS_FastMemCpy(&driver->m_RemoteIPAddress, remoteIpAddress, sizeof(DxIpAddress));
		driver->m_RemoteIPAddress.m_Port = 0;
	} else {
		DX_VOS_SetIPAddress(&driver->m_RemoteIPAddress, 127, 0, 0, 1, 0);
	}
	DX_RETURN(DX_SUCCESS);
}
