/***************************** LICENSE START ***********************************

 Copyright 2016 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include <stdlib.h>

#include <cstring>
#include <fstream>
#include <iostream>

#include "fstream_mars_fix.h"

#include "MvApplication.h"
#include "MvNetwork.h"

// 7.21.0
#define MINCURLVERSIONFORWILDCARD 0x071500

// 7.14.1
#define MINCURLVERSIONFORPROXY 0x070E01

// 7.19.4
#define MINCURLVERSIONFORNOPROXY 0x071304


// ----------------------------------------------------------------------------
// MvNetworkData
// Holds and manages the buffer to store data resulting from a network transfer
// ----------------------------------------------------------------------------

MvNetworkData::~MvNetworkData()
{
    if (buffer_)
        free(buffer_);
}

void MvNetworkData::clear()
{
    buffer_ = 0;
    pos_    = 0;
    len_    = 0;
}

size_t MvNetworkData::add(char* ptr, size_t size)
{
    if (buffer_ == nullptr) {
        pos_    = 0;
        len_    = initialSize_;
        buffer_ = static_cast<char*>(malloc(len_));
    }

    if (pos_ + size > len_) {
        while (pos_ + size > len_) {
            len_ *= 2;
        }

        buffer_ = static_cast<char*>(realloc(buffer_, len_));
        if (!buffer_) {
            //error_++;
            return 0;
        }
    }

    std::memcpy(buffer_ + pos_, ptr, size);
    pos_ += size;

    return size;
}

std::string MvNetworkData::toString()
{
    if (buffer_ && len_ > 0) {
        return std::string(buffer_) + '\0';
    }
    return std::string();
}


// ----------------------------------------------------------------------
// MvNetwork
// Handles the data request and writes the data to the requested location
// ----------------------------------------------------------------------

MvNetwork::MvNetwork()
{
    ch_ = curl_easy_init();
    //    curl_version_info_data *cvi = curl_version_info(CURLVERSION_NOW);

    curl_easy_setopt(ch_, CURLOPT_HEADER, 1);

    curl_easy_setopt(ch_, CURLOPT_WRITEFUNCTION, MvNetwork::writeCb);
    curl_easy_setopt(ch_, CURLOPT_WRITEDATA, this);

    curl_easy_setopt(ch_, CURLOPT_HEADER, 0);
    curl_easy_setopt(ch_, CURLOPT_HEADERFUNCTION, MvNetwork::headerCb);
    curl_easy_setopt(ch_, CURLOPT_HEADERDATA, this);

    curl_easy_setopt(ch_, CURLOPT_FOLLOWLOCATION, 1);  // some WMS servers redirect URLs
    curl_easy_setopt(ch_, CURLOPT_VERBOSE, 1);


    // long minVersionForWildcard = (7 << 16) | (21 << 8) | 0;
    //    if (cvi->version_num >= minVersionForWildcard)
#if LIBCURL_VERSION_NUM >= MINCURLVERSIONFORWILDCARD
    curl_easy_setopt(ch_, CURLOPT_WILDCARDMATCH, 0);  // don't try to match wildcard chars
#endif

    setupProxy();
}


void MvNetwork::setupProxy()
{
    // get the Metview user preferences
    MvRequest myPref = MvApplication::getExpandedPreferences();

    bool use             = false;
    const char* useProxy = myPref("USE_NETWORK_PROXY");
    if ((useProxy != nullptr))
        use = (strcmp(useProxy, "Yes") == 0 ||
               strcmp(useProxy, "YES") == 0 ||
               strcmp(useProxy, "yes") == 0)
                  ? true
                  : false;

    if (use) {
#if LIBCURL_VERSION_NUM >= MINCURLVERSIONFORPROXY
        const char* proxyUrl = myPref("PROXY_URL");
        if (proxyUrl) {
            curl_easy_setopt(ch_, CURLOPT_PROXY, proxyUrl);
            std::cout << "Using user proxy URL: " << proxyUrl << std::endl;
        }
#else
        std::cout << "Curl version is not new enough to support CURLOPT_PROXY - proxy setting not applied." << std::endl;
#endif

        const char* proxyPort = myPref("PROXY_PORT");
        if (proxyPort) {
            curl_easy_setopt(ch_, CURLOPT_PROXYPORT, atoi(proxyPort));
            std::cout << "Using user proxy port: " << proxyPort << std::endl;
        }


#if LIBCURL_VERSION_NUM >= MINCURLVERSIONFORNOPROXY
        const char* noProxy = myPref("NO_PROXY_FOR");  // comma-separated list
        if (noProxy) {
            curl_easy_setopt(ch_, CURLOPT_NOPROXY, noProxy);
            std::cout << "Using user no_proxy: " << noProxy << std::endl;
        }
#else
        std::cout << "Curl version is not new enough to support CURLOPT_NOPROXY - no_proxy setting not applied." << std::endl;
#endif
    }
}


bool MvNetwork::get(const std::string& req, const std::string& outfile, const std::string /*mimeType*/, std::string& errorMsg)
{
    curl_easy_setopt(ch_, CURLOPT_URL, req.c_str());

    // perform the actual network transfer
    CURLcode ret = curl_easy_perform(ch_);

    if (ret != CURLE_OK) {
        errorMsg = curl_easy_strerror(ret);
        return false;
    }

    std::cout << "data size " << data_.len_ << std::endl;

    if (data_.len_ == 0) {
        errorMsg = "No bytes received.";
        return false;
    }

    // get the response code
    curl_easy_getinfo(ch_, CURLINFO_RESPONSE_CODE, &responseCode_);


    // write the data buffer to a file
    std::ofstream out;
    out.open(outfile.c_str());

    if (out.good()) {
        out.write(data_.buffer_, data_.pos_);
        out.close();
        std::cout << "outfile " << outfile << std::endl;
    }
    else {
        errorMsg = "Could not open file for writing: " + outfile;
        return false;
    }


    // in case CURL did anything to change the URL...
    const char* usedUrl;
    curl_easy_getinfo(ch_, CURLINFO_EFFECTIVE_URL, &usedUrl);
    std::cout << "Used URL: " << usedUrl << std::endl;

    data_.clear();  // clear the data buffer
    return true;
}

size_t MvNetwork::writeCb(char* ptr, size_t size, size_t nmemb, void* userdata)
{
    //std::cout << "writeCB " << size*nmemb <<  std::endl;
    if (!userdata)
        return 0;

    if (auto* obj = static_cast<MvNetwork*>(userdata)) {
        return obj->data_.add(ptr, size * nmemb);
    }

    return 0;
}

size_t MvNetwork::headerCb(char* ptr, size_t size, size_t nmemb, void* userdata)
{
    if (!userdata)
        return 0;

    if (auto* obj = static_cast<MvNetwork*>(userdata)) {
        return obj->header_.add(ptr, size * nmemb);
    }

    return 0;
}
