#include "ganalytics.h"
#include "ganalytics_worker.h"
#include "sys.h"

#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkReply>

#include <QGuiApplication>
#include <QScreen>

const QLatin1String GAnalyticsWorker::dateTimeFormat("yyyy,MM,dd-hh:mm::ss:zzz");

GAnalyticsWorker::GAnalyticsWorker(GAnalytics *parent)
	: QObject(parent), q(parent), m_logLevel(GAnalytics::Error)
{
	m_appName = QCoreApplication::instance()->applicationName();
	m_appVersion = QCoreApplication::instance()->applicationVersion();
	m_request.setUrl(QUrl("https://www.google-analytics.com/collect"));
	m_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
	m_request.setHeader(QNetworkRequest::UserAgentHeader, getUserAgent());

	m_language = QLocale::system().name().toLower().replace("_", "-");
	m_screenResolution = getScreenResolution();

	m_timer.setInterval(m_timerInterval);
	connect(&m_timer, &QTimer::timeout, this, &GAnalyticsWorker::postMessage);
}

void GAnalyticsWorker::enable(bool state)
{
	// state change to the same is not valid.
	if(m_isEnabled == state)
	{
		return;
	}

	m_isEnabled = state;
	if(m_isEnabled)
	{
		// enable -> start doing things :)
		m_timer.start();
	}
	else
	{
		// disable -> stop the timer
		m_timer.stop();
	}
}

void GAnalyticsWorker::logMessage(GAnalytics::LogLevel level, const QString &message)
{
	if (m_logLevel > level)
	{
		return;
	}

	qDebug() << "[Analytics]" << message;
}

/**
 * Build the POST query. Adds all parameter to the query
 * which are used in every POST.
 * @param type      Type of POST message. The event which is to post.
 * @return query    Most used parameter in a query for a POST.
 */
QUrlQuery GAnalyticsWorker::buildStandardPostQuery(const QString &type)
{
	QUrlQuery query;
	query.addQueryItem("v", "1");
	query.addQueryItem("tid", m_trackingID);
	query.addQueryItem("cid", m_clientID);
	if (!m_userID.isEmpty())
	{
		query.addQueryItem("uid", m_userID);
	}
	query.addQueryItem("t", type);
	query.addQueryItem("ul", m_language);
	query.addQueryItem("vp", m_viewportSize);
	query.addQueryItem("sr", m_screenResolution);
	if(m_anonymizeIPs)
	{
		query.addQueryItem("aip", "1");
	}
	return query;
}

/**
 * Get primary screen resolution.
 * @return      A QString like "800x600".
 */
QString GAnalyticsWorker::getScreenResolution()
{
	QScreen *screen = QGuiApplication::primaryScreen();
	QSize size = screen->size();

	return QString("%1x%2").arg(size.width()).arg(size.height());
}

/**
 * Try to gain information about the system where this application
 * is running. It needs to get the name and version of the operating
 * system, the language and screen resolution.
 * All this information will be send in POST messages.
 * @return agent        A QString with all the information formatted for a POST message.
 */
QString GAnalyticsWorker::getUserAgent()
{
	return QString("%1/%2").arg(m_appName).arg(m_appVersion);
}

/**
 * The message queue contains a list of QueryBuffer object.
 * QueryBuffer holds a QUrlQuery object and a QDateTime object.
 * These both object are freed from the buffer object and
 * inserted as QString objects in a QList.
 * @return dataList     The list with concartinated queue data.
 */
QList<QString> GAnalyticsWorker::persistMessageQueue()
{
	QList<QString> dataList;
	foreach (QueryBuffer buffer, m_messageQueue)
	{
		dataList << buffer.postQuery.toString();
		dataList << buffer.time.toString(dateTimeFormat);
	}
	return dataList;
}

/**
 * Reads persistent messages from a file.
 * Gets all message data as a QList<QString>.
 * Two lines in the list build a QueryBuffer object.
 */
void GAnalyticsWorker::readMessagesFromFile(const QList<QString> &dataList)
{
	QListIterator<QString> iter(dataList);
	while (iter.hasNext())
	{
		QString queryString = iter.next();
		QString dateString = iter.next();
		QUrlQuery query;
		query.setQuery(queryString);
		QDateTime dateTime = QDateTime::fromString(dateString, dateTimeFormat);
		QueryBuffer buffer;
		buffer.postQuery = query;
		buffer.time = dateTime;
		m_messageQueue.enqueue(buffer);
	}
}

/**
 * Takes a QUrlQuery object and wrapp it together with
 * a QTime object into a QueryBuffer struct. These struct
 * will be stored in the message queue.
 */
void GAnalyticsWorker::enqueQueryWithCurrentTime(const QUrlQuery &query)
{
	QueryBuffer buffer;
	buffer.postQuery = query;
	buffer.time = QDateTime::currentDateTime();

	m_messageQueue.enqueue(buffer);
}

/**
 * This function is called by a timer interval.
 * The function tries to send a messages from the queue.
 * If message was successfully send then this function
 * will be called back to send next message.
 * If message queue contains more than one message then
 * the connection will kept open.
 * The message POST is asyncroniously when the server
 * answered a signal will be emitted.
 */
void GAnalyticsWorker::postMessage()
{
	if (m_messageQueue.isEmpty())
	{
		// queue empty -> try sending later
		m_timer.start();
		return;
	}
	else
	{
		// queue has messages -> stop timer and start sending
		m_timer.stop();
	}

	QString connection = "close";
	if (m_messageQueue.count() > 1)
	{
		connection = "keep-alive";
	}

	QueryBuffer buffer = m_messageQueue.head();
	QDateTime sendTime = QDateTime::currentDateTime();
	qint64 timeDiff = buffer.time.msecsTo(sendTime);

	if (timeDiff > fourHours)
	{
		// too old.
		m_messageQueue.dequeue();
		emit postMessage();
		return;
	}

	buffer.postQuery.addQueryItem("qt", QString::number(timeDiff));
	m_request.setRawHeader("Connection", connection.toUtf8());
	m_request.setHeader(QNetworkRequest::ContentLengthHeader, buffer.postQuery.toString().length());

	logMessage(GAnalytics::Debug, "Query string = " + buffer.postQuery.toString());

	// Create a new network access manager if we don't have one yet
	if (networkManager == NULL)
	{
		networkManager = new QNetworkAccessManager(this);
	}

	QNetworkReply *reply = networkManager->post(m_request, buffer.postQuery.query(QUrl::EncodeUnicode).toUtf8());
	connect(reply, SIGNAL(finished()), this, SLOT(postMessageFinished()));
}

/**
 * NetworkAccsessManager has finished to POST a message.
 * If POST message was successfully send then the message
 * query should be removed from queue.
 * SIGNAL "postMessage" will be emitted to send next message
 * if there is any.
 * If message couldn't be send then next try is when the
 * timer emits its signal.
 */
void GAnalyticsWorker::postMessageFinished()
{
	QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());

	int httpStausCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
	if (httpStausCode < 200 || httpStausCode > 299)
	{
		logMessage(GAnalytics::Error, QString("Error posting message: %s").arg(reply->errorString()));

		// An error ocurred. Try sending later.
		m_timer.start();
		return;
	}
	else
	{
		logMessage(GAnalytics::Debug, "Message sent");
	}

	m_messageQueue.dequeue();
	postMessage();
	reply->deleteLater();
}