/****************************************************************************
** 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://sf.net/projects/dooble
**   http://dooble.sf.net
****************************************************************************/

#include <QFtp>
#include <QFile>
#include <QFileInfo>
#include <QByteArray>
#include <QLatin1String>

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

ddownloadwindowitem::ddownloadwindowitem(void):QWidget()
{
  ui.setupUi(this);
  m_manager = 0;
  connect(ui.retryToolButton, SIGNAL(clicked(void)),
	  this, SLOT(slotDownloadAgain(void)));
  connect(ui.abortToolButton, SIGNAL(clicked(void)),
	  this, SLOT(slotAbortDownload(void)));
  connect(ui.pauseToolButton, SIGNAL(clicked(void)),
	  this, SLOT(slotPauseDownload(void)));
  init();
#ifdef Q_WS_MAC
  ui.abortToolButton->setStyleSheet
    ("QToolButton {border: none;}"
     "QToolButton::menu-button {border: none;}");
  ui.retryToolButton->setStyleSheet
    ("QToolButton {border: none;}"
     "QToolButton::menu-button {border: none;}");
  ui.pauseToolButton->setStyleSheet
	("QToolButton {border: none;}"
	 "QToolButton::menu-button {border: none;}");
#endif
}

void ddownloadwindowitem::init(void)
{
  m_rate = 0;
  m_done = 0;
  m_total = -1;
  m_ftp_total = -1;
  m_paused = false;
  m_lastSize = 0;
  m_requestId = 0;
  ui.progressBar->setValue(0);
  ui.progressBar->setMaximum(100);
  ui.pauseToolButton->setToolTip(tr("Pause Download"));
  ui.retryToolButton->setVisible(false);
  ui.downloadInformationLabel->setText(tr("0 kB/s (0 MB)"));
  slotSetIcons();
}

void ddownloadwindowitem::downloadFile(const QString &srcFileName,
				       const QString &dstFileName,
				       const bool isNew)
{
  m_srcFileName = srcFileName;
  m_dstFileName = dstFileName;

  if(QFileInfo(srcFileName).fileName().isEmpty())
    {
      if(srcFileName.isEmpty())
	ui.sourceLabel->setText(tr("File"));
      else
	ui.sourceLabel->setText(srcFileName);
    }
  else
    ui.sourceLabel->setText(QFileInfo(srcFileName).fileName());

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

  if(!isNew)
    {
      /*
      ** The item is being created on behalf of the downloads.db
      ** database.
      */

      ui.retryToolButton->setVisible(true);
      ui.downloadInformationLabel->setText
	(formattedSize(QFileInfo(m_dstFileName).size()));
      return;
    }

  /*
  ** Copy the local file.
  */

  QFile file(srcFileName);

  if(file.open(QIODevice::ReadOnly))
    {
      QFileInfo fileInfo(m_dstFileName);

      emit recordDownload(fileInfo.fileName(),
			  fileInfo.absolutePath(),
			  QUrl::fromLocalFile(m_srcFileName));
      QFile::remove(dstFileName);
      file.copy(dstFileName);
      file.close();
    }

  downloadFinished(file.size());
}

void ddownloadwindowitem::downloadFile(QNetworkReply *reply,
				       const QString &dstFileName)
{
  m_url = reply->url();
  m_dstFileName = dstFileName;
  m_manager = reply->manager();
  reply = reply->manager()->get(QNetworkRequest(m_url));
  reply->setParent(this);
  reply->ignoreSslErrors();
  reply->manager()->setProxy
    (dmisc::proxyByFunctionAndUrl(DoobleDownloadType::Http, m_url));
  ui.sourceLabel->setText(m_dstFileName);
  connect(reply, SIGNAL(readyRead(void)),
	  this, SLOT(slotReadyRead(void)));
  connect(reply, SIGNAL(downloadProgress(qint64, qint64)),
	  this, SLOT(slotDataTransferProgress(qint64, qint64)));
  connect(reply, SIGNAL(finished(void)),
	  this, SLOT(slotDownloadFinished(void)));

  QFile *file = new QFile(m_dstFileName, this);

  if(!file->open(QIODevice::WriteOnly))
    {
      file->deleteLater();
      reply->deleteLater();
    }
  else
    {
      QFileInfo fileInfo(m_dstFileName);

      emit recordDownload(fileInfo.fileName(),
			  fileInfo.absolutePath(),
			  m_url);
    }
}

void ddownloadwindowitem::downloadUrl(const QUrl &url,
				      const QString &fileName,
				      const bool isNew)
{
  m_url = url;
  m_dstFileName = fileName;
  ui.sourceLabel->setText(QFileInfo(m_dstFileName).fileName());

  if(!isNew)
    {
      /*
      ** The item is being created on behalf of the downloads.db
      ** database.
      */

      ui.abortToolButton->setVisible(false);
      ui.pauseToolButton->setVisible(false);
      ui.progressBar->setVisible(false);
      ui.retryToolButton->setVisible(true);
      ui.downloadInformationLabel->setText
	(formattedSize(QFileInfo(m_dstFileName).size()));
      return;
    }

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

  QFile *file = new QFile(m_dstFileName, this);

  if(file->open(QIODevice::WriteOnly))
    {
      if(m_url.scheme().toLower() == "ftp")
	{
	  ui.progressBar->setMaximum(0);
	  ui.abortToolButton->setEnabled(false);
	  ui.pauseToolButton->setEnabled(false);

	  QFtp *ftp = new QFtp(this);
	  QNetworkProxy proxy
	    (dmisc::proxyByFunctionAndUrl(DoobleDownloadType::Ftp, m_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", m_url.host());
	  ftp->setProperty("path", m_url.path());
	  ftp->setProperty("username", m_url.userName());
	  ftp->setProperty("password", m_url.password());
	  ftp->connectToHost(m_url.host());
	  ftp->login(m_url.userName(), m_url.password());
	  m_requestId = ftp->get
	    (QUrl::toPercentEncoding(m_url.path(), "!$&'()*+,;=:@/"), file);

	  QFileInfo fileInfo(m_dstFileName);

	  emit recordDownload(fileInfo.fileName(),
			      fileInfo.absolutePath(),
			      m_url);
	}
      else if(m_url.scheme().toLower() == "http" ||
	      m_url.scheme().toLower() == "https")
	{
	  QNetworkReply *reply = 0;
	  QNetworkAccessManager *manager = 0;

	  if(m_manager)
	    manager = m_manager;
	  else
	    manager = new QNetworkAccessManager(this);

	  reply = manager->get(QNetworkRequest(m_url));
	  reply->setParent(this);
	  reply->ignoreSslErrors();
	  reply->manager()->setProxy
	    (dmisc::proxyByFunctionAndUrl(DoobleDownloadType::Http, m_url));
	  connect(reply, SIGNAL(readyRead(void)),
		  this, SLOT(slotReadyRead(void)));
	  connect(reply, SIGNAL(downloadProgress(qint64, qint64)),
		  this, SLOT(slotDataTransferProgress(qint64, qint64)));
	  connect(reply, SIGNAL(finished(void)),
		  this, SLOT(slotDownloadFinished(void)));

	  QFileInfo fileInfo(m_dstFileName);

	  emit recordDownload(fileInfo.fileName(),
			      fileInfo.absolutePath(),
			      m_url);
	}
      else
	file->deleteLater();
    }
  else
    file->deleteLater();
}

void ddownloadwindowitem::downloadHtml(const QString &html,
				       const QString &dstFileName)
{
  m_html = html;
  m_dstFileName = dstFileName;
  ui.sourceLabel->setText(QFileInfo(m_dstFileName).fileName());
  ui.abortToolButton->setVisible(false);
  ui.pauseToolButton->setVisible(false);
  ui.progressBar->setVisible(false);

  QFile file(dstFileName);

  if(file.open(QIODevice::WriteOnly | QFile::Truncate))
    {
      QTextStream stream(&file);

      stream << m_html;
      file.close();
    }

  downloadFinished(file.size());
}

ddownloadwindowitem::~ddownloadwindowitem()
{
}

void ddownloadwindowitem::slotDownloadFinished(void)
{
  QFile *file = findChild<QFile *> ();

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

  QNetworkReply *reply = findChild<QNetworkReply *> ();

  if(reply)
    reply->deleteLater();

  QNetworkAccessManager *manager = findChild<QNetworkAccessManager *> ();

  if(manager)
    manager->deleteLater();
}

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

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

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

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

  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();

  m_requestId = 0;
  ui.retryToolButton->setVisible(true);
}

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)
    {
      m_rate = 0;
      m_total = total < 0 ? 0 : total;
      m_ftp_total = m_total;
      m_lastSize = 0;
      m_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 + m_ftp_total - m_total);

      if(ui.progressBar->maximum() != m_ftp_total)
	ui.progressBar->setMaximum(m_ftp_total);
    }
  else
    {
      ui.progressBar->setValue(done - total + m_total);

      if(ui.progressBar->maximum() != m_total)
	ui.progressBar->setMaximum(m_total);
    }

  /*
  ** QApplication::processEvents() may cause segmentation faults.
  */

  // QApplication::processEvents();

  int secs = 0;

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

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

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

	  ui.downloadInformationLabel->setText
	    (QString(tr("%1 kB/s (%2)")).arg(m_rate).
	     arg(formattedSize(fileInfo.size())));
	  m_lastSize = currentSize;
	  m_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.
				      */
    }

  QNetworkReply *reply = findChild<QNetworkReply *> ();

  if(reply)
    {
      QFile *file = findChild<QFile *> ();

      if(file)
	file->deleteLater();

      reply->abort();
      reply->deleteLater();
    }

  QNetworkAccessManager *manager = findChild<QNetworkAccessManager *> ();

  if(manager)
    manager->deleteLater();

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

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)
{
  ui.downloadInformationLabel->setText(formattedSize(fileSize));
  ui.retryToolButton->setVisible(true);
}

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 / 1024));
      else
	str = QString(tr("%1 MB")).arg
	  (QString::number((fileSize * 1.0) / 1048576, 'f', 1));
    }
  else
    str = "0 Bytes";

  return str;
}

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

  m_paused = !m_paused;
  slotSetIcons();

  if(m_paused)
    ui.pauseToolButton->setToolTip(tr("Resume Download"));
  else
    ui.pauseToolButton->setToolTip(tr("Pause Download"));

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

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

      if(m_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(DoobleDownloadType::Ftp,
					  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(m_dstFileName);
	  QFileInfo fileInfo(m_dstFileName);

	  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);
	  m_requestId = ftp->get
	    (QUrl::toPercentEncoding(path, "!$&'()*+,;=:@/"), file);
	}
    }

  QNetworkReply *reply = findChild<QNetworkReply *> ();

  if(reply)
    {
      if(m_paused)
	{
	  reply->blockSignals(true);
	  reply->abort();
	  reply->blockSignals(false);
	}
      else
	{
	  QFile *file = findChild<QFile *> ();
	  QByteArray range;
	  QNetworkReply *r = 0;
	  QNetworkRequest request(QNetworkRequest(reply->url()));

	  range.append("bytes=" + QString::number(file->size()) + "-");
	  request.setRawHeader("Range", range);
	  r = reply->manager()->get(request);
	  reply->deleteLater();
	  reply = r;
	  reply->setParent(this);
	  reply->ignoreSslErrors();
	  reply->manager()->setProxy
	    (dmisc::proxyByFunctionAndUrl(DoobleDownloadType::Http,
					  reply->url()));
	  connect(reply, SIGNAL(readyRead(void)),
		  this, SLOT(slotReadyRead(void)));
	  connect(reply, SIGNAL(downloadProgress(qint64, qint64)),
		  this, SLOT(slotDataTransferProgress(qint64, qint64)));
	  connect(reply, SIGNAL(finished(void)),
		  this, SLOT(slotDownloadFinished(void)));
	}
    }

  if(m_paused)
    {
      m_requestId = 0;

      QFileInfo fileInfo(m_dstFileName);

      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
    (dooble::s_settings.value("iconSet").toString(), QSettings::IniFormat);

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

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

void ddownloadwindowitem::slotReadyRead(void)
{
  QFile *file = findChild<QFile *> ();
  QNetworkReply *reply = findChild<QNetworkReply *> ();

  if(file && reply)
    file->write(reply->readAll());
}

void ddownloadwindowitem::slotDownloadAgain(void)
{
  init();

  if(!m_srcFileName.isEmpty() && !m_dstFileName.isEmpty())
    downloadFile(m_srcFileName, m_dstFileName);
  else if(!m_url.isEmpty() && !m_dstFileName.isEmpty())
    downloadUrl(m_url, m_dstFileName);
  else if(!m_html.isEmpty() && !m_dstFileName.isEmpty())
    downloadHtml(m_html, m_dstFileName);
}

void ddownloadwindowitem::mousePressEvent(QMouseEvent *event)
{
  QWidget::mousePressEvent(event);
  emit itemClicked();
}
