/****************************************************************************
** Dooble - The Secure Internet Web Browser
**
** Copyright (c) 2008, 2009, 2010 Alexis Megas, 
** Gunther van Dooble, and the Dooble Team. 
** All rights reserved.
**
** License: GPL2 only:
** 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; version 2 of the License only.
**
** 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
** or see here: http://www.gnu.org/licenses/gpl.html
**
** For the WebKit library, please see: http://webkit.org.
**
** THE CODE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY
** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
** PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
** ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
** GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
** IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
** ARISING IN ANY WAY OUT OF THE USE OF THIS APPLICATION, EVEN IF ADVISED
** OF THE POSSIBILITY OF SUCH DAMAGE.
**
** Please report all praise, requests, bugs, and problems to the project
** team and administrators: http://sf.net/projects/dooble.
**
** You can find us listed at our project page. New team members are welcome.
** The name of the authors should not be used to endorse or promote products
** derived from Dooble without specific prior written permission.
** If you use this code for other projects, please let us know.
**
** Web sites:
**   http://dooble.net/projects/dooble
**   http://dooble.sf.net
**   http://www.getadooble.com
****************************************************************************/

#include <QFtp>
#include <QFile>
#include <QHttp>
#include <QtDebug>
#include <QFileInfo>
#include <QByteArray>
#include <QLatin1String>
#include <QHttpRequestHeader>

#include "dmisc.h"
#include "ddownloadwindowitem.h"

ddownloadwindowitem::ddownloadwindowitem(void):QWidget()
{
  ui.setupUi(this);
  rate = 0;
  m_done = 0;
  m_total = -1;
  ftp_total = -1;
  paused = false;
  lastSize = 0;
}

void ddownloadwindowitem::download(const QString &srcFileName,
				   const QString &dstFileName)
{
  if(QFileInfo(srcFileName).fileName().isEmpty())
    ui.sourceLabel->setText("index.html");
  else
    ui.sourceLabel->setText(QFileInfo(srcFileName).fileName());

  ui.abortToolButton->setVisible(false);
  ui.pauseToolButton->setVisible(false);
  ui.progressBar->setVisible(false);

  /*
  ** Copy the local file.
  */

  QFile file(srcFileName);

  if(file.open(QIODevice::ReadOnly))
    {
      QFile::remove(dstFileName);
      file.copy(dstFileName);
      file.close();
      downloadFinished(file.size());
    }
}

void ddownloadwindowitem::download(const QUrl &url, const QString &fileName_)
{
  requestId = 0;
  fileName = fileName_;

  if(QFileInfo(url.path()).fileName().isEmpty())
    ui.sourceLabel->setText("index.html");
  else
    ui.sourceLabel->setText(QFileInfo(url.path()).fileName());

  slotSetIcons();
  ui.abortToolButton->setEnabled(false);
  ui.pauseToolButton->setEnabled(false);
  connect(ui.abortToolButton, SIGNAL(clicked(void)),
	  this, SLOT(slotAbortDownload(void)));
  connect(ui.pauseToolButton, SIGNAL(clicked(void)),
	  this, SLOT(slotPauseDownload(void)));

  QFile *file = new QFile(fileName);

  if(file->open(QIODevice::WriteOnly))
    {
      if(url.scheme().toLower() == "ftp")
	{
	  QFtp *ftp = new QFtp(this);
	  QNetworkProxy proxy
	    (dmisc::proxyByFunctionAndUrl(dmisc::FtpDownload, url));

	  connect(ftp, SIGNAL(commandStarted(int)),
		  this, SLOT(slotRequestStarted(int)));
	  connect(ftp, SIGNAL(commandFinished(int, bool)),
		  this, SLOT(slotDownloadFinished(int, bool)));
	  connect(ftp, SIGNAL(dataTransferProgress(qint64, qint64)),
		  this, SLOT(slotDataTransferProgress(qint64, qint64)));
	  connect(this, SIGNAL(downloadAborted(int, bool)),
		  this, SLOT(slotDownloadFinished(int, bool)));
	  ftp->setProxy(proxy.hostName(), proxy.port());
	  ftp->setProperty("host", url.host());
	  ftp->setProperty("path", url.path());
	  ftp->setProperty("username", url.userName());
	  ftp->setProperty("password", url.password());
	  ftp->connectToHost(url.host());
	  ftp->login(url.userName(), url.password());
	  requestId = ftp->get
	    (QUrl::toPercentEncoding(url.path(), "!$&'()*+,;=:@/"), file);
	}
      else if(url.scheme().toLower() == "http" ||
	      url.scheme().toLower() == "https")
	{
	  QHttp *http = new QHttp(this);

	  connect(http, SIGNAL(requestStarted(int)),
		  this, SLOT(slotRequestStarted(int)));
	  connect(http, SIGNAL(requestFinished(int, bool)),
		  this, SLOT(slotDownloadFinished(int, bool)));
	  connect(http, SIGNAL(dataReadProgress(int, int)),
		  this, SLOT(slotDataReadProgress(int, int)));
	  connect(http, SIGNAL(sslErrors(const QList<QSslError> &)),
		  http, SLOT(ignoreSslErrors(void)));
	  http->setProxy
	    (dmisc::proxyByFunctionAndUrl(dmisc::HttpDownload, url));

	  QHttp::ConnectionMode mode = url.scheme().toLower() == "http" ?
	    QHttp::ConnectionModeHttp : QHttp::ConnectionModeHttps;

	  http->setHost(url.host(), mode, url.port() == -1 ? 0 : url.port());
	  http->setProperty("host", QVariant(url.host()));
	  http->setProperty("path", QVariant(url.path()));
	  requestId = http->get
	    (QUrl::toPercentEncoding(url.path(), "!$&'()*+,;=:@/"), file);
	}
      else
	file->deleteLater();
    }
  else
    file->deleteLater();
}

ddownloadwindowitem::~ddownloadwindowitem()
{
}

void ddownloadwindowitem::slotDownloadFinished(int id, bool error)
{
  Q_UNUSED(error);

  if(!error)
    if(id != requestId)
      return;

  QFtp *ftp = findChild<QFtp *> ();
  QIODevice *file = 0;

  if(ftp)
    file = ftp->currentDevice();

  QHttp *http = findChild<QHttp *> ();

  if(http)
    file = http->currentDestinationDevice();

  if(file)
    {
      file->close();
      downloadFinished(file->size());
      ui.progressBar->setVisible(false);
      ui.abortToolButton->setVisible(false);
      ui.pauseToolButton->setVisible(false);
      file->deleteLater();
    }

  if(ftp)
    ftp->deleteLater();

  if(http)
    http->deleteLater();

  requestId = 0;
}

void ddownloadwindowitem::slotDataReadProgress(int done, int total)
{
  updateProgress(static_cast<qint64> (done), static_cast<qint64> (total));
}

void ddownloadwindowitem::slotDataTransferProgress(qint64 done, qint64 total)
{
  updateProgress(done, total);
}

void ddownloadwindowitem::updateProgress(const qint64 done, const qint64 total)
{
  if(m_total == -1)
    {
      rate = 0;
      m_total = total < 0 ? 0 : total;
      ftp_total = m_total;
      lastSize = 0;
      lastTime = QTime::currentTime();
    }

  if(findChild<QFtp *> ())
    {
      /*
      ** Based on observations, total remains unchanged after a restart.
      ** We must subtract m_done from m_total whenever the user pauses
      ** the FTP download.
      */

      m_done = done;
      ui.progressBar->setValue(done + ftp_total - m_total);
      ui.progressBar->setMaximum(ftp_total);
    }
  else
    {
      ui.progressBar->setValue(done - total + m_total);
      ui.progressBar->setMaximum(m_total);
    }

  QApplication::processEvents();

  int secs = 0;

  if((secs = lastTime.secsTo(QTime::currentTime())) >= 1)
    {
      QFileInfo fileInfo(fileName);

      if(fileInfo.exists())
	{
	  int currentSize = fileInfo.size() / 1024;

	  if(currentSize >= lastSize)
	    if(200.0 * qAbs(rate - static_cast<double>
			    (currentSize - lastSize) / secs) /
	       (rate + static_cast<double>
		(currentSize - lastSize) / secs) >= 1.0)
	      rate = static_cast<double> (currentSize - lastSize) / secs;

	  ui.downloadInformationLabel->setText
	    (QString(tr("%1 kB/s (%2)")).arg(rate).
	     arg(formattedSize(fileInfo.size())));
	  lastSize = currentSize;
	  lastTime = QTime::currentTime();
	}
      else
	slotAbortDownload();
    }
}

void ddownloadwindowitem::slotRequestStarted(int id)
{
  Q_UNUSED(id);

  ui.abortToolButton->setEnabled(true);
  ui.pauseToolButton->setEnabled(true);
}

void ddownloadwindowitem::slotAbortDownload(void)
{
  QFtp *ftp = findChild<QFtp *> ();

  if(ftp)
    {
      ftp->abort();
      emit downloadAborted(-1, true); /*
				      ** The commandFinished() signal
				      ** may not be emitted. Therefore,
				      ** the slotDownloadFinished() will
				      ** not be issued. Hence the new
				      ** signal.
				      */
    }

  QHttp *http = findChild<QHttp *> ();

  if(http)
    http->abort();

  ui.progressBar->setVisible(false);
  ui.abortToolButton->setVisible(false);
  ui.pauseToolButton->setVisible(false);
  QFile::remove(fileName);
  ui.downloadInformationLabel->setText(tr("Download Aborted"));
}

bool ddownloadwindowitem::isDownloading(void) const
{
  /*
  ** What should we do for local copies of large files?
  */

  return ui.abortToolButton->isVisible();
}

void ddownloadwindowitem::downloadFinished(const int fileSize)
{
  if(fileSize >= 0)
    ui.downloadInformationLabel->setText(formattedSize(fileSize));

  emit downloadFinished();
}

QString ddownloadwindowitem::formattedSize(const int fileSize) const
{
  QString str("");

  if(fileSize >= 0)
    {
      if(fileSize < 1024)
	str = QString(tr("%1 Byte(s)")).arg(fileSize);
      else if(fileSize < 1048576)
	str = QString(tr("%1 kB")).arg
	  (QString::number((fileSize * 1.0) / 1024, 'f', 2));
      else
	str = QString(tr("%1 MB")).arg
	  (QString::number((fileSize * 1.0) / 1048576, 'f', 3));
    }
  else
    str = "0 Bytes";

  return str;
}

void ddownloadwindowitem::slotPauseDownload(void)
{
  /*
  ** Icons
  */

  paused = !paused;
  slotSetIcons();

  QFtp *ftp = findChild<QFtp *> ();

  if(ftp)
    {
      QString host(ftp->property("host").toString());
      QString path(ftp->property("path").toString());

      if(paused)
	{
	  QString password(ftp->property("password").toString());
	  QString username(ftp->property("username").toString());

	  m_total -= m_done >= 0 ? m_done : 0;

	  QIODevice *file = ftp->currentDevice();

	  if(file)
	    {
	      file->close();
	      file->deleteLater();
	    }

	  ftp->blockSignals(true);
	  ftp->abort();
	  ftp->blockSignals(false);
	  ftp->deleteLater();
	  ftp = new QFtp(this);

	  QNetworkProxy proxy
	    (dmisc::proxyByFunctionAndUrl(dmisc::FtpDownload, QUrl(path)));

	  connect(ftp, SIGNAL(commandStarted(int)),
		  this, SLOT(slotRequestStarted(int)));
	  connect(ftp, SIGNAL(commandFinished(int, bool)),
		  this, SLOT(slotDownloadFinished(int, bool)));
	  connect(ftp, SIGNAL(dataTransferProgress(qint64, qint64)),
		  this, SLOT(slotDataTransferProgress(qint64, qint64)));
	  connect(ftp, SIGNAL(rawCommandReply(int, const QString &)),
		  this, SLOT(slotRawCommandReply(int, const QString &)));
	  connect(this, SIGNAL(downloadAborted(int, bool)),
		  this, SLOT(slotDownloadFinished(int, bool)));
	  ftp->setProxy(proxy.hostName(), proxy.port());
	  ftp->setProperty("host", host);
	  ftp->setProperty("path", path);
	  ftp->setProperty("username", username);
	  ftp->setProperty("password", password);
	}
      else
	{
	  QFile *file = new QFile(fileName);
	  QFileInfo fileInfo(fileName);

	  ftp->connectToHost(ftp->property("host").toString());
	  ftp->login(ftp->property("username").toString(),
		     ftp->property("password").toString());

	  /*
	  ** According to FTP documentation, REST N instructs the FTP
	  ** server to restart file transfer at byte N when the client
	  ** issues a GET.
	  */

	  ftp->rawCommand(QLatin1String("rest ") +
			  QString::number(fileInfo.size()));
	  file->open(QIODevice::Append | QIODevice::WriteOnly);
	  requestId = ftp->get
	    (QUrl::toPercentEncoding(path, "!$&'()*+,;=:@/"), file);
	}
    }

  QHttp *http = findChild<QHttp *> ();

  if(http)
    {
      if(paused)
	{
	  QIODevice *file = http->currentDestinationDevice();

	  if(file)
	    {
	      file->close();
	      file->deleteLater();
	    }

	  http->blockSignals(true);
	  http->abort();
	  http->blockSignals(false);
	}
      else
	{
	  QFile *file = new QFile(fileName);
	  QFileInfo fileInfo(fileName);
	  QHttpRequestHeader header
	    ("GET",
	     QUrl::toPercentEncoding(http->property("path").toString(),
				     "!$&'()*+,;=:@/"));

	  file->open(QIODevice::Append | QIODevice::WriteOnly);
	  header.setValue("Host", http->property("host").toString());
	  header.setValue("Range", "bytes=" +
			  QString::number(fileInfo.size()) + "-");
	  requestId = http->request(header, 0, file);
	}
    }

  if(paused)
    {
      requestId = 0;

      QFileInfo fileInfo(fileName);

      ui.downloadInformationLabel->setText
	(QString(tr("Paused (%1)")).arg(formattedSize(fileInfo.size())));
    }
}

void ddownloadwindowitem::slotRawCommandReply(int replyCode,
					      const QString &detail)
{
  Q_UNUSED(replyCode);
  Q_UNUSED(detail);
}

void ddownloadwindowitem::slotSetIcons(void)
{
  QSettings settings;
  QSettings cfgSettings
    (settings.value("iconSet").toString(), QSettings::IniFormat);

  ui.abortToolButton->setIcon
    (QIcon(cfgSettings.value("downloadWindowItem/abortIcon").toString()));

  if(paused)
    ui.pauseToolButton->setIcon
      (QIcon(cfgSettings.value("downloadWindowItem/resumeIcon").toString()));
  else
    ui.pauseToolButton->setIcon
      (QIcon(cfgSettings.value("downloadWindowItem/pauseIcon").toString()));
}
