///////////////////////////////////////////////////////////////////
//*-------------------------------------------------------------*//
//| Part of the Game Jolt API C++ Library (http://gamejolt.com) |//
//*-------------------------------------------------------------*//
//| Released under the zlib License                             |//
//| More information available in the readme file               |//
//*-------------------------------------------------------------*//
///////////////////////////////////////////////////////////////////
#include "gjAPI.h"


// ****************************************************************
/* constructor */
gjNetwork::gjNetwork(gjAPI* pAPI)noexcept
: m_iNumSessions (0)
, m_pAPI         (pAPI)
{
    // init cURL
    curl_global_init(CURL_GLOBAL_ALL);
    m_pMultiHandle = curl_multi_init();

    // reserve some memory
    m_apCall.reserve(GJ_API_RESERVE_CALL);
}


// ****************************************************************
/* destructor */
gjNetwork::~gjNetwork()
{
    // finish and remove open sessions
    this->Wait(2);
    while(!m_apCall.empty())
    {
        gjAPI::ErrorLogAdd("Network Error: session had to be killed <" + m_apCall[0]->GetInfo() + ">");
        this->__KillCall(m_apCall[0]);
    }

    // clear memory
    m_apCall.clear();

    // exit cURL
    curl_multi_cleanup(m_pMultiHandle);
    curl_global_cleanup();
}


// ****************************************************************
/* update all active non-blocking cURL sessions */
bool gjNetwork::Update()
{
    if(m_apCall.empty()) return false;

    // update cURL
    curl_multi_perform(m_pMultiHandle, &m_iNumSessions);

    // manage active cURL sessions
    CURLMsg* pMsg;
    while((pMsg = curl_multi_info_read(m_pMultiHandle, &m_iNumSessions)))
    {
        // cURL session finished
        if(pMsg->msg == CURLMSG_DONE)
        {
            CURL* pSession = pMsg->easy_handle;
            const bool bOK = (pMsg->data.result == CURLE_OK) ? true : false;

            // search callback object and finish the operation
            FOR_EACH(it, m_apCall)
            {
                gjCall* pCall = (*it);

                if(pCall->GetSession() == pSession)
                {
                    // check for errors
                    if(!bOK)
                    {
                        gjAPI::ErrorLogAdd("Network Error: sending non-blocking request failed <" + pCall->GetInfo() + ">");
                        gjAPI::ErrorLogAdd("Network Error: " + std::string(curl_easy_strerror(pMsg->data.result)));
                    }

                    pCall->Finish(bOK);
                    SAFE_DELETE(pCall)
                    m_apCall.erase(it);
                    break;
                }
            }

            // close cURL session
            curl_multi_remove_handle(m_pMultiHandle, pSession);
            curl_easy_cleanup(pSession);
        }
    }

    return true;
}


// ****************************************************************
/* finish all active sessions and return */
void gjNetwork::Wait(const unsigned int& iMaxWait)
{
    // get max waiting time (low precision)
    const time_t iMaxTime = std::time(NULL) + iMaxWait;

    // force network update
    while((iMaxTime >= std::time(NULL) || !iMaxWait) && this->Update()) {}
}


// ****************************************************************
/* check for existing callbacks on the list */
gjNetwork::gjCall* gjNetwork::__CheckCall(const std::string& sInfo)
{
    // search callback object and compare info string
    FOR_EACH(it, m_apCall)
    {
        if((*it)->GetInfo() == sInfo)
            return (*it);
    }

    return NULL;
}


// ****************************************************************
/* kill and remove session and callback object */
void gjNetwork::__KillCall(gjCall* pCall)
{
    // search callback object
    FOR_EACH(it, m_apCall)
    {
        gjCall* pCurCall = (*it);

        if(pCurCall == pCall)
        {
            CURL* pSession = pCurCall->GetSession();

            // delete callback object
            pCurCall->Finish(false);
            SAFE_DELETE(pCurCall)
            m_apCall.erase(it);

            // close cURL session
            curl_multi_remove_handle(m_pMultiHandle, pSession);
            curl_easy_cleanup(pSession);

            return;
        }
    }
}


// ****************************************************************
/* callbacks for writing operations with cURL */
size_t write_to_string(char* ptr, size_t size, size_t count, std::string* stream)
{
    const size_t num = size * count;
    stream->append(ptr, 0, num);
    return num;
}

size_t write_to_file(void* ptr, size_t size, size_t count, FILE* stream)
{
    return fwrite(ptr, size, count, stream);
}