/////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2005-2017 Dawson Dean
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
/////////////////////////////////////////////////////////////////////////////
//
// HTTP Stream Module
//
// This manages a complete request-response cycle.
/////////////////////////////////////////////////////////////////////////////

#include "osIndependantLayer.h"
#include "config.h"
#include "log.h"
#include "debugging.h"
#include "memAlloc.h"
#include "refCount.h"
#include "threads.h"
#include "stringLib.h"
#include "stringParse.h"
#include "queue.h"
#include "jobQueue.h"
#include "rbTree.h"
#include "nameTable.h"
#include "url.h"
#include "blockIO.h"
#include "asyncIOStream.h"
#include "polyHTTPStream.h"

FILE_DEBUGGING_GLOBALS(LOG_LEVEL_DEFAULT, 0);



/////////////////////////////////////////////////////////////////////////////
//
// [CSynchPolyHttpCallback]
//
/////////////////////////////////////////////////////////////////////////////
CSynchPolyHttpCallback::CSynchPolyHttpCallback() {
    m_pSemaphore = NULL;
    m_Err = ENoErr;
} // CSynchPolyHttpCallback



/////////////////////////////////////////////////////////////////////////////
//
// [~CSynchPolyHttpCallback]
//
/////////////////////////////////////////////////////////////////////////////
CSynchPolyHttpCallback::~CSynchPolyHttpCallback() {
    RELEASE_OBJECT(m_pSemaphore);
} // ~CSynchPolyHttpCallback




/////////////////////////////////////////////////////////////////////////////
//
// [Initialize]
//
/////////////////////////////////////////////////////////////////////////////
ErrVal
CSynchPolyHttpCallback::Initialize() {
    ErrVal err = ENoErr;

    m_pSemaphore = newex CRefEvent;
    if (NULL == m_pSemaphore) {
        gotoErr(EFail);
    }

    err = m_pSemaphore->Initialize();
    if (err) {
        gotoErr(err);
    }

abort:
    returnErr(err);
} // Initialize.





/////////////////////////////////////////////////////////////////////////////
//
// [Wait]
//
/////////////////////////////////////////////////////////////////////////////
ErrVal
CSynchPolyHttpCallback::Wait() {
    if (m_pSemaphore) {
        m_pSemaphore->Wait();
    }
    return(m_Err);
} // Wait.





/////////////////////////////////////////////////////////////////////////////
//
// [OnReadHTTPDocument]
//
/////////////////////////////////////////////////////////////////////////////
void
CSynchPolyHttpCallback::OnReadHTTPDocument(
                            ErrVal err,
                            CPolyHttpStream *pStream,
                            void *pCallbackContext) {
    // Unused
    pStream = pStream;
    pCallbackContext = pCallbackContext;

    m_Err = err;
    if (m_pSemaphore) {
       m_pSemaphore->Signal();
    }
} // OnReadHTTPDocument.







/////////////////////////////////////////////////////////////////////////////
//
// [OnWriteHTTPDocument]
//
/////////////////////////////////////////////////////////////////////////////
void
CSynchPolyHttpCallback::OnWriteHTTPDocument(
                            ErrVal err,
                            CPolyHttpStream *pStream,
                            void *pCallbackContext) {
    // Unused
    pStream = pStream;
    pCallbackContext = pCallbackContext;

    m_Err = err;
    if (m_pSemaphore) {
       m_pSemaphore->Signal();
    }
} // OnWriteHTTPDocument.





/////////////////////////////////////////////////////////////////////////////
//
//                     TESTING PROCEDURES
//
/////////////////////////////////////////////////////////////////////////////
#if INCLUDE_REGRESSION_TESTS

static char g_DumpFileName[1024];
static CAsyncIOEventHandlerSynch *g_pTestCallback = NULL;

static void TestOneURL(const char *urlStr, int32 *pResultSize);


/////////////////////////////////////////////////////////////////////////////
//
// [TestHTTPStream]
//
/////////////////////////////////////////////////////////////////////////////
void
CPolyHttpStream::TestHTTPStream() {
    ErrVal err = ENoErr;
    const char *test;
    int32 size1;
    int32 index;

    g_DebugManager.StartModuleTest("HTTP Stream");

    g_DebugManager.AddTestResultsDirectoryPath("LexerDump.txt", g_DumpFileName, sizeof(g_DumpFileName));

    g_pTestCallback = newex CAsyncIOEventHandlerSynch;
    if (NULL == g_pTestCallback) {
        DEBUG_WARNING("Cannot parse the url.");
        return;
    }
    err = g_pTestCallback->Initialize();
    if (err) {
        DEBUG_WARNING("Cannot parse the url.");
        return;
    }

    g_pNetIOSystem->SetDebugFlags(CDebugObject::CHECK_STATE_ON_EVERY_OP);
    DontCountAllCurrentAllocations();

    ////////////////////////////////////////
    for (index = 0; ; index++) {
        test = g_TestURLList[index];
        if (NULL == test) {
            break;
        }

        g_DebugManager.StartTest(test);
        TestOneURL(test, &size1);
    } // running each test.
} // TestHTTPStream.





/////////////////////////////////////////////////////////////////////////////
//
// [TestOneURL]
//
/////////////////////////////////////////////////////////////////////////////
void
TestOneURL(const char *urlStr, int32 *pResultSize) {
    ErrVal err = ENoErr;
    CAsyncIOStream *pBodyStream = NULL;
    CAsyncIOStream *pWriteStream = NULL;
    CParsedUrl *pUrl = NULL;
    char bodyStr[256];
    CPolyHttpStream *pHTTPStream = NULL;
    CSynchPolyHttpCallback *pCallback = NULL;
    int64 startBodyPosition;
    int16 type;
    int16 subType;
    int32 statusCode;

    *pResultSize = 0;

    // Make a URL
    pUrl = CParsedUrl::AllocateUrl(urlStr);
    if (NULL == pUrl) {
        gotoErr(EFail);
    }

    // Make a stream
    pHTTPStream = CPolyHttpStream::AllocateSimpleStream();
    if (NULL == pHTTPStream) {
        gotoErr(EFail);
    }

    // Make a callback.
    pCallback = newex CSynchPolyHttpCallback;
    if (NULL == pCallback) {
        gotoErr(EFail);
    }
    err = pCallback->Initialize();
    if (err) {
        gotoErr(err);
    }

    // Load the document.
    pHTTPStream->ReadHTTPDocument(pUrl, pCallback, NULL);
    err = pCallback->Wait();
    if (err) {
        //if ((ENoResponse == err) || (ENoHostAddress == err) || (EEOF == err) || (EHTTPSRequired == err))
        OSIndependantLayer::PrintToConsole(CDebugManager::GetErrorDescriptionString(err));
        gotoErr(err);
    }

    statusCode = pHTTPStream->GetStatusCode();
    err = pHTTPStream->GetIOStream(
                        &pBodyStream,
                        &startBodyPosition,
                        NULL); // *pLength
    if (NULL == pBodyStream) {
        gotoErr(EFail);
    }


    // Many servers return no body when they return an error
    // like a redirect message.
    if (200 != statusCode) {
        OSIndependantLayer::PrintToConsole("Error status code: %d.", statusCode);
    }


    // Copy the page to a local file.
    RELEASE_OBJECT(pUrl);
    pUrl = CParsedUrl::AllocateFileUrl(g_DumpFileName);
    if (NULL == pUrl) {
        gotoErr(EFail);
    }

    err = CAsyncIOStream::OpenAsyncIOStream(
                                pUrl,
                                CAsyncBlockIO::CREATE_NEW_STORE
                                    | CAsyncBlockIO::READ_ACCESS
                                    | CAsyncBlockIO::WRITE_ACCESS,
                                g_pTestCallback,
                                NULL);
    if (err) {
        gotoErr(err);
    }

    err = g_pTestCallback->Wait();
    if (err) {
        gotoErr(err);
    }

    pWriteStream = g_pTestCallback->m_pAsyncIOStream;
    g_pTestCallback->m_pAsyncIOStream = NULL;


    // I want the http header as well as the body.
    err = pBodyStream->SetPosition(0);
    if (err) {
        if (EEOF != err)
            DEBUG_WARNING("Cannot open the http stream.");
        gotoErr(err);
    }

    err = pBodyStream->CopyStream(pWriteStream, CAsyncIOStream::COPY_TO_EOF, false);
    if (err) {
        gotoErr(err);
    }

    pWriteStream->Flush();
    err = g_pTestCallback->Wait();
    if (err) {
        gotoErr(err);
    }


    err = pBodyStream->SetPosition(startBodyPosition);
    if (err) {
        if (EEOF != err) {
            DEBUG_WARNING("Cannot open the http stream.");
        }
        gotoErr(err);
    }

    err = pBodyStream->Read(bodyStr, 100);
    bodyStr[100] = 0;
    if ((err) && (EEOF != err)) {
        DEBUG_WARNING("Cannot read 100 bytes from the body.");
        gotoErr(err);
    }

    *pResultSize = (int32) (pBodyStream->GetDataLength());
    err = pHTTPStream->GetContentType(&type, &subType);
    if (err) {
        gotoErr(err);
    }

abort:
    RELEASE_OBJECT(pUrl);
    if (pHTTPStream) {
        pHTTPStream->CloseStreamToURL();
    }
    if (pWriteStream) {
        pWriteStream->Close();
    }
    RELEASE_OBJECT(pWriteStream);
    RELEASE_OBJECT(pHTTPStream);
    RELEASE_OBJECT(pBodyStream);
    RELEASE_OBJECT(pCallback);
} // TestOneURL.


#endif // INCLUDE_REGRESSION_TESTS