/***************************************************************************
 *   Copyright (C) 2019 Alec Leamas                                        *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, see <https://www.gnu.org/licenses/>. *
 ***************************************************************************/

/**
 * \file
 *
 * Implement downloader.h -- handle downloading of files from remote urls.
 */

#include <fstream>

#include <curl/curl.h>

#include <wx/filename.h>
#include <wx/log.h>

#include "config.h"
#include "model/downloader.h"
#include "model/ocpn_utils.h"

/** Dummy curl callback on received data from remote. */
static size_t throw_cb(void* ptr, size_t size, size_t nmemb, void* data) {
  (void)ptr;
  (void)data;
  return (size_t)(size * nmemb);
}

static std::string GetUserAgent() {
  std::string ua = "Mozilla/5.0 (@abi@; @abi_version@) OpenCPN/@o_version@";
  ua += " curl/@curl_version@";
  ocpn::replace(ua, "@o_version@", VERSION_FULL);
  ocpn::replace(ua, "@abi@", PKG_TARGET);
  ocpn::replace(ua, "@abi_version@", PKG_TARGET_VERSION);
  ocpn::replace(ua, "@curl_version@", LIBCURL_VERSION);
  return ua;
}

static unsigned write_cb(char* in, unsigned size, unsigned nmemb, void* data);
// Forward

Downloader::Downloader(std::string url_)
    : url(url_), stream(), error_msg(""), errorcode(0) {};

int Downloader::last_errorcode() { return errorcode; }

std::string Downloader::last_error() { return error_msg; }

void Downloader::on_chunk(const char* buff, unsigned bytes) {
  stream->write(buff, bytes);
}

bool Downloader::download(std::ostream* stream) {
  CURL* curl;
  char curl_errbuf[CURL_ERROR_SIZE];

  this->stream = stream;
  curl = curl_easy_init();
  curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf);
  curl_easy_setopt(curl, CURLOPT_USERAGENT, GetUserAgent().c_str());
  curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
  curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L);
  // FIXME -- Add correct certificates on host.
  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
  curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
  int code = curl_easy_perform(curl);
  curl_easy_cleanup(curl);
  if (code != CURLE_OK) {
    wxLogWarning("Failed to get '%s' [%s]\n", url, curl_errbuf);
    errorcode = code;
    error_msg = std::string(curl_errbuf);
    return false;
  }
  return true;
}

bool Downloader::download(std::string& path) {
  if (path == "") {
    path = wxFileName::CreateTempFileName("ocpn_dl").ToStdString();
  }
  std::ofstream stream;
  stream.open(path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
  if (!stream.is_open()) {
    errorcode = CURLE_WRITE_ERROR;
    error_msg = std::string("Cannot open temporary file ") + path;
    return false;
  }
  bool ok = download(&stream);
  stream.close();
  return ok;
}

long Downloader::get_filesize() {
  CURL* curl;
  char curl_errbuf[CURL_ERROR_SIZE] = {0};
  double filesize = 0.0;

  curl = curl_easy_init();
  curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
  curl_easy_setopt(curl, CURLOPT_USERAGENT, GetUserAgent().c_str());
  curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
  curl_easy_setopt(curl, CURLOPT_FILETIME, 1L);
  curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, throw_cb);
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
  curl_easy_setopt(curl, CURLOPT_HEADER, 0L);
  // FIXME -- Add correct certificates on host.
  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);

  int r = curl_easy_perform(curl);
  if (r == CURLE_OK) {
    r = curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &filesize);
  }
  curl_easy_cleanup(curl);
  wxLogMessage("filesize %s: %d bytes\n", url.c_str(), (int)filesize);
  if (r != CURLE_OK) {
    errorcode = r;
    error_msg = std::string(curl_errbuf);
    return 0;
  }
  return (long)filesize;
}

/** Curl callback on received data from remote. */
static unsigned write_cb(char* in, unsigned size, unsigned nmemb, void* data) {
  auto downloader = static_cast<Downloader*>(data);
  if (data == 0) {
    return 0;
  }
  downloader->on_chunk(in, size * nmemb);
  return in == NULL ? 0 : size * nmemb;
}
