/*
 * Copyright (C) 2013-2014 Colin Fletcher <colin.m.fletcher@googlemail.com>
 * Copyright (C) 2015-2016 Paul Davis <paul@linuxaudiosystems.com>
 * Copyright (C) 2015-2017 Robin Gareus <robin@gareus.org>
 *
 * 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
#include "ardour/debug.h"
#include "ardour/soundcloud_upload.h"

#include "pbd/xml++.h"
#include <pbd/error.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <iostream>
#include "pbd/gstdio_compat.h"

#include "pbd/i18n.h"

using namespace PBD;

static size_t
WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
{
	size_t realsize = (size * nmemb);
	struct MemoryStruct *mem = (struct MemoryStruct *)data;

	mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1);

	if (mem->memory) {
		memcpy(&(mem->memory[mem->size]), ptr, realsize);
		mem->size += realsize;
		mem->memory[mem->size] = 0;
	}
	return realsize;
}

SoundcloudUploader::SoundcloudUploader()
: errorBuffer()
, caller(0)
{
	curl_handle = curl_easy_init();
	multi_handle = curl_multi_init();
}

std::string
SoundcloudUploader::Get_Auth_Token( std::string username, std::string password )
{
	struct MemoryStruct xml_page;
	xml_page.memory = NULL;
	xml_page.size = 0;

	setcUrlOptions();

	curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
	curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &xml_page);

	struct curl_httppost *formpost=NULL;
	struct curl_httppost *lastptr=NULL;

	/* Fill in the filename field */
	curl_formadd(&formpost,
			&lastptr,
			CURLFORM_COPYNAME, "client_id",
			CURLFORM_COPYCONTENTS, "6dd9cf0ad281aa57e07745082cec580b",
			CURLFORM_END);

	curl_formadd(&formpost,
			&lastptr,
			CURLFORM_COPYNAME, "client_secret",
			CURLFORM_COPYCONTENTS, "53f5b0113fb338800f8a7a9904fc3569",
			CURLFORM_END);

	curl_formadd(&formpost,
			&lastptr,
			CURLFORM_COPYNAME, "grant_type",
			CURLFORM_COPYCONTENTS, "password",
			CURLFORM_END);

	curl_formadd(&formpost,
			&lastptr,
			CURLFORM_COPYNAME, "username",
			CURLFORM_COPYCONTENTS, username.c_str(),
			CURLFORM_END);

	curl_formadd(&formpost,
			&lastptr,
			CURLFORM_COPYNAME, "password",
			CURLFORM_COPYCONTENTS, password.c_str(),
			CURLFORM_END);

	struct curl_slist *headerlist=NULL;
	headerlist = curl_slist_append(headerlist, "Expect:");
	headerlist = curl_slist_append(headerlist, "Accept: application/xml");
	curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headerlist);

	/* what URL that receives this POST */
	std::string url = "https://api.soundcloud.com/oauth2/token";
	curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
	curl_easy_setopt(curl_handle, CURLOPT_HTTPPOST, formpost);

	// curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);

	// perform online request
	CURLcode res = curl_easy_perform(curl_handle);
	if (res != 0) {
		DEBUG_TRACE (DEBUG::Soundcloud, string_compose ("curl error %1 (%2)\n", res, curl_easy_strerror(res) ) );
		return "";
	}

	if (xml_page.memory){
		// cheesy way to parse the json return value.  find access_token, then advance 3 quotes

		if ( strstr ( xml_page.memory , "access_token" ) == NULL) {
			error << _("Upload to Soundcloud failed.  Perhaps your email or password are incorrect?\n") << endmsg;
			return "";
		}

		std::string token = strtok( xml_page.memory, "access_token" );
		token = strtok( NULL, "\"" );
		token = strtok( NULL, "\"" );
		token = strtok( NULL, "\"" );

		free( xml_page.memory );
		return token;
	}

	return "";
}

int
SoundcloudUploader::progress_callback(void *caller, double dltotal, double dlnow, double ultotal, double ulnow)
{
	SoundcloudUploader *scu = (SoundcloudUploader *) caller;
	DEBUG_TRACE (DEBUG::Soundcloud, string_compose ("%1: uploaded %2 of %3\n", scu->title, ulnow, ultotal) );
	scu->caller->SoundcloudProgress(ultotal, ulnow, scu->title); /* EMIT SIGNAL */
	return 0;
}


std::string
SoundcloudUploader::Upload(std::string file_path, std::string title, std::string token, bool ispublic, bool downloadable, ARDOUR::ExportHandler *caller)
{
	int still_running;

	struct MemoryStruct xml_page;
	xml_page.memory = NULL;
	xml_page.size = 0;

	setcUrlOptions();

	curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
	curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &xml_page);

	struct curl_httppost *formpost=NULL;
	struct curl_httppost *lastptr=NULL;

	/* Fill in the file upload field. This makes libcurl load data from
	   the given file name when curl_easy_perform() is called. */
	curl_formadd(&formpost,
			&lastptr,
			CURLFORM_COPYNAME, "track[asset_data]",
			CURLFORM_FILE, file_path.c_str(),
			CURLFORM_END);

	/* Fill in the filename field */
	curl_formadd(&formpost,
			&lastptr,
			CURLFORM_COPYNAME, "oauth_token",
			CURLFORM_COPYCONTENTS, token.c_str(),
			CURLFORM_END);

	curl_formadd(&formpost,
			&lastptr,
			CURLFORM_COPYNAME, "track[title]",
			CURLFORM_COPYCONTENTS, title.c_str(),
			CURLFORM_END);

	curl_formadd(&formpost,
			&lastptr,
			CURLFORM_COPYNAME, "track[sharing]",
			CURLFORM_COPYCONTENTS, ispublic ? "public" : "private",
			CURLFORM_END);

	curl_formadd(&formpost,
			&lastptr,
			CURLFORM_COPYNAME, "track[downloadable]",
			CURLFORM_COPYCONTENTS, downloadable ? "true" : "false",
			CURLFORM_END);



	/* initalize custom header list (stating that Expect: 100-continue is not
	   wanted */
	struct curl_slist *headerlist=NULL;
	static const char buf[] = "Expect:";
	headerlist = curl_slist_append(headerlist, buf);


	if (curl_handle && multi_handle) {

		/* what URL that receives this POST */
		std::string url = "https://api.soundcloud.com/tracks";
		curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
		// curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);

		curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headerlist);
		curl_easy_setopt(curl_handle, CURLOPT_HTTPPOST, formpost);

		this->title = title; // save title to show in progress bar
		this->caller = caller;

		curl_easy_setopt (curl_handle, CURLOPT_NOPROGRESS, 0); // turn on the progress bar
		curl_easy_setopt (curl_handle, CURLOPT_PROGRESSFUNCTION, &SoundcloudUploader::progress_callback);
		curl_easy_setopt (curl_handle, CURLOPT_PROGRESSDATA, this);

		curl_multi_add_handle(multi_handle, curl_handle);

		curl_multi_perform(multi_handle, &still_running);


		while(still_running) {
			struct timeval timeout;
			int rc; /* select() return code */

			fd_set fdread;
			fd_set fdwrite;
			fd_set fdexcep;
			int maxfd = -1;

			long curl_timeo = -1;

			FD_ZERO(&fdread);
			FD_ZERO(&fdwrite);
			FD_ZERO(&fdexcep);

			/* set a suitable timeout to play around with */
			timeout.tv_sec = 1;
			timeout.tv_usec = 0;

			curl_multi_timeout(multi_handle, &curl_timeo);
			if(curl_timeo >= 0) {
				timeout.tv_sec = curl_timeo / 1000;
				if(timeout.tv_sec > 1)
					timeout.tv_sec = 1;
				else
					timeout.tv_usec = (curl_timeo % 1000) * 1000;
			}

			/* get file descriptors from the transfers */
			curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);

			/* In a real-world program you OF COURSE check the return code of the
			   function calls.  On success, the value of maxfd is guaranteed to be
			   greater or equal than -1.  We call select(maxfd + 1, ...), specially in
			   case of (maxfd == -1), we call select(0, ...), which is basically equal
			   to sleep. */

			rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);

			switch(rc) {
				case -1:
					/* select error */
					break;
				case 0:
				default:
					/* timeout or readable/writable sockets */
					curl_multi_perform(multi_handle, &still_running);
					break;
			}
		}

		/* then cleanup the formpost chain */
		curl_formfree(formpost);

		/* free slist */
		curl_slist_free_all (headerlist);
	}

	curl_easy_setopt (curl_handle, CURLOPT_NOPROGRESS, 1); // turn off the progress bar

	if(xml_page.memory){

		DEBUG_TRACE (DEBUG::Soundcloud, xml_page.memory);

		XMLTree doc;
		doc.read_buffer( xml_page.memory );
		XMLNode *root = doc.root();

		if (!root) {
			DEBUG_TRACE (DEBUG::Soundcloud, "no root XML node!\n");
			return "";
		}

		XMLNode *url_node = root->child("permalink-url");
		if (!url_node) {
			DEBUG_TRACE (DEBUG::Soundcloud, "no child node \"permalink-url\" found!\n");
			return "";
		}

		XMLNode *text_node = url_node->child("text");
		if (!text_node) {
			DEBUG_TRACE (DEBUG::Soundcloud, "no text node found!\n");
			return "";
		}

		free( xml_page.memory );
		return text_node->content();
	}

	return "";
};


SoundcloudUploader:: ~SoundcloudUploader()
{
	curl_easy_cleanup(curl_handle);
	curl_multi_cleanup(multi_handle);
}


void
SoundcloudUploader::setcUrlOptions()
{
	// some servers don't like requests that are made without a user-agent field, so we provide one
	curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0");
	// setup curl error buffer
	curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, errorBuffer);
	// Allow redirection
	curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);

	// Allow connections to time out (without using signals)
	curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
	curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, 30);

	curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0);
	curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0);
}

