/****************************************************************************
** Dooble - The Secure Internet Web Browser
**
** Copyright (c) 2008, 2009, 2010, 2011, 2012 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 <QFile>
#include <QFileInfo>
#include <QSettings>
#include <QByteArray>
#include <QNetworkReply>
#include <QNetworkRequest>

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

ddownloadwindowitem::ddownloadwindowitem(QWidget *parent):QWidget(parent)
{
  ui.setupUi(this);
  m_choice = 0;
  m_networkAccessManager = new QNetworkAccessManager(this);
  m_networkAccessManager->setCookieJar(dooble::s_cookies);
  dooble::s_cookies->setParent(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)));
  connect(m_networkAccessManager,
	  SIGNAL(authenticationRequired(QNetworkReply *,
					QAuthenticator *)),
	  this,
	  SIGNAL(authenticationRequired(QNetworkReply *,
					QAuthenticator *)));
  connect(m_networkAccessManager,
	  SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &,
					     QAuthenticator *)),
	  this,
	  SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &,
					     QAuthenticator *)));
  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
}

int ddownloadwindowitem::choice(void) const
{
  return m_choice;
}

void ddownloadwindowitem::init(void)
{
  m_rate = 0;
  m_total = -1;
  m_paused = false;
  m_lastSize = 0;
  m_abortedByUser = false;
  ui.progressBar->setValue(0);
  ui.progressBar->setMinimum(0);
  ui.progressBar->setMaximum(100);
  ui.pauseToolButton->setChecked(false);
  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,
				       const QDateTime &dateTime)
{
  m_url = QUrl::fromLocalFile(srcFileName);
  m_url = QUrl::fromEncoded(m_url.toEncoded(QUrl::StripTrailingSlash));
  m_dateTime = dateTime;
  m_srcFileName = srcFileName;
  m_dstFileName = dstFileName;
  ui.sourceLabel->setText(m_srcFileName);
  ui.downloadDateLabel->setText
    (m_dateTime.toString("MM/dd/yyyy hh:mm:ss AP"));

  if(QFileInfo(m_dstFileName).fileName().isEmpty())
    {
      if(m_dstFileName.isEmpty())
	ui.destinationLabel->setText("dooble.download");
      else
	ui.destinationLabel->setText(m_dstFileName);
    }
  else
    ui.destinationLabel->setText
      (QFileInfo(m_dstFileName).absoluteFilePath());

  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.
      */

      if(m_srcFileName != m_dstFileName)
	ui.retryToolButton->setVisible(true);

      ui.downloadInformationLabel->setText
	(dmisc::formattedSize(QFileInfo(m_dstFileName).size()));
      return;
    }

  /*
  ** Copy the local file.
  */

  QFile file(m_srcFileName);

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

      emit recordDownload(fileInfo.absoluteFilePath(), m_url, m_dateTime);
      QFile::remove(m_dstFileName);
      file.copy(m_dstFileName);
      file.close();
    }

  downloadFinished(file.size());
  ui.retryToolButton->setVisible(true);
  emit downloadFinished();
}

void ddownloadwindowitem::downloadUrl
(const QUrl &url,
 const QString &fileName,
 const bool isNew,
 const QDateTime &dateTime,
 const int choice)
{
  m_url = url;
  m_choice = choice;
  m_dateTime = dateTime;
  m_dstFileName = fileName;
  ui.sourceLabel->setText
    (m_url.toString(QUrl::StripTrailingSlash));
  ui.destinationLabel->setText
    (QFileInfo(m_dstFileName).absoluteFilePath());
  ui.downloadDateLabel->setText
    (m_dateTime.toString("MM/dd/yyyy hh:mm:ss AP"));

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

      if(url.toString(QUrl::StripTrailingSlash) !=
	 QUrl::fromLocalFile(fileName).toString(QUrl::StripTrailingSlash))
	ui.retryToolButton->setVisible(true);

      ui.downloadInformationLabel->setText
	(dmisc::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().trimmed() == "ftp")
	{
	  dftp *ftp = new dftp(this);

	  connect(ftp, SIGNAL(readyRead(void)),
		  this, SLOT(slotReadyRead(void)));
	  connect(ftp, SIGNAL(downloadProgress(qint64, qint64)),
		  this, SLOT(slotDataTransferProgress(qint64, qint64)));
	  connect(ftp, SIGNAL(finished(void)),
		  this, SLOT(slotDownloadFinished(void)));
	  ftp->get(m_url);
	  emit recordDownload(QFileInfo(m_dstFileName).absoluteFilePath(),
			      m_url, m_dateTime);
	}
      else if(m_url.scheme().toLower().trimmed() == "http" ||
	      m_url.scheme().toLower().trimmed() == "https")
	{
	  ui.pauseToolButton->setEnabled(false);

	  QNetworkReply *reply = 0;
	  QNetworkRequest request;

	  m_networkAccessManager->setProxy
	    (dmisc::proxyByFunctionAndUrl(DoobleDownloadType::Http, m_url));
	  request.setUrl(m_url);
	  request.setAttribute
	    (QNetworkRequest::HttpPipeliningAllowedAttribute, true);
	  reply = m_networkAccessManager->get(request);
	  reply->setParent(this);
	  reply->ignoreSslErrors();
	  connect(reply, SIGNAL(readyRead(void)),
		  this, SLOT(slotReadyRead(void)));
	  connect(reply, SIGNAL(metaDataChanged(void)),
		  this, SLOT(slotMetaDataChanged(void)));
	  connect(reply, SIGNAL(downloadProgress(qint64, qint64)),
		  this, SLOT(slotDataTransferProgress(qint64, qint64)));
	  connect(reply, SIGNAL(finished(void)),
		  this, SLOT(slotDownloadFinished(void)));
	  emit recordDownload(QFileInfo(m_dstFileName).absoluteFilePath(),
			      m_url, m_dateTime);
	}
      else
	file->deleteLater();
    }
  else
    file->deleteLater();
}

void ddownloadwindowitem::downloadHtml(const QString &html,
				       const QString &dstFileName)
{
  m_url = QUrl::fromLocalFile(dstFileName);
  m_url = QUrl::fromEncoded(m_url.toEncoded(QUrl::StripTrailingSlash));
  m_html = html;
  m_dateTime = QDateTime::currentDateTime();
  m_dstFileName = dstFileName;

  QFileInfo fileInfo(m_dstFileName);

  ui.sourceLabel->setVisible(false);
  ui.destinationLabel->setText(fileInfo.absoluteFilePath());
  ui.downloadDateLabel->setText
    (m_dateTime.toString("MM/dd/yyyy hh:mm:ss AP"));
  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());
  emit recordDownload(fileInfo.absoluteFilePath(), m_url, m_dateTime);
  emit downloadFinished();
}

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);
      ui.retryToolButton->setVisible(true);
      file->deleteLater();
    }

  dftp *ftp = qobject_cast<dftp *> (sender());

  if(ftp)
    ftp->deleteLater();

  QNetworkReply *reply = qobject_cast<QNetworkReply *> (sender());

  if(reply)
    reply->deleteLater();

  emit downloadFinished();
}

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_lastSize = 0;
      m_lastTime = QTime::currentTime();

      if(!m_total)
	ui.progressBar->setMaximum(0);
      else
	ui.progressBar->setMaximum(100);
    }

  if(m_total)
    {
      int completed = 100 * qAbs
	(static_cast<double> (done - total + m_total) /
	 qMax(1.0, static_cast<double> (m_total)));

      if(completed <= ui.progressBar->maximum())
	ui.progressBar->setValue(completed);
    }

  /*
  ** QApplication::processEvents() may cause segmentation faults.
  ** Please do not use it here.
  */

  int secs = 0;

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

      if(fileInfo.exists())
	{
	  qint64 currentSize = qRound(fileInfo.size() / 1024.0);

	  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;

	  if(m_total > 0 && m_total >= fileInfo.size())
	    ui.downloadInformationLabel->setText
	      (QString(tr("%1 KB/s (%2 of %3)")).arg(m_rate).
	       arg(dmisc::formattedSize(fileInfo.size())).
	       arg(dmisc::formattedSize(m_total)));
	  else
	    ui.downloadInformationLabel->setText
	      (QString(tr("%1 KB/s (%2)")).arg(m_rate).
	       arg(dmisc::formattedSize(fileInfo.size())));

	  m_lastSize = currentSize;
	  m_lastTime = QTime::currentTime();
	}
      else
	slotAbortDownload();
    }
}

void ddownloadwindowitem::slotAbortDownload(void)
{
  if(sender())
    m_abortedByUser = true;
  else
    m_abortedByUser = false;

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

  if(ftp)
    {
      ftp->abort();
      ftp->deleteLater();
    }

  QFile *file = findChild<QFile *> ();

  if(file)
    file->deleteLater();

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

  if(reply)
    {
      reply->abort();
      reply->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);

  QWidget *table = parentWidget();

  do
    {
      if(qobject_cast<QTableWidget *> (table))
	break;
      else if(table)
	table = table->parentWidget();
    }
  while(table != 0);

  if(qobject_cast<QTableWidget *> (table))
    for(int i = 0; i < qobject_cast<QTableWidget *> (table)->rowCount(); i++)
      if(this == qobject_cast<QTableWidget *> (table)->cellWidget(i, 0))
	{
	  qobject_cast<QTableWidget *> (table)->resizeRowToContents(i);
	  break;
	}
}

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

  QFile *file = findChild<QFile *> ();

  if(file && file->isOpen())
    return true;

  return ui.abortToolButton->isVisible();
}

void ddownloadwindowitem::downloadFinished(const qint64 fileSize)
{
  ui.downloadInformationLabel->setText(dmisc::formattedSize(fileSize));
}

void ddownloadwindowitem::slotPauseDownload(void)
{
  m_paused = !m_paused;
  slotSetIcons();

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

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

  if(ftp)
    {
      if(m_paused)
	{
	  ftp->blockSignals(true);
	  ftp->abort();
	  ftp->blockSignals(false);
	}
      else
	{
	  QFile *file = findChild<QFile *> ();

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

	  if(file)
	    ftp->get(ftp->url(), QString("REST %1").arg(file->size()));
	  else
	    ftp->get(ftp->url());
	}
    }

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

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

	  request.setUrl(reply->url());
	  request.setAttribute
	    (QNetworkRequest::HttpPipeliningAllowedAttribute, true);

	  if(file)
	    {
	      QByteArray range;

	      range.append("bytes=" + QString::number(file->size()) + "-");
	      request.setRawHeader("Range", range);
	    }

	  r = reply->manager()->get(request);
	  r->setParent(this);
	  reply->deleteLater();
	  reply = r;
	  reply->ignoreSslErrors();
	  connect(reply, SIGNAL(readyRead(void)),
		  this, SLOT(slotReadyRead(void)));
	  connect(reply, SIGNAL(metaDataChanged(void)),
		  this, SLOT(slotMetaDataChanged(void)));
	  connect(reply, SIGNAL(downloadProgress(qint64, qint64)),
		  this, SLOT(slotDataTransferProgress(qint64, qint64)));
	  connect(reply, SIGNAL(finished(void)),
		  this, SLOT(slotDownloadFinished(void)));
	}
    }

  if(m_paused)
    {
      QFileInfo fileInfo(m_dstFileName);

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

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

  if(file)
    {
      dftp *ftp = qobject_cast<dftp *> (sender());
      QNetworkReply *reply = qobject_cast<QNetworkReply *> (sender());

      if(ftp)
	file->write(ftp->readAll());
      else if(reply)
	{
	  if(!ui.pauseToolButton->isEnabled())
	    foreach(QNetworkReply::RawHeaderPair header,
		    reply->rawHeaderPairs())
	      if((header.first.toLower() == "accept-ranges" &&
		  header.second.toLower() != "none") ||
		 (header.first.toLower() == "content-range"))
		{
		  ui.pauseToolButton->setEnabled(true);
		  break;
		}

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

void ddownloadwindowitem::slotDownloadAgain(void)
{
  if(!m_srcFileName.isEmpty() && !m_dstFileName.isEmpty())
    {
      init();
      downloadFile(m_srcFileName, m_dstFileName, true,
		   QDateTime::currentDateTime());
    }
  else if(!m_url.isEmpty() && !m_dstFileName.isEmpty())
    {
      init();
      downloadUrl(m_url, m_dstFileName, true,
		  QDateTime::currentDateTime(), m_choice);
    }
  else
    return;

  QWidget *table = parentWidget();

  do
    {
      if(qobject_cast<QTableWidget *> (table))
	break;
      else
	table = table->parentWidget();
    }
  while(table != 0);

  if(qobject_cast<QTableWidget *> (table))
    for(int i = 0; i < qobject_cast<QTableWidget *> (table)->rowCount(); i++)
      if(this == qobject_cast<QTableWidget *> (table)->cellWidget(i, 0))
	{
	  qobject_cast<QTableWidget *> (table)->resizeRowToContents(i);
	  break;
	}
}

bool ddownloadwindowitem::abortedByUser(void) const
{
  return m_abortedByUser;
}

QString ddownloadwindowitem::text(void) const
{
  return m_url.toString(QUrl::StripTrailingSlash) + "\n" +
    m_dstFileName;
}

QUrl ddownloadwindowitem::url(void) const
{
  return m_url;
}

QString ddownloadwindowitem::fileName(void) const
{
  return m_dstFileName;
}

void ddownloadwindowitem::slotMetaDataChanged(void)
{
  QNetworkReply *reply = qobject_cast<QNetworkReply *> (sender());

  if(reply)
    {
      QUrl url(reply->header(QNetworkRequest::LocationHeader).toUrl());

      if(!url.isEmpty() && url.isValid())
	{
	  /*
	  ** The slotDownloadFinished() method should eliminate all items
	  ** that are currently in use.
	  */

	  reply->abort();
	  m_url = url;
	  init();
	  downloadUrl(m_url, m_dstFileName, true,
		      QDateTime::currentDateTime(), m_choice);
	}
    }
}

QDateTime ddownloadwindowitem::dateTime(void) const
{
  return m_dateTime;
}
