#include "src/updaters/retroarch-updater.hpp"

#if !defined(__APPLE__) && !defined(FLATPAK_VERSION)
#include <QProgressDialog>
#include <QMessageBox>
#include <QCoreApplication>
#include <functional>
#include <cstdlib>
#include <memory>
#include <thread>
#include "src/core/qthread.hpp"
#include "src/core/version.hpp"
#include "src/core/updates.hpp"
#include "src/core/file-controller.hpp"
#include "src/core/retroarch.hpp"
#include "src/core/retroarch-sdl-fix.hpp"
#include "src/core/zip.hpp"
#include "src/polyfill/base-directory.hpp"
#include "src/polyfill/process.hpp"
#include "src/ui/download-dialog.hpp"

static const Version LKG_VERSION = { 1, 20, 0 };

static inline string buildDownloadUrl( const Version &version ) {
#ifdef __linux__
	return "https://buildbot.libretro.com/stable/"s + version.toString() + "/linux/x86_64/RetroArch.7z";
#else
	#ifdef _WIN64
		return "https://buildbot.libretro.com/stable/"s + version.toString() + "/windows/x86_64/RetroArch.7z";
	#else
		return "https://buildbot.libretro.com/stable/"s + version.toString() + "/windows/x86/RetroArch.7z";
	#endif
#endif
}

#if defined(__linux__)
#define RA_ROOT "RetroArch-Linux-x86_64"
#elif defined(_WIN64)
#define RA_ROOT "RetroArch-Win64"
#else
#define RA_ROOT "RetroArch-Win32"
#endif

static void installWorker(
	QProgressDialog *dialog,
	std::shared_ptr<bool> cancelled,
	bool *result
) {
	const fs::path installDir = RetroArch::getExePath().parent_path();

	fs::error_code err;
	fs::remove_all( installDir, err );
	fs::create_directories( installDir, err );

#ifdef _WIN32
	const string unzipper = (BaseDir::program() / L"7za.exe").u8string();
#else
	string unzipper = "7zr";
	if( std::system( "which 7zr" ) != 0 ) {
		// Some RPM based distros don't provide 7zr in their p7zip package
		if( std::system( "which 7za" ) == 0 ) {
			unzipper = "7za";
		} else if( std::system( "which 7z" ) == 0 ) {
			unzipper = "7z";
		}
	}
#endif

	AsyncProcess extraction(
		unzipper,
		{
			"x", (BaseDir::temp() / _NFS("retroarch.7z")).u8string(),
			"-t7z",
			"-aoa",
			"-y",
			"-o"s + installDir.u8string()
		}
	);
	const uint exitCode = extraction.join();

	if( exitCode == 0 ) {
		if( fs::isDirectorySafe( installDir / RA_ROOT ) ) {
			const fs::path tempDir = installDir.parent_path() / _NFS("retroarch-temp");
			fs::remove_all( tempDir, err );
			fs::rename( installDir / RA_ROOT, tempDir, err );
			fs::remove_all( installDir, err );
			fs::rename( tempDir, installDir, err );

#ifdef __linux__
			const fs::path retroData = BaseDir::data() / "retro-data";
			const fs::path tempData = installDir / "RetroArch-Linux-x86_64.AppImage.home" / ".config" / "retroarch";
			fs::remove_all( retroData / "assets", err );
			fs::rename( tempData / "assets", retroData / "assets" );
			fs::remove_all( retroData / "filters", err );
			fs::rename( tempData / "filters", retroData / "filters" );
			fs::remove_all( retroData / "overlays", err );
			fs::rename( tempData / "overlays", retroData / "overlays" );
			fs::remove_all( retroData / "shaders", err );
			fs::rename( tempData / "shaders", retroData / "shaders" );
			fs::remove_all( installDir / "RetroArch-Linux-x86_64.AppImage.home" );
#endif
		}
	}

	QtThread::safeAsync( [=](){
		if( *cancelled ) return;
		*result = (exitCode == 0 && fs::existsSafe( RetroArch::getExePath() ));
		dialog->close();
	});
}

static inline bool downloadAndInstall( const Version &version ) {
	const fs::path tempBundlePath = BaseDir::temp() / _NFS("retroarch.7z");

	fs::error_code err;
	fs::remove( tempBundlePath, err );

	DownloadResult status = DownloadDialog::download(
		QT_TRANSLATE_NOOP( "DownloadDialog", "Downloading RetroArch" ),
		buildDownloadUrl( version ),
		tempBundlePath
	);

	if( !status.success ) {
		QMessageBox::critical(
			nullptr,
			QCoreApplication::translate( "WindowsRetroArchUpdater", "Download Failed" ),
			QCoreApplication::translate( "WindowsRetroArchUpdater", "Failed to download RetroArch." ).append( '\n' ).append( status.errorMessage.c_str() )
		);
		return false;
	}

	QProgressDialog dialog;
	dialog.setRange( 0, 0 );
	dialog.setLabelText( QCoreApplication::translate( "WindowsRetroArchUpdater", "Installing RetroArch" ) );

	bool success = false;
	std::shared_ptr<bool> cancelled( new bool( false ) );

	std::thread( installWorker, &dialog, cancelled, &success ).detach();

	dialog.exec();
	*cancelled = true;

	if( !success ) {
		fs::error_code err;
		fs::remove_all( RetroArch::getExePath().parent_path(), err );

		QMessageBox::critical(
			nullptr,
			QCoreApplication::translate( "WindowsRetroArchUpdater", "Installation Error" ),
			QCoreApplication::translate( "WindowsRetroArchUpdater", "An error occurred attempting to uncompress the portable RetroArch bundle" )
		);
	}

	fs::remove( tempBundlePath, err );
	return success;
}

bool RetroArchUpdater::install() {
	if( downloadAndInstall( LKG_VERSION ) ) {
		InstalledVersionsInfo versions = FileController::loadInstalledVersions();
		versions.retroArchVersion.version = LKG_VERSION;
		FileController::saveInstalledVersions( versions );
#ifdef _WIN32
		RetroArch::fixSdlLibrary();
#endif
		return true;
	}

	return false;
}

void RetroArchUpdater::update() {
	InstalledVersionsInfo versions = FileController::loadInstalledVersions();
	Version &version = versions.retroArchVersion.version;

	if( LKG_VERSION <= version ) {
		return;
	}

	if( QMessageBox::question(
		nullptr,
		QCoreApplication::translate( "WindowsRetroArchUpdater", "Install Update?" ),
		QCoreApplication::translate( "WindowsRetroArchUpdater", "An update for RetroArch is available. Would you like to install it now?" )
	) != QMessageBox::Yes ) {
		return;
	}

	if( downloadAndInstall( LKG_VERSION ) ) {
		version = LKG_VERSION;
		FileController::saveInstalledVersions( versions );
	}

#ifdef _WIN32
	RetroArch::fixSdlLibrary();
#endif
}

#endif
