#include "Win32WebServer.h"

#include "Win32TempFile.h"
#include "Win32Buffer.h"
#include "Win32WebResponse.h"
#include "Win32WebRequest.h"

#include "AbstractWebApp.h"
#include "HttpRequest.h"
#include "HttpResponse.h"
#include "FileLogger.h"
#include "UnicodeUtils.h"

Win32WebServer::Win32WebServer(AbstractWebApp* webApp)
	:   mListenUrl("http://localhost:49153/"),
        mWebApp(webApp)
{

}

Win32WebServer::~Win32WebServer()
{
	clear();
}

void Win32WebServer::clear()
{
	// Remove url
	::HttpRemoveUrl(mWorkingQueue, UnicodeUtils::utf8ToUtf16WString(mListenUrl).c_str());

	// Close the Request Queue handle.
	if (mWorkingQueue)
	{
		::CloseHandle(mWorkingQueue);
	}

	// Call HttpTerminate.
	::HttpTerminate(HTTP_INITIALIZE_SERVER, nullptr);
}

void Win32WebServer::initialize()
{
	HTTPAPI_VERSION HttpApiVersion = HTTPAPI_VERSION_1;

    auto retCode = ::HttpInitialize(HttpApiVersion, HTTP_INITIALIZE_SERVER, nullptr);
	if (FAILED(retCode))
	{
		MLOG_ERROR("Failed to initialize win32 http server.");
		return;
	}

	retCode = ::HttpCreateHttpHandle(&mWorkingQueue, 0);
	if (FAILED(retCode))
	{
		MLOG_ERROR("Failed to create request queue handle.");
		return;
	}

	retCode = ::HttpAddUrl(mWorkingQueue, UnicodeUtils::utf8ToUtf16WString(mListenUrl).c_str(), nullptr);
	if (FAILED(retCode))
	{
		MLOG_ERROR("Failed to register queue to url: " << mListenUrl);
		return;
	}
}

bool Win32WebServer::onPostRequest(Win32WebRequest& request)
{
    if (request.getHandle()->Flags & HTTP_REQUEST_FLAG_MORE_ENTITY_BODY_EXISTS)
    {
        if (!request.startReceiveEntity())
        {
            return false;
        }

        ULONG bytes_read = 0;
        do
        {
            bytes_read = 0;
            auto result = ::HttpReceiveRequestEntityBody(mWorkingQueue, request.getHandle()->RequestId, 0, request.getEntityBuffer()->getBuffer(), 
                request.getEntityBufferSize(), &bytes_read, nullptr);

            if (result == NO_ERROR || result == ERROR_HANDLE_EOF)
            {
                request.flushEntityBuffer(bytes_read);
                if (result == ERROR_HANDLE_EOF)
                {
                    return true;
                }
            }
            else
            {
                MLOG_ERROR("HttpReceiveRequestEntityBody failed with: " << result);
                return false;
            }
        } while (true);
    }
    return true;

}

bool Win32WebServer::onRequest(Win32WebRequest& request)
{
    if (request.getHandle()->Verb == HttpVerbPOST)
    {
        if (!onPostRequest(request))
        {
            return false;
        }
    }

    const auto app_request = request.getRequest();
    const auto response = mWebApp->onHttpRequest(app_request);
    if (app_request.getVerb() == HttpRequest::Verb::POST)
    {
        if (!sendHttpPostResponse(request, response))
        {
            return false;
        }
    }
    else
    {
        if (!sendHttpResponse(request, response))
        {
            return false;
        }
    }

    /*
    case HttpVerbGET:
        MLOG_INFO("Got a GET request for: " << url);
        result = sendHttpResponse(request, 200, "OK", "Hey! You hit the server \r\n");
        break;
    case HttpVerbPOST:
        MLOG_INFO("Got a POST request for: " << url);
        result = sendHttpPostResponse(request);
        break;
    default:
        MLOG_INFO("Got an unknown request for: " << url);
        result = sendHttpResponse(request, 503, "Not Implemented");
        break;
    */

    return true;
}

bool Win32WebServer::sendHttpResponse(const Win32WebRequest& request, const HttpResponse& appResponse)
{
    Win32WebResponse response(appResponse);

    DWORD bytesSent;
    const auto result = ::HttpSendHttpResponse(mWorkingQueue, request.getHandle()->RequestId, 0, &response.getResponse(), nullptr, &bytesSent, nullptr, 0, nullptr, nullptr);
    if (result != NO_ERROR)
    {
        MLOG_ERROR("Http response failed with error: " << result);
    }
    return result == NO_ERROR;
}

bool Win32WebServer::sendHttpPostResponse(const Win32WebRequest& request, const HttpResponse& appResponse)
{
    DWORD result{ 0 };
    if (request.getEntityBuffer())
    {
        Win32WebResponse response(appResponse);
        response.addRequestContentLength(request);

        DWORD bytesSent;
        result = ::HttpSendHttpResponse(mWorkingQueue, request.getHandle()->RequestId, HTTP_SEND_RESPONSE_FLAG_MORE_DATA, &(response.getResponse()), NULL, &bytesSent, NULL, 0, NULL, NULL);
        if (result != NO_ERROR)
        {
            MLOG_ERROR("HttpSendHttpResponse failed with: " << result);
            return false;
        }

        // Send entity body from a file handle.
        HTTP_DATA_CHUNK dataChunk;
        dataChunk.DataChunkType = HttpDataChunkFromFileHandle;
        dataChunk.FromFileHandle.ByteRange.StartingOffset.QuadPart = 0;
        dataChunk.FromFileHandle.ByteRange.Length.QuadPart = HTTP_BYTE_RANGE_TO_EOF;
        dataChunk.FromFileHandle.FileHandle = request.getTempFile()->getHandle();

        result = ::HttpSendResponseEntityBody(mWorkingQueue, request.getHandle()->RequestId, 0, 1, &dataChunk, NULL, NULL, 0, NULL, NULL);
        if (result != NO_ERROR)
        {
            MLOG_ERROR("HttpSendResponseEntityBody failed" << result);
            return false;
        }
    }
    else
    {
        result = sendHttpResponse(request, appResponse);
    }
    return result == NO_ERROR;;
}

void Win32WebServer::run()
{
    MLOG_INFO("Running web server on: " << mListenUrl);

    Win32WebRequest request;
    if (!request.isValid())
    {
        MLOG_ERROR("Failed to allocate http request buffer");
        return;
    }

    HTTP_REQUEST_ID request_id;
    HTTP_SET_NULL_ID(&request_id);
    while(true)
    {
        request.zeroBufferMemory();

        DWORD bytes_read;
        auto result = ::HttpReceiveHttpRequest(mWorkingQueue, request_id, 0, request.getHandle(), request.getBufferSize(), &bytes_read, nullptr);
        if (NO_ERROR == result)
        {
            if (!onRequest(request))
            {
                break;
            }
            HTTP_SET_NULL_ID(&request_id);
        }
        else if (result == ERROR_MORE_DATA)
        {
            // The input buffer was too small to hold the request headers. Increase the buffer size and call the API again. 
            request_id = request.getHandle()->RequestId;

            request.resizeBuffer(bytes_read);
            if (!request.isValid())
            {
                result = ERROR_NOT_ENOUGH_MEMORY;
                break;
            }
        }
        else if (ERROR_CONNECTION_INVALID == result && !HTTP_IS_NULL_ID(&request_id))
        {
            // The TCP connection was corrupted by the peer when attempting to handle a request with more buffer. Continue to the next request.
            HTTP_SET_NULL_ID(&request_id);
        }
        else
        {
            break;
        }
    }
}