/* Copyright 2017-2021 m-privacy GmbH.
 *
 * This 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 software 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 software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/* local */
#include "CAutotransferHandler.h"
#include "i18n.h"
#include "parameters.h"

/* other components */
#include <rfb/LogWriter.h>

/* system: m-privacy library */
#include <mp_utils.h>
#include <tgpro_environment.h>

/* system */
#if defined(WIN32) || defined(WIN64)
#include <shlobj.h>
#include <winerror.h>
#endif

#include <FL/fl_ask.H>
#include <errno.h>
#include <sys/stat.h>
#include <stdlib.h>

static const char* AUTOTRANSFER_TMP_SUBDIR = "autotransfer";
#if defined(WIN32) || defined(WIN64)
static const char* PATH_SEPARATOR = "\\";
#else
static const char* PATH_SEPARATOR = "/";
#endif
static rfb::LogWriter vlog("CAutotransferHandler");

char* CAutotransferHandler::autotransferTmpDir;

bool CAutotransferHandler::initCorrect;
char* CAutotransferHandler::autotransferDir;
char* CAutotransferHandler::autotransferDirFromServer;

MUTEX_TYPE CAutotransferHandler::processingQueueLock;
std::queue<char*> CAutotransferHandler::processingQueue;

void CAutotransferHandler::init() {
	MUTEX_INIT(&processingQueueLock);
	size_t autotransferTmpDirSize = strlen(get_tgpro_tmp_path()) + strlen(AUTOTRANSFER_TMP_SUBDIR) + 2;
	autotransferTmpDir = new char[autotransferTmpDirSize];
	snprintf(autotransferTmpDir, autotransferTmpDirSize, "%s%s%s", get_tgpro_tmp_path(), PATH_SEPARATOR, AUTOTRANSFER_TMP_SUBDIR);

	if (createAutotransferDir()) {
		vlog.debug("Successfully initialized CAutotransferHandler. Created '%s' directory (or it already existed)", autotransferDir);
		initCorrect = true;
	} else {
		vlog.error("Error initializing CAutotransferHandler. Couldn't create download directory '%s'. Autotransfer won't work!", autotransferDir);
		initCorrect = false;
	}
}

bool CAutotransferHandler::createAutotransferDir() {
	char* autotransferDirRuntime = NULL;
	if (autotransferDirFromServer && *autotransferDirFromServer) {
		autotransferDirRuntime = autotransferDirFromServer;
	} else {
		autotransferDirRuntime = autotransferFolder.getData();
	}
	// Create selected download directory
#if defined(WIN32) || defined(WIN64)
	autotransferDir = new char[4096];
	ExpandEnvironmentStrings(autotransferDirRuntime, autotransferDir, 4096);

	vlog.debug("Download directory: %s", autotransferDir);
	bool createDirSucceeded = false;
	if (strlen(autotransferDir) > 100) {
		vlog.error("%s: Download directory is too long (it has a length of %lld, and it can't be longer than 100 characters)! Not creating it...", __func__, strlen(autotransferDir));
		createDirSucceeded = false;
	} else {
		int createDirResult = SHCreateDirectoryEx(NULL, autotransferDir, NULL);
		createDirSucceeded = createDirResult == ERROR_SUCCESS || createDirResult == ERROR_ALREADY_EXISTS;
	}
#else
	if (autotransferFolder.getData()[0] == '~') { // Expand ~ for linux
		char* home = getenv("HOME");
		char* rest = autotransferDirRuntime + 1; // Skip the ~
		size_t autotransferDirSize = strlen(home) + strlen(rest) + 1;
		autotransferDir = new char[autotransferDirSize];
		snprintf(autotransferDir, autotransferDirSize, "%s%s", home, rest);
	} else {
		autotransferDir = autotransferDirRuntime;
	}
	char* mkdirCommand = new char[5006];
	snprintf(mkdirCommand, 5006, "mkdir -p \"%s\"", autotransferDir);
	bool createDirSucceeded = mkdir(autotransferDir, 0700) == 0 || errno == EEXIST;
	delete [] mkdirCommand;
#endif /* defined(WIN32) || defined(WIN64) */

	if (createDirSucceeded) {
		vlog.debug("Created '%s' directory (or it already existed)", autotransferDir);
	} else {
		vlog.error("Couldn't create download directory '%s'. Autotransfer won't work!", autotransferDir);
	}

	return createDirSucceeded;
}

void CAutotransferHandler::handle(char* autotransferFile) {
	if (!initCorrect) {
		init();
	}
	if (!initCorrect) {
		vlog.error("%s: an error occurred while trying to initialize CAutotransferHandler. Cannot handle received file '%s'", __func__, autotransferFile);
		return;
	}

	createAutotransferDir(); // Re-creates if necessary in case the user deleted the autodownload folder during a running session

	char* source = strdup((char*) autotransferFile);
	if (!source) {
		vlog.error("%s: Failed to generate temporary string (%s)", __func__, autotransferFile);
		return;
	}

	char* filename;
	if (char* lastSlash = strrchr((char*) autotransferFile, '/')) {
		filename = strdup(lastSlash + 1);
	} else if (char* lastBackSlash = strrchr((char*) autotransferFile, '\\')) {
		filename = strdup(lastBackSlash + 1);
	} else {
		filename = strdup((char*) autotransferFile);
	}
	if (!filename) {
		vlog.error("%s: Failed to generate temporary string (%s)", __func__, autotransferFile);
		return;
	}

#ifndef WIN32
	char* destination = (char*) malloc(4096);
	if (!destination) {
		vlog.error("%s: Failed to allocate memory (%s)", __func__, autotransferFile);
		return;
	}
	snprintf(destination, 4095, "%s%s%s", autotransferDir, PATH_SEPARATOR, filename);
	destination[4095] = '\0'; // We don't need the ridiculous Windows limitation anymore :)
#else
	// For some reason, the concatenation as UTF8 string doesn't work in windows using snprintf and breaks filenames
	// with non-ASCII chars (at least) when combined with users who have non-ASCII chars in their usernames
	wchar_t* autotransferDirW = utf8_char_to_wchar_t(autotransferDir);
	if (!autotransferDirW) {
		vlog.error("%s: Failed to transform autotransferDir (%s) to wchar_t", __func__, autotransferDir);
		return;
	}
	wchar_t* filenameW = utf8_char_to_wchar_t(filename);
	if (!filenameW) {
		vlog.error("%s: Failed to transform filenameW (%s) to wchar_t", __func__, filename);
		return;
	}

	wchar_t* destinationW = (wchar_t*) malloc(4096);
	if (!destinationW) {
		vlog.error("%s: Failed to allocate memory (%s)", __func__, autotransferFile);
		return;
	}
	swprintf(destinationW, 4095, L"%ls\\%ls", autotransferDirW, filenameW);
	destinationW[250] = '\0'; // Limit window filename sizew

	char* destination = convert_utf16_wchar_to_utf8_char(destinationW);
	if (!destination) {
		vlog.error("%s: Failed to transform destination (%ls) back to char*", __func__, destinationW);
		return;
	}
#endif

	if (file_exists(destination)) {
		vlog.debug("%s: File '%s' already exists. Trying to rename to avoid overwriting...", __func__, destination);
		char* path_without_ext = strdup(destination);
		char* ext;
		char* dot = strrchr(path_without_ext, '.');
		if (dot) {
			ext = strdup(dot);
			dot[0] = '\0';
		} else {
			ext = strdup("");
		}

		for (unsigned int i = 1; i <= 10000; i++) {
			snprintf(destination, 4095, "%s (%d)%s", path_without_ext, i, ext);
			destination[4095] = '\0';
			if (file_exists(destination)) {
				vlog.debug("%s: File '%s' already exists. Trying to rename to avoid overwriting...", __func__, destination);
			} else {
				break;
			}
		}

		free(path_without_ext);
		free(ext);
	}

	int moveFile = 1;
	if (file_exists(destination)) { // Leaving this here for the extreme case where the user auto-downloads more than 10000 copies of a file with the same name
		vlog.debug("%s: File '%s' already exists. Asking the user if we should overwrite it", __func__, destination);
		int overwrite = ! fl_choice(_("The file '%s' already exists.\n\nDo you wish to replace it?"), fl_yes, fl_no, 0, destination);
		if (!overwrite) { // Delete temporary file and leave the one in the user autotransfer alone
			delete_file(source);
		}
		moveFile = overwrite;
	}

	if (moveFile) {
		vlog.debug("%s: moving '%s' to '%s'", __func__, source, destination);
		mp_move(source, destination, moveFile); // move and overwrite (the user actually chose that!)
	} else {
		vlog.debug("%s: *not* moving '%s' to '%s' (the user said s/he didn't want to overwrite it)", __func__, source, destination);
	}

	free(destination);
	free(filename);
	free(source);
}

void CAutotransferHandler::addToProcessingQueue(char* filename) {
	if (!initCorrect) {
		init();
	}
	if (!initCorrect) {
		vlog.error("%s: an error occurred while trying to initialize CAutotransferHandler. Cannot handle received file '%s'", __func__, filename);
		return;
	}

	MUTEX_LOCK(&processingQueueLock);
	processingQueue.push(filename);
	MUTEX_UNLOCK(&processingQueueLock);

	vlog.debug("%s: Added file to '%s' handling queue. The vncviewer mainloop will take care of it as soon as possible", __func__, filename);
}

CAutotransferHandler::~CAutotransferHandler() {
	if (initCorrect) {
		MUTEX_DESTROY(&processingQueueLock);
	}
}
