/*
 * Copyright (c) 2010-2020 Belledonne Communications SARL.
 *
 * This file is part of linphone-desktop
 * (see https://www.linphone.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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include <QDebug>
#include <QDir>
#include <QTimer>
#include <QProcess>

#include "FileExtractor.hpp"
#include "FileDownloader.hpp"
#include "app/paths/Paths.hpp"
#include "utils/Utils.hpp"

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

using namespace std;

constexpr char LinphoneBZip2_exe[] = "http://www.linphone.org/releases/windows/tools/bzip2/bzip2.exe";
constexpr char LinphoneBZip2_dll[] = "http://www.linphone.org/releases/windows/tools/bzip2/bzip2.dll";

FileExtractor::FileExtractor (QObject *parent) : QObject(parent) {}

FileExtractor::~FileExtractor () {}

void FileExtractor::extract () {
  if (mExtracting) {
    qWarning() << "Unable to extract file. Already extracting!";
    return;
  }
  setExtracting(true);
  QFileInfo fileInfo(mFile);
  if(!fileInfo.isReadable()){
	emitExtractFailed(-1);
	return;
  }

  mDestinationFile = QDir::cleanPath(mExtractFolder) + QDir::separator() + (mExtractName.isEmpty() ? fileInfo.completeBaseName() : mExtractName);
  if(QFile::exists(mDestinationFile) && !QFile::remove(mDestinationFile)){
    emitOutputError();
    return;
  }
  if( mTimer == nullptr){
	  mTimer = new QTimer(this);
	  QObject::connect(mTimer, &QTimer::timeout, this, &FileExtractor::handleExtraction);
  }
#ifdef WIN32
// Test the presence of bzip2 in the system
	int result = QProcess::execute("bzip2.exe", QStringList());
	if( result != -2 || QProcess::execute(Utils::coreStringToAppString(Paths::getToolsDirPath())+"\\bzip2.exe", QStringList())!=-2){
		mTimer->start();
	}else{// Download bzip2
		QTimer * timer = mTimer;
		FileDownloader * fileDownloader = new FileDownloader();
		int downloadStep = 0;
		fileDownloader->setUrl(QUrl(LinphoneBZip2_exe));
		fileDownloader->setDownloadFolder(Utils::coreStringToAppString(Paths::getToolsDirPath()));
		QObject::connect(fileDownloader, &FileDownloader::totalBytesChanged, this, &FileExtractor::setTotalBytes);
		QObject::connect(fileDownloader, &FileDownloader::readBytesChanged, this, &FileExtractor::setReadBytes);

		QObject::connect(fileDownloader, &FileDownloader::downloadFinished, [fileDownloader, timer, downloadStep ]()mutable {
			if( downloadStep++ == 0){
				fileDownloader->setUrl(QUrl(LinphoneBZip2_dll));
				fileDownloader->download();
			}else {
				fileDownloader->deleteLater();
				timer->start();
			}
		});

		QObject::connect(fileDownloader, &FileDownloader::downloadFailed, [fileDownloader, this]() {
			fileDownloader->deleteLater();
			emitExtractorFailed();
		});
		fileDownloader->download();
	}
#else
	mTimer->start();
#endif
}

bool FileExtractor::remove () {
  return QFile::exists(mDestinationFile) && QFile::remove(mDestinationFile);
}

QString FileExtractor::getFile () const {
  return mFile;
}

void FileExtractor::setFile (const QString &file) {
  if (mExtracting) {
    qWarning() << QStringLiteral("Unable to set file, a file is extracting.");
    return;
  }
  if (mFile != file) {
    mFile = file;
    emit fileChanged(mFile);
  }
}

QString FileExtractor::getExtractFolder () const {
  return mExtractFolder;
}

void FileExtractor::setExtractFolder (const QString &extractFolder) {
  if (mExtracting) {
    qWarning() << QStringLiteral("Unable to set extract folder, a file is extracting.");
    return;
  }
  if (mExtractFolder != extractFolder) {
    mExtractFolder = extractFolder;
    emit extractFolderChanged(mExtractFolder);
  }
}

QString FileExtractor::getExtractName () const {
  return mExtractName;
}

void FileExtractor::setExtractName (const QString &extractName) {
  if (mExtracting) {
    qWarning() << QStringLiteral("Unable to set extract name, a file is extracting.");
    return;
  }
  if (mExtractName != extractName) {
    mExtractName = extractName;
    emit extractNameChanged(mExtractName);
  }
}

bool FileExtractor::getExtracting () const {
  return mExtracting;
}

void FileExtractor::setExtracting (bool extracting) {
  if (mExtracting != extracting) {
    mExtracting = extracting;
    emit extractingChanged(extracting);
  }
}

qint64 FileExtractor::getReadBytes () const {
  return mReadBytes;
}

void FileExtractor::setReadBytes (qint64 readBytes) {
  if (mReadBytes != readBytes) {
	mReadBytes = readBytes;
	emit readBytesChanged(readBytes);
  }
}

qint64 FileExtractor::getTotalBytes () const {
  return mTotalBytes;
}

void FileExtractor::setTotalBytes (qint64 totalBytes) {
  if (mTotalBytes != totalBytes) {
	mTotalBytes = totalBytes;
	emit totalBytesChanged(totalBytes);
  }
}
void FileExtractor::clean () {
  if (mTimer) {
    mTimer->stop();
    mTimer->deleteLater();
    mTimer = nullptr;
  }
  setExtracting(false);
}

void FileExtractor::emitExtractorFailed () {
  qWarning() << QStringLiteral("Unable to extract file `%1`. bzip2 is unavailable, please install it.")
	.arg(mFile);
  clean();
  emit extractFailed();
}
void FileExtractor::emitExtractFailed (int error) {
  qWarning() << QStringLiteral("Unable to extract file with bzip2: `%1` (code: %2).")
    .arg(mFile).arg(error);
  clean();
  emit extractFailed();
}

void FileExtractor::emitExtractFinished () {
  clean();
  emit extractFinished();
}

void FileExtractor::emitOutputError () {
  qWarning() << QStringLiteral("Could not write into `%1`.")
    .arg(mDestinationFile);
  clean();
  emit extractFailed();
}

void FileExtractor::handleExtraction () {
  QString tempDestination = mDestinationFile+"."+QFileInfo(mFile).suffix();
  QStringList args;
  args.push_back("-dq");
  args.push_back(tempDestination);
  QFile::copy(mFile, tempDestination);
#ifdef WIN32
  int result = QProcess::execute("bzip2.exe", args);
  if( result == -2)
	  result = QProcess::execute(Utils::coreStringToAppString(Paths::getToolsDirPath())+"\\bzip2.exe", args);
#else
  int result = QProcess::execute("bzip2", args);
#endif
  if(QFile::exists(tempDestination))
       QFile::remove(tempDestination);
  if (result == 0)
    emitExtractFinished();
  else if (result > 0)
    emitExtractFailed(result);
  else if(result == -2)
	  emitExtractorFailed();
  else
    emitOutputError();
}
