////////////////////////////////////////////////////////////////////////////
// Copyright KAZ 2021							  //
// 									  //
// contact (at) kaz.bzh							  //
// 									  //
// This software is a filter to shrink email by attachment extraction.	  //
// 									  //
// This software is governed by the CeCILL-B license under French law and //
// abiding by  the rules of distribution  of free software. You  can use, //
// modify  and/or  redistribute  the  software under  the  terms  of  the //
// CeCILL-B license as circulated by CEA, CNRS and INRIA at the following //
// URL "http://www.cecill.info".					  //
// 									  //
// As a counterpart to the access to  the source code and rights to copy, //
// modify and  redistribute granted  by the  license, users  are provided //
// only with a limited warranty and  the software's author, the holder of //
// the economic  rights, and the  successive licensors have  only limited //
// liability.								  //
// 									  //
// In this respect, the user's attention is drawn to the risks associated //
// with loading,  using, modifying  and/or developing or  reproducing the //
// software by the user in light of its specific status of free software, //
// that may  mean that  it is  complicated to  manipulate, and  that also //
// therefore means  that it  is reserved  for developers  and experienced //
// professionals having in-depth computer  knowledge. Users are therefore //
// encouraged  to load  and test  the software's  suitability as  regards //
// their  requirements  in  conditions  enabling the  security  of  their //
// systems and/or  data to  be ensured  and, more  generally, to  use and //
// operate it in the same conditions as regards security.		  //
// 									  //
// The fact that  you are presently reading this means  that you have had //
// knowledge of the CeCILL-B license and that you accept its terms.	  //
////////////////////////////////////////////////////////////////////////////

#define LAST_VERSION "1.1 2022-10-30 jirafeauAPI"

#include <iostream>
#include <string>
#include <curl/curl.h>
#include <chrono>
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>

#include "kazDebug.hpp"
#include "kazMisc.hpp"
#include "SizeArg.hpp"

using namespace std;
using namespace boost;
using namespace boost::program_options;
using namespace kaz;

namespace bfs = boost::filesystem;

// ================================================================================
static options_description mainDescription ("Main options", getCols ());
static options_description hide ("Hidded options", getCols ());
static char *prog = NULL;

// ================================================================================
void
usage (const string &msg = "", const bool &hidden = false) {
  if (!msg.empty ())  {
    cout << msg << endl;
    exit (1);
  }
  cout << endl
       << "Usage: " << endl
       << "       A) " << prog << " [-s size] [-t period] [-c content-type] [-n attachName] [-f server] send file [password] > url,delCode" << endl
       << "       B) " << prog << " [-t period] [-f server] update ref > dealine" << endl
       << endl << "  store ficle" << endl << endl
       << "  A: send file (options : s, t)" << endl
       << "  B: update deadline (options : t) " << endl
       << endl << mainDescription
       << endl;
  if (hidden)
    cout << hide << endl;
  exit (0);
}

void
version () {
  cout << LAST_VERSION << " KAZ team production (https://kaz.bzh/)" << endl;
  exit (0);
}

static auto startPrg = std::chrono::high_resolution_clock::now ();
void
showTime (string msg) {
  using namespace std::chrono;
  static auto stopPrg = high_resolution_clock::now ();

  cerr << msg << " done in " << ns2string (duration_cast<duration<double> > (stopPrg-startPrg).count ()) << endl;
}

// ================================================================================
static size_t
WriteCallback (void *contents, size_t size, size_t nmemb, void *userp) {
  ((std::string*) userp)->append ((char*) contents, size * nmemb);
  return size * nmemb;
}

// ================================================================================
static const string inputFile = "input-file";
static const char *const inputFileC = inputFile.c_str ();

int
main (int argc, char** argv) {
  // XXX debug before parse options
  // Log::debug = true;
  DEF_LOG ("main:", "");
  prog = argv [0];
  bool
    debugFlag (false),
    helpFlag (false),
    versionFlag (false),
    useTheForceLuke (false);
  enum JirCmd { SEND, UPDATE } jirCmd;
  string
    inputFileName,
    password,
    contentType,
    attachName,
    urlBase ("http://file.kaz.bzh"),
    uploadPage ("/a.php"),
    updatePage ("/a.php"),
    minimumAvailability ("month"),
    proxy;

  SizeArg maxUploadSize ("100 Mi");

  try {
    mainDescription.add_options ()
      ("help,h", bool_switch (&helpFlag), "produce this help message")
      ("version,v", bool_switch (&versionFlag), "display version information")
      ("contentType,c", value<string> (&contentType)->default_value (contentType), "content-type of the sended file")
      ("attachName,n", value<string> (&attachName)->default_value (attachName), "force attachment name")
      ("minimumAvailability,t", value<string> (&minimumAvailability)->default_value (minimumAvailability), "minimum period of available download")
      ("maxUploadSize,s", value<SizeArg> (&maxUploadSize)->default_value (maxUploadSize), "maximum upload size")
      ("file server registery,f", value<string> (&urlBase)->default_value (urlBase), "server where file are temporary stored")
      ;

    hide.add_options ()
      ("useTheForceLuke", bool_switch (&useTheForceLuke), "display hidded options")
      ("debug,g", bool_switch (&debugFlag), "debug mode")
      ("proxy,p", value<string> (&proxy)->default_value (proxy), "set proxy (proxy-host.org:8080)")
      ("uploadPage,u", value<string> (&uploadPage)->default_value (uploadPage), "upload page")
      ("updatePage,v", value<string> (&updatePage)->default_value (updatePage), "update page")
      ;

    options_description cmd ("All options");
    cmd.add (mainDescription).add (hide).add_options ()
      (inputFileC, value<vector<string> > (), "input")
      ;
    positional_options_description p;
    p.add (inputFileC, -1);
    variables_map vm;
    basic_parsed_options<char> parsed = command_line_parser (argc, argv).options (cmd).positional (p).run ();
    store (parsed, vm);
    notify (vm);

    if (debugFlag) {
#ifdef DISABLE_LOG
      cerr << "No debug option available (was compiled with -DDISABLE_LOG)" << endl;
#endif
    }
    Log::debug = debugFlag;

    if (useTheForceLuke)
      usage ("", true);
    if (versionFlag)
      version ();
    if (helpFlag)
      usage ();

    if (vm.count (inputFileC)) {
      vector<string> var = vm[inputFileC].as<vector<string> > ();
      int nbArgs = vm[inputFileC].as<vector<string> > ().size ();
      if (!nbArgs)
	usage ("No command");
      if (var [0].compare ("send") == 0)
	jirCmd = SEND;
      else if (var [0].compare ("update") == 0)
	jirCmd = UPDATE;
      else
	usage ("Unknown command ("+var [0]+")");
      if (nbArgs < 2)
	usage ("no input file");
      inputFileName = var [1];
      if (nbArgs == 3)
	password = var [2];
      if (nbArgs > 3)
	usage ("Too much arguments");
    }
  } catch (std::exception &e) {
    cerr << "error: " << e.what() << endl;
    usage ();
    return 1;
  } catch (...) {
    cerr << "Exception of unknown type!" << endl;
    return 1;
  }

  if (inputFileName.empty ())
    usage ("no input");

  CURL *easyhandle = curl_easy_init ();
  if (! easyhandle) {
    cerr << "no curl" << endl;
    return 1;
  }

  string readBuffer;
  if (proxy.length ())
    curl_easy_setopt(easyhandle, CURLOPT_PROXY, proxy.c_str ()); 
  curl_easy_setopt (easyhandle, CURLOPT_WRITEFUNCTION, WriteCallback);
  curl_easy_setopt (easyhandle, CURLOPT_WRITEDATA, &readBuffer);
  curl_mime *multipart = curl_mime_init (easyhandle);
  curl_mimepart *part = nullptr;

  switch (jirCmd) {
  case SEND: {
    LOG ("SEND: " << (urlBase+uploadPage));
    curl_easy_setopt (easyhandle, CURLOPT_URL, (urlBase+uploadPage).c_str ());

    LOG ("maxUploadSize: " << maxUploadSize);
    long uploadsize = (size_t) maxUploadSize;
    curl_easy_setopt (easyhandle, CURLOPT_INFILESIZE, uploadsize);

    LOG ("time: " << minimumAvailability);
    part = curl_mime_addpart (multipart);
    curl_mime_name (part, "time");
    curl_mime_data (part, minimumAvailability.c_str (), CURL_ZERO_TERMINATED);

    if (password.size ()) {
      LOG ("key: " << password);
      part = curl_mime_addpart (multipart);
      curl_mime_name (part, "key");
      curl_mime_data (part, password.c_str (), CURL_ZERO_TERMINATED);
    }

    LOG ("inputFileName: " << bfs::path (inputFileName).filename ());
    part = curl_mime_addpart (multipart);
    curl_mime_name (part, "file");
    if (contentType.length ()) {
      LOG ("contentType: " << contentType);
      curl_mime_type (part, contentType.c_str ());
    }
    if (attachName.empty ()) {
      attachName = bfs::path (inputFileName).filename ().c_str ();
      LOG ("attachName: " << attachName);
    }
    curl_mime_filename (part, attachName.c_str ());
    FILE *fp = fopen (inputFileName.c_str (), "r");
    fseek (fp, 0L, SEEK_END);
    long int fsize (ftell (fp));
    fseek (fp, 0L, SEEK_SET);
    curl_mime_data_cb (part, fsize,
		       (curl_read_callback) fread,
		       (curl_seek_callback) fseek,
    		       NULL, //(curl_seek_callback) fclose,
		       fp);
  }
    break;

  case UPDATE: {
    LOG ("UPDATE: " << (urlBase+updatePage));
    curl_easy_setopt (easyhandle, CURLOPT_URL, (urlBase+updatePage).c_str ());

    LOG ("h: " << inputFileName);
    part = curl_mime_addpart (multipart);
    curl_mime_name (part, "h");
    curl_mime_data (part, inputFileName.c_str (), CURL_ZERO_TERMINATED);

    LOG ("u: " << minimumAvailability);
    part = curl_mime_addpart (multipart);
    curl_mime_name (part, "u");
    curl_mime_data (part, minimumAvailability.c_str (), CURL_ZERO_TERMINATED);
  }
    break;
  }

  curl_easy_setopt (easyhandle, CURLOPT_MIMEPOST, multipart);
  CURLcode res (curl_easy_perform (easyhandle));
  curl_easy_cleanup (easyhandle);
  cout << readBuffer << endl;

  showTime ("Upload");
  if (res != CURLE_OK)
    cerr << prog << " failed: " << curl_easy_strerror (res) << endl;
  return 0;
}

// ================================================================================
