#define DX_DBG_MODULE DX_DBG_MODULE_VOS

#include "VOS_API/DX_VOS_Http.h"
#include "VOS_API/DX_VOS_Socket.h"
#include "VOS_API/DX_VOS_Stdio.h"
#include "VOS_API/DX_VOS_String.h"
#include "VOS_API/DX_VOS_Utils.h"
#include "VOS_API/DX_VOS_Mem.h"

#define DX_HTTP_CACHE_SIZE  1024
typedef enum {
    DX_REQUEST_SENT,
    DX_HEADER_END_CHAR1_RECOGNIZED,
    DX_HEADER_END_CHAR2_RECOGNIZED,
    DX_HEADER_END_CHAR3_RECOGNIZED,
    DX_HEADER_FINISHED,
} DxHttpState;

struct _DxVosHttp
{
    DxVosSocket m_Socket;
    DxUint8 m_HttpCache[DX_HTTP_CACHE_SIZE];
    DxUint32 m_CacheSize;
    DxUint32 m_CacheOffset;
    DxHttpState m_State;
};

static DxStatus DxParseUrl(const DxChar *url, DxChar* serverName, DxUint32 serverNameSize, DxUint32* serverPort, DxChar* fileName, DxUint32 fileNameSize)
{
    DX_DECLARE(DxStatus, result, DX_SUCCESS);
    DxChar	urlBuff[DX_VOS_MAX_URL_SIZE];
    DxChar  *pc;
    DxUint32 prefixSize = sizeof("http://") - 1; // Not including the terminating NULL

    *serverName = 0;
    *serverPort = 80;
    *fileName = DX_NULL;

    if (DX_VOS_StrNCmp("http://", url, prefixSize) != 0)
        RETURN_NEW_ERROR(DX_INVALID_URL);

    url += prefixSize;

    result = DX_VOS_StrNCopy(urlBuff, sizeof(urlBuff), url);
    if (result != DX_SUCCESS)
        RETURN_NEW_ERROR(DX_INVALID_URL);

    for (pc = urlBuff; (*pc != 0 && *pc != ':' && *pc != '/'); pc++)
    {
        // No Body
    }

    if (*pc == ':')
    {
        DxUint32 val = 0;
        DxChar* portPtr = DX_NULL;
        *pc++ = 0;
        portPtr = pc;
        for (; (*pc != 0 && *pc != '/'); pc++)
        {
            // No Body
        }
        if (*pc != 0)
            *pc++ = 0;

        result = DX_VOS_StrToUnsigned(portPtr, &val, 10);
        if (result != DX_SUCCESS)
            RETURN_NEW_ERROR(DX_INVALID_URL);
        *serverPort = (DxUint16)val;
    } else if (*pc == '/')
        *pc++ = 0;

    result = DX_VOS_StrNCopy(serverName, serverNameSize, urlBuff);
    if (result != DX_SUCCESS)
        RETURN_OLD_ERROR(result);

    result = DX_VOS_StrNCopy(fileName, fileNameSize, url + (pc - urlBuff));
    if (result != DX_SUCCESS)
    	RETURN_OLD_ERROR(result);

    result = DX_VOS_ReplaceAll(fileName, "&amp;", "&");
    if (result != DX_SUCCESS)
    	RETURN_OLD_ERROR(result);

    DX_RETURN(DX_SUCCESS);
}

#define DX_MAX_HTTP_HEADER_LINE_LENGTH  512
#define DX_HTTP_DEFAULT_PORT            80
static DxStatus HttpQuery(DxVosHttp httpHandle, const DxChar *command, const DxChar* serverName, DxUint32 serverPort,
                   const DxChar* fileName, const DxChar* requestContent, const DxChar* additionalHeaderLines)
{

    DX_DECLARE(DxStatus, result, DX_SUCCESS);
    DxChar*   header = DX_NULL;
    DxUint32 headerSize = 0;
    DxUint32 length = 0;
    DxIpAddress ipAddress;

    DX_VERIFY_PARAM(serverPort == (DxUint16)serverPort);
    result = DX_VOS_GetHostByName(serverName, &ipAddress);
    if (result != DX_SUCCESS)
        RETURN_OLD_ERROR(result);
    
    if (serverPort == 0)
        serverPort = DX_HTTP_DEFAULT_PORT;

    ipAddress.m_Port = (DxUint16)serverPort;
    
    result = DX_VOS_SocketCreate(&httpHandle->m_Socket, DX_AF_INET, DX_SOCK_STREAM, DX_IPPROTO_TCP);
    if (result != DX_SUCCESS)
        RETURN_OLD_ERROR(result);

    result = DX_VOS_SocketConnect(httpHandle->m_Socket, &ipAddress);
    if (result != DX_SUCCESS)
        RETURN_OLD_ERROR(result);

    if (additionalHeaderLines == DX_NULL)
        additionalHeaderLines = "";

    length = DX_VOS_StrLen(requestContent);

    headerSize = DX_MAX_HTTP_HEADER_LINE_LENGTH + DX_VOS_StrLen(additionalHeaderLines);
    header = (DxChar*)DX_VOS_MemMalloc(headerSize);
    GOTO_END_IF_ALLOC_FAILED(header);

    result = DX_VOS_SPrintf(header, headerSize, "%s /%s HTTP/1.0\r\nHost: %s\r\nContent-Length: %d\r\n%s\r\n", 
        command, fileName, serverName, length, additionalHeaderLines);
    if (result != DX_SUCCESS)
        GOTO_END_WITH_OLD_ERROR(result);

    result = DX_VOS_SocketSend(httpHandle->m_Socket, header, DX_VOS_StrLen(header), DX_NULL);
    if (result != DX_SUCCESS)
        GOTO_END_WITH_OLD_ERROR(result);

    if (requestContent != DX_NULL)
    {
        result = DX_VOS_SocketSend(httpHandle->m_Socket, requestContent, length, DX_NULL);
        if (result != DX_SUCCESS)
            GOTO_END_WITH_OLD_ERROR(result);
    }
end:
    DX_VOS_MemFree(header);
    DX_RETURN(result);
}

DxStatus DX_VOS_HttpSendRequest(DxVosHttp* httpHandlePtr, DxHttpMethod httpMethod, const DxChar* url,
                                const DxChar* requestContent, const DxChar* additionalHeaderLines)
{
    DX_DECLARE(DxStatus, result, DX_SUCCESS);
    DxChar   serverName[DX_VOS_MAX_URL_SIZE];
    DxChar   fileName[DX_VOS_MAX_URL_SIZE];
    DxUint32 serverPort = 0;
    DxChar* command = DX_NULL;
    DX_ASSERT_PARAM(httpHandlePtr != DX_NULL);

    switch (httpMethod)
    {
    case DX_HTTP_GET:   command = "GET"; break;
    case DX_HTTP_POST:  command = "POST"; break;
    default:
        RETURN_NEW_ERROR(DX_BAD_ARGUMENTS);
    }
    
    result = DxParseUrl(url, serverName, sizeof(serverName), &serverPort, fileName, sizeof(fileName));
    if (result != DX_SUCCESS)
    	RETURN_OLD_ERROR(result);

    *httpHandlePtr = (DxVosHttp) DX_VOS_MemMalloc(sizeof(struct _DxVosHttp));
    RETURN_IF_ALLOC_FAILED(*httpHandlePtr);

    DX_VOS_MemSetZero(*httpHandlePtr, sizeof(struct _DxVosHttp));

    result = HttpQuery(*httpHandlePtr, command, serverName, serverPort, fileName, requestContent, additionalHeaderLines);
    if (result != DX_SUCCESS)
    {
        DX_VOS_HttpClose(*httpHandlePtr);
        *httpHandlePtr = DX_NULL;
    	RETURN_OLD_ERROR(result);
    }

    DX_RETURN(DX_SUCCESS);
}

static DxStatus DxHttpFillHttpCache(DxVosHttp httpHandle, DxUint32 timeout)
{
    DX_DECLARE(DxStatus, result, DX_SUCCESS);
    httpHandle->m_CacheOffset = 0;
    
    result = DX_VOS_SocketRecv(httpHandle->m_Socket, httpHandle->m_HttpCache, sizeof(httpHandle->m_HttpCache), 
        &httpHandle->m_CacheSize, timeout);
    if (result != DX_SUCCESS)
        RETURN_OLD_ERROR(result);

    DX_RETURN(DX_SUCCESS);

}

static void DxHttpAdvanceState(DxVosHttp httpHandle, DxChar ch)
{
    if (ch == '\r')
    {
        if (httpHandle->m_State == DX_REQUEST_SENT)
            httpHandle->m_State = DX_HEADER_END_CHAR1_RECOGNIZED;
        else if (httpHandle->m_State == DX_HEADER_END_CHAR2_RECOGNIZED)
            httpHandle->m_State = DX_HEADER_END_CHAR3_RECOGNIZED;
        else
            httpHandle->m_State = DX_REQUEST_SENT;
    }
    else if (ch == '\n')
    {
        if (httpHandle->m_State == DX_HEADER_END_CHAR1_RECOGNIZED)
            httpHandle->m_State = DX_HEADER_END_CHAR2_RECOGNIZED;
        else if (httpHandle->m_State == DX_HEADER_END_CHAR3_RECOGNIZED)
            httpHandle->m_State = DX_HEADER_FINISHED;
        else
            httpHandle->m_State = DX_REQUEST_SENT;
    }
    else 
        httpHandle->m_State = DX_REQUEST_SENT;

}
DxStatus DX_VOS_HttpReadResponseHeader(DxVosHttp httpHandle, DxChar* outBuffer, DxUint32 buffSizeInBytes, DxUint32 timeout)
{
    DX_DECLARE(DxStatus, result, DX_SUCCESS);
    DxChar* endOfoutBuff = outBuffer + buffSizeInBytes - 1;
    
    *outBuffer = 0;
    
    while (outBuffer < endOfoutBuff && httpHandle->m_State != DX_HEADER_FINISHED)
    {
        if (httpHandle->m_CacheOffset == httpHandle->m_CacheSize)
        {
            if (result != DX_SUCCESS)
                RETURN_OLD_ERROR(result);
            result = DxHttpFillHttpCache(httpHandle, timeout);
            continue;
        }

        DxHttpAdvanceState(httpHandle, httpHandle->m_HttpCache[httpHandle->m_CacheOffset]);
        *outBuffer++ = httpHandle->m_HttpCache[httpHandle->m_CacheOffset++];
        *outBuffer = 0;
    }
    if (httpHandle->m_State != DX_HEADER_FINISHED)
        RETURN_NEW_ERROR(DX_BUFFER_IS_NOT_BIG_ENOUGH);

    DX_RETURN(DX_SUCCESS);
}

DxStatus DX_VOS_HttpReadResponseData(DxVosHttp httpHandle, void* outBuffer, DxUint32 buffSizeInBytes, DxUint32* bytesRead, DxUint32 timeout)
{
    DX_DECLARE(DxStatus, result, DX_SUCCESS);
    DxUint8* outPtr = (DxUint8*)outBuffer;
    DxUint32 localBytesRead = 0;

    DX_ASSERT_PARAM(bytesRead != DX_NULL);
    DX_VERIFY_PARAM(httpHandle->m_State == DX_HEADER_FINISHED);

    *bytesRead = 0;
    if (httpHandle->m_CacheOffset < httpHandle->m_CacheSize)
    {
        DxUint32 bytesToRead = httpHandle->m_CacheSize - httpHandle->m_CacheOffset;
        result = DX_VOS_MemCpy(outPtr, buffSizeInBytes, httpHandle->m_HttpCache + httpHandle->m_CacheOffset, bytesToRead);
        if (result == DX_BUFFER_IS_NOT_BIG_ENOUGH)
        {
            httpHandle->m_CacheOffset += buffSizeInBytes;
            RETURN_OLD_ERROR(result);
        }
        if (result != DX_SUCCESS)
        	RETURN_OLD_ERROR(result);
        *bytesRead += bytesToRead;
        outPtr += bytesToRead;
        buffSizeInBytes -= bytesToRead;
    }
    httpHandle->m_CacheOffset = 0;
    httpHandle->m_CacheSize = 0;
    
    result = DX_VOS_SocketRead(httpHandle->m_Socket, outPtr, buffSizeInBytes, &localBytesRead, timeout);
    *bytesRead += localBytesRead;
    if (result == DX_SUCCESS)
        RETURN_NEW_ERROR(DX_BUFFER_IS_NOT_BIG_ENOUGH);

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

    DX_RETURN(DX_SUCCESS);
}

void DX_VOS_HttpClose(DxVosHttp httpHandle)
{
    if (httpHandle == DX_NULL)
        return;
    DX_VOS_SocketClose(&httpHandle->m_Socket);
    DX_VOS_MemFree(httpHandle);
}
