/*
 * Copyright (C) 2012, Gaetan Bisson <bisson@archlinux.org>.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Squaw. The simplistic Qt-based user agent for the Web.
 *
 * Squaw is a Web browser based on the Qt port of WebKit which strives to be
 * minimalistic and flexible; it achieves both by merely consisting of this
 * short, easy-to-hack C++ file.
 *
 * Compile with:
 *
 *   moc -o squaw.moc squaw.cpp
 *
 *   c++ -O2 -lQtWebKit -lQtGui -lQtNetwork -lQtCore -o squaw squaw.cpp
 */


/* ****************************************************************************
 *
 * HEADERS
 *
 */


#include <sys/stat.h>
#include <QtGui/QtGui>
#include <QtNetwork/QtNetwork>
#include <QtWebKit/QtWebKit>


/* ****************************************************************************
 *
 * FEATURES ON TOP OF QTWEBKIT
 *
 */


QString S, T, U;
QList< QPair<QByteArray, QByteArray> > H;

/* Customize user-agent string, use single window */
class Page: public QWebPage {
	public:
	QString userAgentForUrl (const QUrl &u) const { return U.isEmpty() ? QWebPage::userAgentForUrl(u) : U; }
	QWebPage *createWindow (WebWindowType t) { return this; }
};

/* Customize HTTP request headers */
class Network: public QNetworkAccessManager {
	public:
	QNetworkReply *createRequest (Operation o, const QNetworkRequest &r, QIODevice *d=0) {
		QNetworkRequest q (r);
		for (int i=0;i<H.length();i++)
			q.setRawHeader(H.at(i).first, H.at(i).second);
		return QNetworkAccessManager::createRequest(o, q, d);
	}
};

/* Implement persistent, plain text cookie storage */
class Cookies: public QNetworkCookieJar {
	QString F;
	public:
	Cookies () {
		F = S+"/cookies.txt";
		QFile f (F);
		if (f.open(QIODevice::ReadOnly)) {
			QList<QNetworkCookie> C;
			while (f.bytesAvailable())
				C.append(QNetworkCookie::parseCookies(f.readLine()));
			setAllCookies(C);
			f.close();
	  	}
	}
	bool setCookiesFromUrl (const QList<QNetworkCookie> &c, const QUrl &u) {
		bool r = this->QNetworkCookieJar::setCookiesFromUrl(c, u);
		QFile f (F);
		if (f.open(QIODevice::WriteOnly)) {
			QList<QNetworkCookie> C = allCookies();
			for (int i=0;i<C.size();i++)
				f.write(C.at(i).toRawForm()+"\n");
			f.close();
		}
		return r;
	}
};


/* ****************************************************************************
 *
 * INTERFACE LAYOUT
 *
 */


class Squaw: public QMainWindow {

	Q_OBJECT
	Page *p;
	Network *n;
	QWebView *v;
	QLineEdit *a;
	QLineEdit *s;
	QStatusBar *b;
	QPrintDialog *l;
	QList<QString> c;
	QList<QNetworkReply*> d;
	QList<QNetworkReply*> e;

	public:
	Squaw (QUrl u) {

		/* Load credentials */
		QFile f (S+"/credentials.txt");
		if (f.open(QIODevice::ReadOnly)) {
			while (f.bytesAvailable())
				c.append(QString(f.readLine().trimmed()).split(QChar('	')));
			f.close();
		}

		/* Global settings for QtWebKit */
		QWebSettings *o = QWebSettings::globalSettings();
		//o->setAttribute(QWebSettings::PluginsEnabled, true);
		o->setAttribute(QWebSettings::DnsPrefetchEnabled, true);
		o->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
		o->setAttribute(QWebSettings::PrintElementBackgrounds, false);
		o->enablePersistentStorage(S);
		o->setMaximumPagesInCache(5);
		o->setUserStyleSheetUrl(QUrl(
			"data:text/css;charset=utf-8;base64,"+QByteArray(
"body{text-rendering:optimizeLegibility;font-family:serif!important;background-color:#ddeeee;}"
"code,pre,samp,textarea,tt{font-family:monospace!important;}"
"*{font-family:inherit!important;}"
			).toBase64()));

		/* Create core objects */
		n = new Network;
		n->setCookieJar(new Cookies);
		n->setCache(new QNetworkDiskCache);
		qobject_cast<QNetworkDiskCache*>(n->cache())->setCacheDirectory(T);
		p = new Page;
		p->setNetworkAccessManager(n);
		p->setForwardUnsupportedContent(true);
		v = new QWebView;
		v->setPage(p);
		v->setFocus();
		v->load(u);
		setCentralWidget(v);

		/* Create widgets */
		s = new QLineEdit;
		a = new QLineEdit;
		a->setReadOnly(true);
		a->setFont(QFont("monospace"));
		b = statusBar();
		b->setSizeGripEnabled(false);
		b->addPermanentWidget(a, 4);
		b->addPermanentWidget(s, 1);
		l = new QPrintDialog;

		/* Event listeners */
		connect(new QShortcut(QKeySequence::Forward,  v), SIGNAL(activated()), SLOT(a_forward()));
		connect(new QShortcut(QKeySequence::Back,     v), SIGNAL(activated()), SLOT(a_back()));
		connect(new QShortcut(QKeySequence::Refresh,  v), SIGNAL(activated()), SLOT(a_reload()));
		connect(new QShortcut(QKeySequence::Print,    v), SIGNAL(activated()), SLOT(a_print()));
		connect(new QShortcut(QKeySequence::ZoomIn,   v), SIGNAL(activated()), SLOT(a_zoom_in()));
		connect(new QShortcut(QKeySequence::ZoomOut,  v), SIGNAL(activated()), SLOT(a_zoom_out()));
		connect(new QShortcut(QKeySequence("Ctrl+="), v), SIGNAL(activated()), SLOT(a_zoom_none()));
		connect(new QShortcut(QKeySequence::Find,     v), SIGNAL(activated()), SLOT(a_search_focus()));
		connect(new QShortcut(QKeySequence("Escape"), v), SIGNAL(activated()), SLOT(a_search_unfocus()));
		connect(new QShortcut(QKeySequence::FindNext, v), SIGNAL(activated()), SLOT(a_search_next()));
		connect(s, SIGNAL(textChanged(QString)),                               SLOT(a_search_change(QString)));
		connect(v, SIGNAL(urlChanged(QUrl)),                                   SLOT(a_url(QUrl)));
		connect(v, SIGNAL(titleChanged(QString)),                              SLOT(a_title(QString)));
		connect(p, SIGNAL(downloadRequested(QNetworkRequest)),                 SLOT(a_download(QNetworkRequest)));
		connect(p, SIGNAL(unsupportedContent(QNetworkReply*)),                 SLOT(a_unsupported(QNetworkReply*)));
		connect(n, SIGNAL(finished(QNetworkReply*)),                           SLOT(a_finished(QNetworkReply*)));
		connect(n, SIGNAL(sslErrors(QNetworkReply*, QList<QSslError>)),        SLOT(a_ssl(QNetworkReply*, QList<QSslError>)));
		connect(n, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)), SLOT(a_auth(QNetworkReply*, QAuthenticator*)));
	}

	/* Save downloaded data permanently */
	QString save (QNetworkReply *r) {
		QString s = QDir::homePath()+"/Desktop/"+r->url().toString()
			.section(QChar('?'), 0, 0).section(QChar('/'), -1, -1);
		while (QFileInfo(s).exists()) s += "+";
		QFile f (s);
		f.open(QIODevice::WriteOnly);
		f.write(r->readAll());
		f.close();
		return s;
	}

	protected slots:

		void a_forward () { p->triggerAction(QWebPage::Forward); }
		void a_back () { p->triggerAction(QWebPage::Back); }
		void a_reload () { p->triggerAction(QWebPage::Reload); }
		void a_print () { if (l->exec() == QDialog::Accepted) p->currentFrame()->print(l->printer()); }
		void a_zoom_in () { v->setZoomFactor(v->zoomFactor()*1.1); }
		void a_zoom_out () { v->setZoomFactor(v->zoomFactor()/1.1); }
		void a_zoom_none () { v->setZoomFactor(1); }
		void a_search_focus () { s->setFocus(); s->selectAll(); }
		void a_search_unfocus () { s->setText(""); v->setFocus(); }
		void a_search_change (QString s) { p->findText("", QWebPage::HighlightAllOccurrences); p->findText(s, QWebPage::HighlightAllOccurrences); }
		void a_search_next () { p->findText(s->text(), QWebPage::FindWrapsAroundDocument); }
		void a_url (QUrl u) { a->setText(u.toString()); }
		void a_title (QString t) { setWindowTitle(t); }

		// FIXME bind middle click to:
		// QProcess().startDetached(QString("squaw"), QStringList(request.url().toString()));

		/* Handle downloads and external/unsupported resources */
		void a_download (QNetworkRequest r) {
			d.append(n->get(r));
			a->setCursor(Qt::WaitCursor);
		}
		void a_unsupported (QNetworkReply *r) {
			e.append(r);
			a->setCursor(Qt::WaitCursor);
		}
		void a_finished (QNetworkReply *r) {
			if (d.removeAll(r)) save(r);
			if (e.removeAll(r)) {
				QString s = save(r);
				QProcess().startDetached(QString("xdg-open"), QStringList(s));
			}
			if (!(d.length()+e.length())) a->unsetCursor();
		}

		/* Mark broken SSL red, let it through */
		void a_ssl (QNetworkReply *r, QList<QSslError> e) {
			QPalette t = a->palette();
			t.setColor(QPalette::Base, Qt::red);
			a->setPalette(t);
			r->ignoreSslErrors();
		}

		/* Use credentials when possible */
		void a_auth (QNetworkReply *r, QAuthenticator *a) {
			for (int i=0;i<c.size();i+=3)
				if (r->url().toString().startsWith(c.at(i))) {
					a->setUser(c.at(i+1));
					a->setPassword(c.at(i+2));
				}
		}

};


/* ****************************************************************************
 *
 * STARTUP
 *
 */


/* Process optional arguments */
void arg (int *c, char **v[]) {
	while (*c>0)
		if (!strcmp((*v)[0], "-h")) {
			H.append(QPair<QByteArray, QByteArray>((*v)[1], (*v)[2]));
			*c -= 3;
			*v += 3;
		} else if (!strcmp((*v)[0], "-u")) {
			U = QString::fromUtf8((*v)[1]);
			*c -= 2;
			*v += 2;
		} else break;
}

/* Find resource required */
QUrl res (int c, char *v[]) {
	QString s = QString::fromUtf8(v[0]);
	QString t = QString::fromUtf8(v[1]);
	if (s.contains("://")) return s;
	if (s.contains(".")) return "http://"+s;
	for (int i=2;i<c;i++) t += "+"+QString::fromUtf8(v[i]);
	if (s=="dd")   return "https://duckduckgo.com/?q="+t;
	if (s=="gg")   return "https://encrypted.google.com/search?q="+t;
	if (s=="im")   return "https://encrypted.google.com/images?q="+t;
	if (s=="wp")   return "http://en.wikipedia.org/w/index.php?title=Special:Search&search="+t;
	if (s=="wpfr") return "http://fr.wikipedia.org/w/index.php?title=Spécial:Recherche&search="+t;
	if (s=="abc")  return "http://www.abc.net.au/news/world/"+t;
	if (s=="bbc")  return "http://www.bbc.co.uk/news/"+t;
	if (s=="fs")   return "https://bugs.archlinux.org/task/"+t;
	if (s=="bug")  return "https://bugs.archlinux.org/?dev=4787"+t;
	if (s=="pkg")  return "https://www.archlinux.org/packages/?q="+t;
	if (s=="aur")  return "https://aur.archlinux.org/packages.php?O=0&do_Search=Go&K="+t;
	if (s=="arch") return "https://www.archlinux.org/devel/"+t;
	if (s=="gm")   return "https://mail.google.com/mail/"+t;
	               return "https://duckduckgo.com/?q="+s+"+"+t;
}

/* Initialize and run */
int main (int argc, char *argv[]) {
	QApplication x (argc, argv);
	x.setApplicationName("Squaw");
	x.setApplicationVersion("0.6");

	S = QString(getenv("HOME"))+"/.squaw";
	T = "/tmp/"+QString(getenv("USER"))+"-squaw";
	umask(S_IRWXG|S_IRWXO);
	QDir().mkdir(S);
	QDir().mkdir(T);

	int c = argc-1;
	char **v = argv+1;
	arg(&c, &v);
	
	if (c<1) {
		printf("\
Squaw. The simplistic Qt-based user agent for the Web.\n\
Copyright (C) 2012 Gaetan Bisson. All rights reserved.\n\
Version 0.6; compiled on "__DATE__".\n\
\n\
Squaw does not support wandering the Web: a description of\n\
the resource to be accessed must be provided as argument.\n\
See the source code for details.\n\
");
		return 1;
	}

	(new Squaw(res(c, v)))->show();
	return x.exec();
}

#include "squaw.moc"
