/****************************************************************************
** Dooble - The Secure Internet Web Browser
**
** Copyright (c) 2008, 2009, 2010, 2011, 2012 Ronald Landheer-Cieslak,
** Alexis Megas, Gunther van Dooble, and the Dooble Team. Ronald's
** remaining contribution is mostly his name.
** 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 <QUrl>
#include <QFile>
#include <QSettings>
#include <QWebFrame>
#include <QMessageBox>
#include <QWebHistory>
#include <QInputDialog>
#ifdef DOOBLE_USE_QGRAPHICSWEBVIEW
#include <QGraphicsScene>
#endif
#include <QWebElementCollection>

#include "dmisc.h"
#include "dooble.h"
#include "dwebpage.h"
#include "dwebview.h"
#include "dexceptionswindow.h"
#include "dnetworkaccessmanager.h"

dwebpage::dwebpage(QObject *parent):QWebPage(parent)
{
  m_networkAccessManager = new dnetworkaccessmanager(this);
  setNetworkAccessManager(m_networkAccessManager);
  history()->setMaximumItemCount(dooble::MAX_HISTORY_ITEMS);

  /*
  ** The initialLayoutCompleted() signal is not emitted by every
  ** page.
  */

  connect(mainFrame(),
	  SIGNAL(initialLayoutCompleted(void)),
	  this,
	  SLOT(slotInitialLayoutCompleted(void)));
  connect(this,
	  SIGNAL(popupRequested(const QString &,
				const QUrl &,
				const QDateTime &)),
	  dooble::s_popupsWindow,
	  SLOT(slotAdd(const QString &,
		       const QUrl &,
		       const QDateTime &)));
  connect(m_networkAccessManager,
	  SIGNAL(doNotTrack(const QString &,
			    const QUrl &,
			    const QDateTime &)),
	  dooble::s_dntWindow,
	  SLOT(slotAdd(const QString &,
		       const QUrl &,
		       const QDateTime &)));
  connect(m_networkAccessManager,
	  SIGNAL(blockThirdPartyHost(const QString &,
				     const QUrl &,
				     const QDateTime &)),
	  dooble::s_adBlockWindow,
	  SLOT(slotAdd(const QString &,
		       const QUrl &,
		       const QDateTime &)));
  connect(m_networkAccessManager,
	  SIGNAL(suppressHttpReferrer(const QString &,
				      const QUrl &,
				      const QDateTime &)),
	  dooble::s_httpReferrerWindow,
	  SLOT(slotAdd(const QString &,
		       const QUrl &,
		       const QDateTime &)));
  connect(m_networkAccessManager,
	  SIGNAL(urlRedirectionRequest(const QString &,
				       const QUrl &,
				       const QDateTime &)),
	  dooble::s_httpRedirectWindow,
	  SLOT(slotAdd(const QString &,
		       const QUrl &,
		       const QDateTime &)));
  connect(m_networkAccessManager,
	  SIGNAL(loadImageRequest(const QString &,
				  const QUrl &,
				  const QDateTime &)),
	  dooble::s_imageBlockWindow,
	  SLOT(slotAdd(const QString &,
		       const QUrl &,
		       const QDateTime &)));
  connect(m_networkAccessManager,
	  SIGNAL(finished(QNetworkReply *)),
	  this,
	  SLOT(slotFinished(QNetworkReply *)));
  setForwardUnsupportedContent(true);
}

void dwebpage::javaScriptAlert(QWebFrame *frame, const QString &msg)
{
  /*
  ** The default implementation of javaScriptAlert() causes
  ** segmentation faults when viewing certain Web pages.
  */

  Q_UNUSED(frame);

  if(dooble::s_settings.value("settingsWindow/javascriptEnabled",
			      true).toBool() &&
     !dooble::s_settings.value("settingsWindow/javascriptAcceptAlerts",
			       true).toBool())
    return;

  QMessageBox *mb = new QMessageBox(view());

  connect(mb,
	  SIGNAL(finished(int)),
	  mb,
	  SLOT(deleteLater(void)));
#ifdef Q_WS_MAC
  mb->setAttribute(Qt::WA_MacMetalStyle, false);
#endif
  mb->setIcon(QMessageBox::Information);
  mb->setWindowTitle(tr("JavaScript Alert"));
  mb->setWindowModality(Qt::WindowModal);
  mb->setStandardButtons(QMessageBox::Ok);
  mb->setText(msg);

  QSettings settings(dooble::s_settings.value("iconSet").toString(),
		     QSettings::IniFormat);

  for(int i = 0; i < mb->buttons().size(); i++)
    if(mb->buttonRole(mb->buttons().at(i)) == QMessageBox::AcceptRole)
      {
	mb->buttons().at(i)->setIcon
	  (QIcon(settings.value("okButtonIcon").toString()));
	mb->buttons().at(i)->setIconSize(QSize(16, 16));
      }

  mb->setWindowIcon
    (QIcon(settings.value("mainWindow/windowIcon").toString()));
  mb->show();
}

bool dwebpage::javaScriptConfirm(QWebFrame *frame, const QString &msg)
{
  Q_UNUSED(frame);
  QMessageBox *mb = new QMessageBox(view());

  connect(mb,
	  SIGNAL(finished(int)),
	  mb,
	  SLOT(deleteLater(void)));
#ifdef Q_WS_MAC
  mb->setAttribute(Qt::WA_MacMetalStyle, false);
#endif
  mb->setIcon(QMessageBox::Question);
  mb->setWindowTitle(tr("JavaScript Confirm"));
  mb->setWindowModality(Qt::WindowModal);
  mb->setStandardButtons(QMessageBox::No | QMessageBox::Yes);
  mb->setText(msg);

  QSettings settings(dooble::s_settings.value("iconSet").toString(),
		     QSettings::IniFormat);

  for(int i = 0; i < mb->buttons().size(); i++)
    if(mb->buttonRole(mb->buttons().at(i)) == QMessageBox::YesRole)
      {
	mb->buttons().at(i)->setIcon
	  (QIcon(settings.value("okButtonIcon").toString()));
	mb->buttons().at(i)->setIconSize(QSize(16, 16));
      }
    else
      {
	mb->buttons().at(i)->setIcon
	  (QIcon(settings.value("cancelButtonIcon").toString()));
	mb->buttons().at(i)->setIconSize(QSize(16, 16));
      }

  mb->setWindowIcon
    (QIcon(settings.value("mainWindow/windowIcon").toString()));

  if(mb->exec() == QMessageBox::Yes)
    return true;
  else
    return false;
}

bool dwebpage::shouldInterruptJavaScript(void)
{
  return javaScriptConfirm(0, tr("Dooble has detected a stagnant JavaScript "
				 "script. Should the script be terminated?"));
}

bool dwebpage::javaScriptPrompt(QWebFrame *frame, const QString &msg,
				const QString &defaultValue, QString *result)
{
  Q_UNUSED(frame);

  if(!result)
    return false;

  QSettings settings(dooble::s_settings.value("iconSet").toString(),
		     QSettings::IniFormat);
  QPointer<QInputDialog> id = new QInputDialog(view());

  connect(id,
	  SIGNAL(finished(int)),
	  id,
	  SLOT(deleteLater(void)));
#ifdef Q_WS_MAC
  id->setAttribute(Qt::WA_MacMetalStyle, false);
#endif
  id->setInputMode(QInputDialog::TextInput);
  id->setLabelText(msg);
  id->setWindowTitle(tr("JavaScript Prompt"));
  id->setWindowModality(Qt::WindowModal);
  id->setTextValue(defaultValue);
  id->setOkButtonText(tr("OK"));
  id->setCancelButtonText(tr("Cancel"));
  id->setWindowIcon
    (QIcon(settings.value("mainWindow/windowIcon").toString()));

  foreach(QPushButton *button, id->findChildren<QPushButton *> ())
    if(button->text() == tr("OK"))
      {
	button->setIcon
	  (QIcon(settings.value("okButtonIcon").toString()));
	button->setIconSize(QSize(16, 16));
      }
    else
      {
	button->setIcon
	  (QIcon(settings.value("cancelButtonIcon").toString()));
	button->setIconSize(QSize(16, 16));
      }

  if(id->exec() == QDialog::Accepted)
    {
      if(id)
	*result = id->textValue();
      else
	*result = "";

      return true;
    }
  else
    {
      *result = "";
      return false;
    }
}

QWebPage *dwebpage::createWindow(WebWindowType type)
{
  Q_UNUSED(type);

  if(dooble::s_settings.value("settingsWindow/blockPopups", false).toBool())
    {
      /*
      ** This is a difficult situation. The createWindow() method
      ** doesn't provide us with a URL.
      */

      bool allowPopup = false;
      dwebview *v = qobject_cast<dwebview *> (parent());

      if(v)
	allowPopup = v->checkAndClearPopup();

      if(!allowPopup)
	{
	  if(!dooble::s_popupsWindow->allowed(m_requestedUrl.host()))
	    {
	      emit exceptionRaised(dooble::s_popupsWindow, m_requestedUrl);
	      emit popupRequested(m_requestedUrl.host(),
				  m_requestedUrl,
				  QDateTime::currentDateTime());
	      return 0;
	    }
	  else
	    allowPopup = true;
	}

      if(!allowPopup)
	return 0;
    }

  if(dooble::s_settings.value("settingsWindow/javascriptEnabled",
			      true).toBool())
    {
      if(dooble::s_settings.value("settingsWindow/javascriptAllowNewWindows",
				  true).toBool())
	{
	  dview *v = 0;
	  dooble *dbl = 0;
	  dwebview *webview = qobject_cast<dwebview *> (parent());
	  Qt::MouseButton lastMouseButton = Qt::NoButton;

	  if(webview)
	    lastMouseButton = webview->mouseButtonPressed();

	  if(lastMouseButton == Qt::MiddleButton ||
	     QApplication::keyboardModifiers() == Qt::ControlModifier)
	    dbl = findDooble();

	  if(!dbl)
	    dbl = new dooble(true, findDooble());

	  v = dbl->newTab(m_requestedUrl);
	  return v->page();
	}
      else
	return 0;
    }

  /*
  ** A new window will not be created.
  */

  return 0;
}

dooble *dwebpage::findDooble(void)
{
  dooble *dbl = 0;
  QObject *prnt(this);

  do
    {
      prnt = prnt->parent();

#ifdef DOOBLE_USE_QGRAPHICSWEBVIEW
      if(qobject_cast<dwebview *> (prnt))
	prnt = qobject_cast<dwebview *> (prnt)->scene();
#endif

      dbl = qobject_cast<dooble *> (prnt);
    }
  while(prnt != 0 && dbl == 0);

  return dbl;
}

QString dwebpage::userAgentForUrl(const QUrl &url) const
{
  QString agent(QWebPage::userAgentForUrl(url));

  if(url.host().contains("gmail.com") ||
     url.host().contains("google.com"))
    {
#ifdef Q_OS_MAC
      QSysInfo::MacVersion version = QSysInfo::MacintoshVersion;

      if(version == QSysInfo::MV_10_3)
	agent.replace("Mac OS X", "Mac OS X 10_3");
      else if(version == QSysInfo::MV_10_4)
	agent.replace("Mac OS X", "Mac OS X 10_4");
      else if(version == QSysInfo::MV_10_5)
	agent.replace("Mac OS X", "Mac OS X 10_5");
      else if(version == QSysInfo::MV_10_6)
	agent.replace("Mac OS X", "Mac OS X 10_6");
      else
	agent.replace("Mac OS X", "Mac OS X 10");
#endif
    }

  return agent;
}

dwebpage::~dwebpage()
{
}

bool dwebpage::acceptNavigationRequest(QWebFrame *frame,
				       const QNetworkRequest &request,
				       NavigationType type)
{
  if(!request.url().isValid())
    return false;

  m_requestedUrl = request.url();

  if(type == QWebPage::NavigationTypeLinkClicked)
    {
      if(!frame)
	{
	  if(dooble::s_settings.value("settingsWindow/openInNewTab",
				      true).toBool())
	    {
	      dooble *dbl = findDooble();

	      if(!dbl)
		dbl = new dooble(false, 0);

	      dview *new_page = dbl->newTab(m_requestedUrl);

	      if(dooble::s_settings.value("settingsWindow/proceedToNewTab",
					  true).toBool())
		dbl->setCurrentPage(new_page);

	      return false;
	    }
	  else
	    {
	      dooble *dbl = new dooble(false, findDooble());

	      dbl->newTab(m_requestedUrl);
	      return false;
	    }
	}
      else
	{
	  dwebview *v = qobject_cast<dwebview *> (parent());
	  Qt::MouseButton lastMouseButton = Qt::NoButton;

	  if(v)
	    lastMouseButton = v->mouseButtonPressed();

	  if(lastMouseButton == Qt::MiddleButton ||
	     QApplication::keyboardModifiers() == Qt::ControlModifier)
	    {
	      dooble *dbl = findDooble();

	      if(!dbl)
		dbl = new dooble(false, 0);

	      dview *new_page = dbl->newTab(m_requestedUrl);

	      if(dooble::s_settings.value("settingsWindow/proceedToNewTab",
					  true).toBool())
		dbl->setCurrentPage(new_page);

	      return false;
	    }
	}
    }

  if(frame == mainFrame())
    /*
    ** Let's set the proxy, but only if the navigation request
    ** occurs within the main frame.
    */

    m_networkAccessManager->setProxy(dmisc::proxyByUrl(m_requestedUrl));

  return QWebPage::acceptNavigationRequest(frame, request, type);
}

void dwebpage::downloadFavicon(const QUrl &faviconUrl, const QUrl &url)
{
  if(faviconUrl.scheme().toLower().trimmed() == "http" ||
     faviconUrl.scheme().toLower().trimmed() == "https")
    {
      QNetworkProxy proxy(m_networkAccessManager->proxy());
      QNetworkReply *reply = 0;
      QNetworkRequest request(faviconUrl);

      request.setAttribute(QNetworkRequest::User, "dooble-favicon");
      m_networkAccessManager->setProxy(dmisc::proxyByUrl(faviconUrl));
      reply = m_networkAccessManager->get(request);
      m_networkAccessManager->setProxy(proxy);
      reply->setProperty("dooble-favicon", true);
      reply->setProperty("dooble-favicon-for-url", url);
      connect(reply, SIGNAL(finished(void)),
	      this, SLOT(slotIconDownloadFinished(void)));
    }
}

void dwebpage::slotIconDownloadFinished(void)
{
  QNetworkReply *reply = qobject_cast<QNetworkReply *> (sender());

  if(reply)
    {
      QUrl url(reply->attribute(QNetworkRequest::RedirectionTargetAttribute).
	       toUrl());

      /*
      ** The URL may be relative.
      */

      url = reply->url().resolved(url);

      if(!url.isEmpty() && url.isValid() &&
	 url.toString(QUrl::StripTrailingSlash) !=
	 reply->url().toString(QUrl::StripTrailingSlash))
	{
	  if(url.path().isEmpty())
	    url.setPath("favicon.ico");

	  QNetworkProxy proxy(m_networkAccessManager->proxy());
	  QNetworkReply *r = 0;
	  QNetworkRequest request(url);

	  request.setAttribute(QNetworkRequest::User, "dooble-favicon");
	  m_networkAccessManager->setProxy(dmisc::proxyByUrl(url));
	  r = m_networkAccessManager->get(request);
	  m_networkAccessManager->setProxy(proxy);
	  r->setProperty("dooble-favicon", true);
	  r->setProperty("dooble-favicon-for-url",
			 reply->property("dooble-favicon-for-url"));
	  connect(r, SIGNAL(finished(void)),
		  this, SLOT(slotIconDownloadFinished(void)));
	}
      else
	{
	  QIcon icon;
	  QPixmap pixmap;
	  QByteArray bytes(reply->readAll());

	  if(pixmap.loadFromData(bytes, "gif"))
	    icon = QIcon(pixmap);
	  else if(pixmap.loadFromData(bytes, "ico"))
	    icon = QIcon(pixmap);
	  else if(pixmap.loadFromData(bytes, "jpg"))
	    icon = QIcon(pixmap);
	  else if(pixmap.loadFromData(bytes, "jpeg"))
	    icon = QIcon(pixmap);
	  else if(pixmap.loadFromData(bytes, "png"))
	    icon = QIcon(pixmap);
	  else
	    icon = QIcon();

	  if(!icon.isNull())
	    {
	      QUrl urlForFavicon
		(reply->property("dooble-favicon-for-url").toUrl());

	      dmisc::saveIconForUrl(icon, urlForFavicon);
	      emit iconChanged();
	    }
	}

      reply->deleteLater();
    }
}

void dwebpage::slotInitialLayoutCompleted(void)
{
  QWebFrame *frame = qobject_cast<QWebFrame *> (sender());

  if(frame)
    {
      bool relFound = false;
      QWebElementCollection elements(frame->findAllElements("link"));

      foreach(QWebElement element, elements)
	{
	  QStringList attributesList = element.attributeNames();

	  qSort(attributesList.begin(), attributesList.end(),
		qGreater<QString> ());

	  foreach(QString attributeName, attributesList)
	    {
	      if(attributeName.toLower() == "rel" && (element.
						      attribute(attributeName).
						      toLower() == "icon" ||
						      element.
						      attribute(attributeName).
						      toLower() ==
						      "shortcut icon"))
		relFound = true;

	      if(relFound)
		if(attributeName.toLower() == "href")
		  {
		    QUrl url;
		    QString href(element.attribute(attributeName));

		    if(href.startsWith("http://") ||
		       href.startsWith("https://"))
		      url = QUrl::fromUserInput(href);
		    else if(href.startsWith("//"))
		      {
			url = QUrl::fromUserInput(href);
			url.setScheme(frame->url().scheme());
		      }
		    else
		      {
			url.setHost(frame->url().host());
			url.setPath(href);
			url.setScheme(frame->url().scheme());
		      }

		    if(!url.isEmpty() && url.isValid())
		      {
			downloadFavicon
			  (dmisc::correctedUrlPath(url), frame->url());
			break;
		      }
		  }
	    }

	  if(relFound)
	    break;
	}

      if(!relFound)
	{
	  QUrl url;

	  url.setHost(frame->url().host());
	  url.setPath("favicon.ico");
	  url.setScheme(frame->url().scheme());
	  downloadFavicon(url, frame->url());
	}
    }
}

void dwebpage::slotFinished(QNetworkReply *reply)
{
  if(!reply)
    return;
  else if(reply->error() == QNetworkReply::NoError)
    return;
  else if(!reply->url().isValid() || !m_requestedUrl.isValid())
    return;
  else if(!dmisc::isSchemeAcceptedByDooble(reply->url().scheme()) ||
	  !dmisc::isSchemeAcceptedByDooble(m_requestedUrl.scheme()))
    return;

  switch(reply->error())
    {
    case QNetworkReply::ConnectionRefusedError:
    case QNetworkReply::HostNotFoundError:
    case QNetworkReply::ProtocolInvalidOperationError:  
    case QNetworkReply::ProtocolUnknownError:
    case QNetworkReply::ProxyConnectionClosedError:
    case QNetworkReply::ProxyConnectionRefusedError:
    case QNetworkReply::ProxyNotFoundError:
    case QNetworkReply::RemoteHostClosedError:
    case QNetworkReply::TimeoutError:
    case QNetworkReply::UnknownContentError:
    case QNetworkReply::UnknownNetworkError:
    case QNetworkReply::UnknownProxyError:
      {
	break;
      }
    case QNetworkReply::ContentNotFoundError:
      {
	if(!dooble::s_settings.value("mainWindow/offlineMode", false).toBool())
	  return;

	break;
      }
    default:
      {
	return;
      }
    }

  if(mainFrame() !=
     qobject_cast<QWebFrame *> (reply->request().originatingObject()))
    /*
    ** We only wish to process replies that originated from the page's
    ** main frame.
    */

    return;

  if(reply->url().toString(QUrl::StripTrailingSlash) ==
     m_requestedUrl.toString(QUrl::StripTrailingSlash))
    {
      dmisc::logError
	(tr("dwebpage::slotFinished(): "
	    "The URL %1 generated an error (%2:%3).").
	 arg(reply->url().toString(QUrl::StripTrailingSlash)).
	 arg(reply->error()).
	 arg(reply->errorString()));
      emit loadErrorPage(m_requestedUrl);
    }
}
