#include <pch.hpp>
#include <scriptLoader.h>
#include "xmlDocumentCache.h"
#include <string.h>
#include <logger.h>
#include <libxml/HTMLparser.h>
#include <boost/bind.hpp>
#include <net.h>
#include "exceptions.h"
#include "curlHelper.h"
#include "safeMapFind.h"

XmlDocumentCache::Documents XmlDocumentCache::documents;
XmlDocumentCache::Queued XmlDocumentCache::queued;
AdHoc::Net::CurlMultiHandle XmlDocumentCache::cbf;

SimpleMessageException(XmlParseError);
SimpleMessageException(DownloadFailed);

template <class Exception>
static XmlDocumentCache::DocumentPtr helperThrow(const std::string & msg) {
	throw Exception(msg);
}
static XmlDocumentCache::DocumentPtr helperReturnDom(XmlDocumentCache::DomParserPtr dp) {
	return dp->get_document();
}
static XmlDocumentCache::DocumentPtr helperReturnDocument(XmlDocumentCache::DocumentPtr dp) {
	return dp;
}

XmlDocumentCache::DocumentPtr
XmlDocumentCache::getDocument(const Glib::ustring & url, boost::optional<std::string> encoding, bool html, bool warnings, ExecContext * ec) const
{
	Documents::const_iterator i = documents.find(url);
	if (i == documents.end()) {
		queue(url, encoding, html, warnings, ec);
		cbf.performAll();
		queued.clear();
	}
	return AdHoc::safeMapLookup<DownloadFailed>(documents, url)();
}

int
xmlReadFunc(void * context, char * buffer, int len)
{
	try {
		std::istream * strm = static_cast<std::istream *>(context);
		strm->read(buffer, len);
		return strm->gcount();
	}
	catch (const AdHoc::Net::CurlException & error) {
		return -1;
	}
}

int
xmlCloseFunc(void *)
{
	return 0;
}

void
XmlDocumentCache::queue(const Glib::ustring & url, boost::optional<std::string> encoding, bool html, bool warnings, ExecContext *) const
{
	if (queued.find(url) == queued.end()) {
		cbf.addCurl(url, [url, encoding, html, warnings](std::istream & strm) {
				if (html) {
					int flags = warnings ? 0 : XML_PARSE_NOWARNING | XML_PARSE_NOERROR;
					htmlDocPtr doc = htmlReadIO(xmlReadFunc, xmlCloseFunc, &strm, url.c_str(),
							encoding ? encoding->c_str() : nullptr, flags);
					if (!doc) {
						Logger()->messagebf(LOG_DEBUG, "Download of '%s' failed with error  '%s'", url, xmlGetLastError()->message);
						XmlDocumentCache::documents.insert(XmlDocumentCache::Documents::value_type(url,
									boost::bind(helperThrow<XmlParseError>, std::string(xmlGetLastError()->message))));
						return;
					}

					// Dirty hack alert
					// xmlpp doesn't play nicely with HTML documents...
					// sooo ummm, lie and hope it doesn't break something else
					doc->type = XML_DOCUMENT_NODE;
					// end hack

					XmlDocumentCache::documents.insert(XmlDocumentCache::Documents::value_type(url,
								boost::bind(helperReturnDocument, XmlDocumentCache::DocumentPtr(new xmlpp::Document(doc)))));
				}
				else {
					try {
						DomParserPtr doc = DomParserPtr(new xmlpp::DomParser());
						doc->parse_stream(strm);
						XmlDocumentCache::documents.insert(XmlDocumentCache::Documents::value_type(url,
									boost::bind(helperReturnDom, doc)));
					}
					catch (const AdHoc::Net::CurlException & error) {
						Logger()->messagebf(LOG_DEBUG, "Download of '%s' failed with error  '%s'", url, error.message);
						XmlDocumentCache::documents.insert(XmlDocumentCache::Documents::value_type(url,
									boost::bind(helperThrow<DownloadFailed>, error.message)));
					}
				}
			})->setopt(CURLOPT_ENCODING, "deflate, gzip");
		queued.insert(url);
	}
}

class XmlDocumentCacheClearer : public LifeCycle {
	public:
		typedef bool KeyType;

		void onIteration() override
		{
			Logger()->messagef(LOG_DEBUG, "%s: Clearing XML document cache", __PRETTY_FUNCTION__);
			XmlDocumentCache::documents.clear();
			Logger()->messagef(LOG_DEBUG, "%s: Cleared XML document cache", __PRETTY_FUNCTION__);
		}
};
NAMEDPLUGIN("XmlDocumentCacheClearer", XmlDocumentCacheClearer, LifeCycle);