#include <iostream>
#include <sstream>
#include <string>
#include <stdio.h>
#include <string.h>
#include <stdexcept>
#include <limits>
#include "curl/curl.h"

using namespace std;

template<typename OptionType>
void Set_Easy_Opt(CURL* easyHandle, CURLoption option, OptionType value) throw(exception)
{
    CURLcode result = curl_easy_setopt(easyHandle, option, value);
    if (result != CURLE_OK)
    {
        std::ostringstream str;
        str << "curl_easy_setopt FAILED for option: " << option << ", value: " << value 
			<< " returned: " << (int) result << " " << curl_easy_strerror(result);
        throw runtime_error(str.str());
    }
}

int CurlDebugCallback (CURL *, curl_infotype infotype, char *buf, size_t size, void *)
{
    if (infotype == CURLINFO_TEXT)
    {
        char dbgString[size+1];
        strncpy(dbgString, buf, size);
        dbgString[size] = '\0';

        cout << "CURL DEBUG: " << dbgString << endl;
    }

    return 0;
}

int SetupTransfer(CURL* handle, FILE* file, string const& urlToFile, char* errorBuffer)
{
	const long FTP_RESPONSE_TIMEOUT = 15;
    const long CONNECT_TIMEOUT = 60;
    const long OP_TIMEOUT = 1000000000;
    
	try {
		errorBuffer[0] = '\0';
		Set_Easy_Opt(handle, CURLOPT_ERRORBUFFER, errorBuffer);

		Set_Easy_Opt(handle, CURLOPT_URL, urlToFile.c_str());
		
		Set_Easy_Opt(handle, CURLOPT_UPLOAD, 1l);
		Set_Easy_Opt(handle, CURLOPT_READDATA, file);

		Set_Easy_Opt(handle, CURLOPT_NOSIGNAL, 1l);
		
		cout << "Setting timeouts, operation timeout = " << OP_TIMEOUT << ", connect timeout = " << CONNECT_TIMEOUT
                    << ", ftpResponseTimeout = " << FTP_RESPONSE_TIMEOUT << endl
                    << "maximum long int is: " << numeric_limits<long>::max() << endl;
        
		Set_Easy_Opt(handle, CURLOPT_TIMEOUT, OP_TIMEOUT);
		Set_Easy_Opt(handle, CURLOPT_CONNECTTIMEOUT, CONNECT_TIMEOUT);
		Set_Easy_Opt(handle, CURLOPT_FTP_RESPONSE_TIMEOUT, FTP_RESPONSE_TIMEOUT);

		Set_Easy_Opt(handle, CURLOPT_VERBOSE, 1l);
		Set_Easy_Opt(handle, CURLOPT_DEBUGFUNCTION, CurlDebugCallback);

		Set_Easy_Opt(handle, CURLOPT_LOW_SPEED_TIME, 60l);
		Set_Easy_Opt(handle, CURLOPT_LOW_SPEED_LIMIT, 1l);

		Set_Easy_Opt(handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_WHATEVER);
	} catch (runtime_error const& e) {
		cout << "easy handle could not be configured, exception: " << e.what() << endl;
		if (errorBuffer[0])
			cout << "CURL: ConfigureCurlHandle: curl error buffer: " << errorBuffer << endl;
		fclose(file);
		return 1;
	}
	
	return 0;
}

int main(int argc, char** argv)
{
    if (argc != 3)
    {
        cout << "Two parameters are needed: path to source, url to destination\n";
        return 2;
    }
    
    const string localPathToFile(argv[1]);
    const string urlToFile(argv[2]);
    
    cout << "File: " << localPathToFile << " will be uploaded to " << urlToFile << endl;
    
    curl_global_init(CURL_GLOBAL_ALL);
    
    CURL* curl;
 
    curl = curl_easy_init();
    if (not curl)
    {
        cout << "Easy handle could not be initialised\n";
        return 1;
    }
    
    char errorBuffer[CURL_ERROR_SIZE];

    FILE* file = fopen(localPathToFile.c_str(), "rb");
    if (not file)
    {
        cout << "File for upload could not be opened\n";
        return 1;
    }
    
    if ( SetupTransfer(curl, file, urlToFile, errorBuffer) )
    {
        cout << "Easy handle could not be configured\n";
        return 1;
    }
    
    CURLcode res = curl_easy_perform(curl);
  
    cout << "Easy performed returned: " << (int) res << ", " << curl_easy_strerror(res)
         << ", error buffer: \"" << errorBuffer << "\"\n";
         
    curl_easy_cleanup(curl);
    
    if (res != CURLE_OK)
        return 1;
        
    return 0;
}
