#include <QFileInfo>
#include <QTemporaryDir>
#include <QCryptographicHash>
#include <QUrl>
#include <QRegularExpression>
#include <QBuffer>
#include "common/util.h"
#include "common/zip.h"
#include "kmlparser.h"

static QString kmlFile(const QList<QString> &files)
{
	for (int i = 0; i < files.size(); i++) {
		QFileInfo fi(files.at(i));
		if (fi.suffix() == "kml")
			return files.at(i);
	}

	return QString();
}

qreal KMLParser::number()
{
	bool res;
	QString str(_reader.readElementText());

	if (str.isEmpty())
		return NAN;

	qreal ret = str.toDouble(&res);
	if (!res)
		_reader.raiseError(QString("Invalid %1").arg(
		  _reader.name().toString()));

	return ret;
}

QDateTime KMLParser::time()
{
	QDateTime d = QDateTime::fromString(_reader.readElementText(),
	  Qt::ISODate);
	if (!d.isValid())
		_reader.raiseError(QString("Invalid %1").arg(
		  _reader.name().toString()));

	return d;
}

bool KMLParser::coord(Trackpoint &trackpoint)
{
	QString data = _reader.readElementText();
	const QChar *sp, *ep, *cp, *vp;
	int c = 0;
	qreal val[3];
	bool res;

	if (data.isEmpty())
		return true;

	sp = data.constData();
	ep = sp + data.size();

	for (cp = sp; cp < ep; cp++)
		if (!cp->isSpace())
			break;

	for (vp = cp; cp <= ep; cp++) {
		if (cp->isSpace() || cp->isNull()) {
			if (c > 2)
				return false;

			val[c] = QString(vp, cp - vp).toDouble(&res);
			if (!res)
				return false;

			if (c == 1) {
				trackpoint.setCoordinates(Coordinates(val[0], val[1]));
				if (!trackpoint.coordinates().isValid())
					return false;
			} else if (c == 2)
				trackpoint.setElevation(val[2]);

			while (cp->isSpace())
				cp++;
			vp = cp;
			c++;
		}
	}

	return true;
}

bool KMLParser::pointCoordinates(Waypoint &waypoint)
{
	QString data = _reader.readElementText();
	const QChar *sp, *ep, *cp, *vp;
	int c = 0;
	qreal val[3];
	bool res;


	sp = data.constData();
	ep = sp + data.size();

	for (cp = sp; cp < ep; cp++)
		if (!cp->isSpace())
			break;

	for (vp = cp; cp <= ep; cp++) {
		if (*cp == ',') {
			if (c > 1)
				return false;

			val[c] = QString(vp, cp - vp).toDouble(&res);
			if (!res)
				return false;

			c++;
			vp = cp + 1;
		} else if (cp->isSpace() || cp->isNull()) {
			if (c < 1)
				return false;

			val[c] = QString(vp, cp - vp).toDouble(&res);
			if (!res)
				return false;

			waypoint.setCoordinates(Coordinates(val[0], val[1]));
			if (!waypoint.coordinates().isValid())
				return false;
			if (c == 2)
				waypoint.setElevation(val[2]);

			while (cp->isSpace())
				cp++;
			c = 3;
		}
	}

	return true;
}

bool KMLParser::lineCoordinates(SegmentData &segment)
{
	QString data = _reader.readElementText();
	const QChar *sp, *ep, *cp, *vp;
	int c = 0;
	qreal val[3];
	bool res;


	sp = data.constData();
	ep = sp + data.size();

	for (cp = sp; cp < ep; cp++)
		if (!cp->isSpace())
			break;

	for (vp = cp; cp <= ep; cp++) {
		if (*cp == ',') {
			if (c > 1)
				return false;

			val[c] = QString(vp, cp - vp).toDouble(&res);
			if (!res)
				return false;

			c++;
			vp = cp + 1;
		} else if (cp->isSpace() || cp->isNull()) {
			if (c < 1 || c > 2)
				return false;

			val[c] = QString(vp, cp - vp).toDouble(&res);
			if (!res)
				return false;

			segment.append(Trackpoint(Coordinates(val[0], val[1])));
			if (!segment.last().coordinates().isValid())
				return false;
			if (c == 2)
				segment.last().setElevation(val[2]);

			while (cp->isSpace())
				cp++;
			c = 0;
			vp = cp;
		}
	}

 	return true;
}

bool KMLParser::polygonCoordinates(QVector<Coordinates> &points)
{
	QString data = _reader.readElementText();
	const QChar *sp, *ep, *cp, *vp;
	int c = 0;
	qreal val[3];
	bool res;


	sp = data.constData();
	ep = sp + data.size();

	for (cp = sp; cp < ep; cp++)
		if (!cp->isSpace())
			break;

	for (vp = cp; cp <= ep; cp++) {
		if (*cp == ',') {
			if (c > 1)
				return false;

			val[c] = QString(vp, cp - vp).toDouble(&res);
			if (!res)
				return false;

			c++;
			vp = cp + 1;
		} else if (cp->isSpace() || cp->isNull()) {
			if (c < 1 || c > 2)
				return false;

			val[c] = QString(vp, cp - vp).toDouble(&res);
			if (!res)
				return false;

			points.append(Coordinates(val[0], val[1]));
			if (!points.last().isValid())
				return false;

			while (cp->isSpace())
				cp++;
			c = 0;
			vp = cp;
		}
	}

	return true;
}

QDateTime KMLParser::timeStamp()
{
	QDateTime ts;

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("when"))
			ts = time();
		else
			_reader.skipCurrentElement();
	}

	return ts;
}

void KMLParser::lineString(SegmentData &segment)
{
	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("coordinates")) {
			if (!lineCoordinates(segment))
				_reader.raiseError("Invalid coordinates");
		} else
			_reader.skipCurrentElement();
	}
}

void KMLParser::linearRing(QVector<Coordinates> &coordinates)
{
	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("coordinates")) {
			if (!polygonCoordinates(coordinates))
				_reader.raiseError("Invalid coordinates");
		} else
			_reader.skipCurrentElement();
	}
}

void KMLParser::boundary(QVector<Coordinates> &coordinates)
{
	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("LinearRing"))
			linearRing(coordinates);
		else
			_reader.skipCurrentElement();
	}
}

void KMLParser::polygon(Area &area)
{
	Polygon polygon;

	while (_reader.readNextStartElement()) {
		QVector<Coordinates> path;

		if (_reader.name() == QLatin1String("outerBoundaryIs")) {
			if (!polygon.isEmpty()) {
				_reader.raiseError("Multiple polygon outerBoundaryIss");
				return;
			}
			boundary(path);
			polygon.append(path);
		} else if (_reader.name() == QLatin1String("innerBoundaryIs")) {
			if (polygon.isEmpty()) {
				_reader.raiseError("Missing polygon outerBoundaryIs");
				return;
			}
			boundary(path);
			polygon.append(path);
		} else
			_reader.skipCurrentElement();
	}

	area.append(polygon);
}

void KMLParser::point(Waypoint &waypoint)
{
	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("coordinates")) {
			if (!pointCoordinates(waypoint))
				_reader.raiseError("Invalid coordinates");
		} else
			_reader.skipCurrentElement();
	}

	if (waypoint.coordinates().isNull())
		_reader.raiseError("Missing Point coordinates");
}

void KMLParser::heartRate(SegmentData &segment)
{
	int i = 0;
	const char error[] = "Heartrate data count mismatch";

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("value")) {
			if (i < segment.size())
				segment[i++].setHeartRate(number());
			else {
				_reader.raiseError(error);
				return;
			}
		} else
			_reader.skipCurrentElement();
	}

	if (!_reader.error() && i != segment.size())
		_reader.raiseError(error);
}

void KMLParser::cadence(SegmentData &segment)
{
	int i = 0;
	const char error[] = "Cadence data count mismatch";

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("value")) {
			if (i < segment.size())
				segment[i++].setCadence(number());
			else {
				_reader.raiseError(error);
				return;
			}
		} else
			_reader.skipCurrentElement();
	}

	if (!_reader.error() && i != segment.size())
		_reader.raiseError(error);
}

void KMLParser::speed(SegmentData &segment)
{
	int i = 0;
	const char error[] = "Speed data count mismatch";

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("value")) {
			if (i < segment.size())
				segment[i++].setSpeed(number());
			else {
				_reader.raiseError(error);
				return;
			}
		} else
			_reader.skipCurrentElement();
	}

	if (!_reader.error() && i != segment.size())
		_reader.raiseError(error);
}

void KMLParser::temperature(SegmentData &segment)
{
	int i = 0;
	const char error[] = "Temperature data count mismatch";

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("value")) {
			if (i < segment.size())
				segment[i++].setTemperature(number());
			else {
				_reader.raiseError(error);
				return;
			}
		} else
			_reader.skipCurrentElement();
	}

	if (!_reader.error() && i != segment.size())
		_reader.raiseError(error);
}

void KMLParser::power(SegmentData &segment)
{
	int i = 0;
	const char error[] = "Power data count mismatch";

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("value")) {
			if (i < segment.size())
				segment[i++].setPower(number());
			else {
				_reader.raiseError(error);
				return;
			}
		} else
			_reader.skipCurrentElement();
	}

	if (!_reader.error() && i != segment.size())
		_reader.raiseError(error);
}

void KMLParser::schemaData(SegmentData &segment)
{
	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("SimpleArrayData")) {
			QXmlStreamAttributes attr = _reader.attributes();
			// There are files using capitalized names in the wild!
			QString name(attr.value("name").toString().toLower());

			if (name == QLatin1String("heartrate"))
				heartRate(segment);
			else if (name == QLatin1String("cadence"))
				cadence(segment);
			else if (name == QLatin1String("speed"))
				speed(segment);
			else if (name == QLatin1String("temperature"))
				temperature(segment);
			else if (name == QLatin1String("power"))
				power(segment);
			else
				_reader.skipCurrentElement();
		} else
			_reader.skipCurrentElement();
	}
}

void KMLParser::extendedData(SegmentData &segment)
{
	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("SchemaData"))
			schemaData(segment);
		else
			_reader.skipCurrentElement();
	}
}

void KMLParser::track(SegmentData &segment)
{
	const char error[] = "gx:coord/when element count mismatch";
	int i = 0;
	bool empty = false;

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("when")) {
			segment.append(Trackpoint());
			segment.last().setTimestamp(time());
			if (_reader.error())
				return;
		} else if (_reader.name() == QLatin1String("coord")) {
			if (i == segment.size()) {
				_reader.raiseError(error);
				return;
			} else if (!coord(segment[i])) {
				_reader.raiseError("Invalid coordinates");
				return;
			}
			if (segment.at(i).coordinates().isNull())
				empty = true;
			i++;
		} else if (_reader.name() == QLatin1String("ExtendedData"))
			extendedData(segment);
		else
			_reader.skipCurrentElement();
	}

	if (i != segment.size()) {
		_reader.raiseError(error);
		return;
	}

	/* empty (invalid) coordinates are allowed per KML specification, but
	   invalid in our data representation so get rid of the segment entries */
	if (empty) {
		SegmentData filtered;
		for (int i = 0; i < segment.size(); i++)
			if (!segment.at(i).coordinates().isNull())
				filtered.append(segment.at(i));
		segment = filtered;
	}
}

void KMLParser::multiTrack(TrackData &t)
{
	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("Track")) {
			t.append(SegmentData());
			track(t.last());
		} else
			_reader.skipCurrentElement();
	}
}

void KMLParser::photoOverlay(const Ctx &ctx, QVector<Waypoint> &waypoints,
  PointStyleMap &pointStyles, QMap<QString, QString> &map)
{
	QString img, id;
	Waypoint w;
	QMap<QString, PolygonStyle> unused;
	QMap<QString, LineStyle> unused2;
	static const QRegularExpression re("\\$\\[[^\\]]+\\]");

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("name"))
			w.setName(_reader.readElementText());
		else if (_reader.name() == QLatin1String("description"))
			w.setDescription(_reader.readElementText());
		else if (_reader.name() == QLatin1String("phoneNumber"))
			w.setPhone(_reader.readElementText());
		else if (_reader.name() == QLatin1String("address"))
			w.setAddress(_reader.readElementText());
		else if (_reader.name() == QLatin1String("TimeStamp"))
			w.setTimestamp(timeStamp());
		else if (_reader.name() == QLatin1String("Style")) {
			style(ctx.dir, ctx.zip, pointStyles, unused, unused2);
			id = QString();
		} else if (_reader.name() == QLatin1String("StyleMap"))
			styleMap(map);
		else if (_reader.name() == QLatin1String("Icon"))
			img = icon();
		else if (_reader.name() == QLatin1String("Point"))
			point(w);
		else if (_reader.name() == QLatin1String("styleUrl"))
			id = styleUrl();
		else
			_reader.skipCurrentElement();
	}

	if (!w.coordinates().isNull()) {
		PointStyleMap::iterator pit = pointStyles.find(id);
		if (pit == pointStyles.end())
			pit = pointStyles.find(map.value(id));
		w.setStyle(pit == pointStyles.end()
		  ? PointStyle(QColor(0x55, 0x55, 0x55)) : *pit);

		img.replace(re, "0");
		if (!QUrl(img).scheme().isEmpty())
			w.addLink(Link(img, "Photo Overlay"));
		else if (ctx.zip) {
			QFileInfo fi(img);
			QByteArray id(ctx.path.toUtf8() + img.toUtf8());
			QString path(Util::tempDir().path() + "/" + QString("%0.%1")
			  .arg(QCryptographicHash::hash(id, QCryptographicHash::Sha1)
			  .toHex(), QString(fi.suffix())));

			QByteArray ba(ctx.zip->file(img));
			QFile file(path);
			if (file.open(QIODevice::WriteOnly))
				file.write(ba);

			w.addImage(path);
		} else if (ctx.dir)
			w.addImage(ctx.dir->absoluteFilePath(img));

		waypoints.append(w);
	}
}

void KMLParser::multiGeometry(const Ctx &ctx, QList<TrackData> &tracks,
  QList<Area> &areas, QVector<Waypoint> &waypoints)
{
	TrackData *tp = 0;
	Area *ap = 0;

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("Point")) {
			waypoints.append(Waypoint());
			Waypoint &w = waypoints.last();
			point(w);
		} else if (_reader.name() == QLatin1String("LineString")) {
			if (!tp) {
				tracks.append(TrackData());
				tracks.last().setFile(ctx.path);
				tp = &tracks.last();
			}
			tp->append(SegmentData());
			lineString(tp->last());
		} else if (_reader.name() == QLatin1String("Polygon")) {
			if (!ap) {
				areas.append(Area());
				ap = &areas.last();
			}
			polygon(*ap);
		} else
			_reader.skipCurrentElement();
	}
}

void KMLParser::placemark(const Ctx &ctx, QList<TrackData> &tracks,
  QList<Area> &areas, QVector<Waypoint> &waypoints, PointStyleMap &pointStyles,
  PolygonStyleMap &polyStyles, LineStyleMap &lineStyles,
  QMap<QString, QString> &map)
{
	QString name, desc, phone, address, id;
	QDateTime timestamp;
	int trkIdx = tracks.size();
	int wptIdx = waypoints.size();
	int areaIdx = areas.size();

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("name"))
			name = _reader.readElementText();
		else if (_reader.name() == QLatin1String("description"))
			desc = _reader.readElementText();
		else if (_reader.name() == QLatin1String("phoneNumber"))
			phone = _reader.readElementText();
		else if (_reader.name() == QLatin1String("address"))
			address = _reader.readElementText();
		else if (_reader.name() == QLatin1String("TimeStamp"))
			timestamp = timeStamp();
		else if (_reader.name() == QLatin1String("Style")) {
			style(ctx.dir, ctx.zip, pointStyles, polyStyles, lineStyles);
			id = QString();
		} else if (_reader.name() == QLatin1String("StyleMap"))
			styleMap(map);
		else if (_reader.name() == QLatin1String("MultiGeometry"))
			multiGeometry(ctx, tracks, areas, waypoints);
		else if (_reader.name() == QLatin1String("Point")) {
			waypoints.append(Waypoint());
			point(waypoints.last());
		} else if (_reader.name() == QLatin1String("LineString")
		  || _reader.name() == QLatin1String("LinearRing")) {
			tracks.append(TrackData());
			tracks.last().setFile(ctx.path);
			tracks.last().append(SegmentData());
			lineString(tracks.last().last());
		} else if (_reader.name() == QLatin1String("Track")) {
			tracks.append(TrackData());
			tracks.last().setFile(ctx.path);
			tracks.last().append(SegmentData());
			track(tracks.last().last());
		} else if (_reader.name() == QLatin1String("MultiTrack")) {
			tracks.append(TrackData());
			tracks.last().setFile(ctx.path);
			multiTrack(tracks.last());
		} else if (_reader.name() == QLatin1String("Polygon")) {
			areas.append(Area());
			polygon(areas.last());
		} else if (_reader.name() == QLatin1String("styleUrl"))
			id = styleUrl();
		else
			_reader.skipCurrentElement();
	}

	PointStyleMap::iterator pit = pointStyles.find(id);
	if (pit == pointStyles.end())
		pit = pointStyles.find(map.value(id));
	LineStyleMap::iterator lit = lineStyles.find(id);
	if (lit == lineStyles.end())
		lit = lineStyles.find(map.value(id));
	PolygonStyleMap::iterator ait = polyStyles.find(id);
	if (ait == polyStyles.end())
		ait = polyStyles.find(map.value(id));

	for (int i = wptIdx; i < waypoints.size(); i++) {
		Waypoint &w = waypoints[i];
		w.setName(name);
		w.setDescription(desc);
		w.setTimestamp(timestamp);
		w.setAddress(address);
		w.setPhone(phone);
		w.setStyle(pit == pointStyles.end()
		  ? PointStyle(QColor(0x55, 0x55, 0x55)) : *pit);
	}
	for (int i = trkIdx; i < tracks.size(); i++) {
		TrackData &t = tracks[i];
		t.setName(name);
		t.setDescription(desc);
		t.setStyle(lit == lineStyles.end()
		  ? LineStyle(QColor(0x55, 0x55, 0x55), 2, Qt::SolidLine) : *lit);
	}
	for (int i = areaIdx; i < areas.size(); i++) {
		Area &a = areas[i];
		a.setName(name);
		a.setDescription(desc);
		a.setStyle(ait == polyStyles.end()
		  ? PolygonStyle(QColor(0x55, 0x55, 0x55, 0x99),
		  QColor(0x55, 0x55, 0x55, 0x99), 2) : *ait);
	}
}

QString KMLParser::icon()
{
	QString path;

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("href"))
			path = _reader.readElementText();
		else
			_reader.skipCurrentElement();
	}

	return path;
}

QColor KMLParser::color()
{
	QString str(_reader.readElementText());
	if (str.size() != 8)
		return QColor();

	bool aok, bok, gok, rok;
	int a = str.mid(0, 2).toInt(&aok, 16);
	int b = str.mid(2, 2).toInt(&bok, 16);
	int g = str.mid(4, 2).toInt(&gok, 16);
	int r = str.mid(6, 2).toInt(&rok, 16);

	return (aok && bok && gok && rok) ? QColor(r, g, b, a) : QColor();
}

QString KMLParser::styleUrl()
{
	QString id(_reader.readElementText());
	return (id.at(0) == '#') ? id.right(id.size() - 1) : QString();
}

void KMLParser::iconStyle(const QDir *dir, const Zip *zip, const QString &id,
  PointStyleMap &styles)
{
	QPixmap img;
	QColor c(0x55, 0x55, 0x55);

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("Icon")) {
			if (dir)
				img = QPixmap(dir->absoluteFilePath(icon()));
			else if (zip)
				img = QPixmap::fromImage(QImage::fromData(zip->file(icon())));
		} else if (_reader.name() == QLatin1String("color"))
			c = color();
		else
			_reader.skipCurrentElement();
	}

	styles.insert(id, PointStyle(img, c));
}

void KMLParser::polyStyle(const QString &id, PolygonStyleMap &styles)
{
	QColor c(0x55, 0x55, 0x55, 0x99);
	uint fill = 1, outline = 1;

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("color"))
			c = color();
		else if (_reader.name() == QLatin1String("fill"))
			fill = _reader.readElementText().toUInt();
		else if (_reader.name() == QLatin1String("outline"))
			outline = _reader.readElementText().toUInt();
		else
			_reader.skipCurrentElement();
	}

	styles.insert(id, PolygonStyle(fill ? c : QColor(),
	  outline ? c : QColor(), 2));
}

void KMLParser::lineStyle(const QString &id, LineStyleMap &styles)
{
	QColor c(0x55, 0x55, 0x55);
	double width = 2;

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("color"))
			c = color();
		else if (_reader.name() == QLatin1String("width"))
			width = _reader.readElementText().toDouble();
		else
			_reader.skipCurrentElement();
	}

	styles.insert(id, LineStyle(c, width, Qt::SolidLine));
}

void KMLParser::styleMapPair(const QString &id, QMap<QString, QString> &map)
{
	QString key, url;

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("key"))
			key = _reader.readElementText();
		else if (_reader.name() == QLatin1String("styleUrl"))
			url = styleUrl();
		else
			_reader.skipCurrentElement();
	}

	if (key == "normal")
		map.insert(id, url);
}

void KMLParser::styleMap(QMap<QString, QString> &map)
{
	QString id = _reader.attributes().value("id").toString();

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("Pair"))
			styleMapPair(id, map);
		else
			_reader.skipCurrentElement();
	}
}

void KMLParser::style(const QDir *dir, const Zip *zip,
  PointStyleMap &pointStyles, PolygonStyleMap &polyStyles,
  LineStyleMap &lineStyles)
{
	QString id = _reader.attributes().value("id").toString();

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("IconStyle"))
			iconStyle(dir, zip, id, pointStyles);
		else if (_reader.name() == QLatin1String("PolyStyle"))
			polyStyle(id, polyStyles);
		else if (_reader.name() == QLatin1String("LineStyle"))
			lineStyle(id, lineStyles);
		else
			_reader.skipCurrentElement();
	}
}

void KMLParser::folder(const Ctx &ctx, QList<TrackData> &tracks,
  QList<Area> &areas, QVector<Waypoint> &waypoints,
  PointStyleMap &pointStyles, PolygonStyleMap &polyStyles,
  LineStyleMap &lineStyles, QMap<QString, QString> &map)
{
	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("Document"))
			document(ctx, tracks, areas, waypoints);
		else if (_reader.name() == QLatin1String("Folder"))
			folder(ctx, tracks, areas, waypoints, pointStyles, polyStyles,
			  lineStyles, map);
		else if (_reader.name() == QLatin1String("Placemark"))
			placemark(ctx, tracks, areas, waypoints, pointStyles, polyStyles,
			  lineStyles, map);
		else if (_reader.name() == QLatin1String("PhotoOverlay"))
			photoOverlay(ctx, waypoints, pointStyles, map);
		else
			_reader.skipCurrentElement();
	}
}

void KMLParser::document(const Ctx &ctx, QList<TrackData> &tracks,
  QList<Area> &areas, QVector<Waypoint> &waypoints)
{
	PointStyleMap pointStyles;
	PolygonStyleMap polyStyles;
	LineStyleMap lineStyles;
	QMap<QString, QString> map;

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("Document"))
			document(ctx, tracks, areas, waypoints);
		else if (_reader.name() == QLatin1String("Folder"))
			folder(ctx, tracks, areas, waypoints, pointStyles, polyStyles,
			  lineStyles, map);
		else if (_reader.name() == QLatin1String("Placemark"))
			placemark(ctx, tracks, areas, waypoints, pointStyles, polyStyles,
			  lineStyles, map);
		else if (_reader.name() == QLatin1String("PhotoOverlay"))
			photoOverlay(ctx, waypoints, pointStyles, map);
		else if (_reader.name() == QLatin1String("Style"))
			style(ctx.dir, ctx.zip, pointStyles, polyStyles, lineStyles);
		else if (_reader.name() == QLatin1String("StyleMap"))
			styleMap(map);
		else
			_reader.skipCurrentElement();
	}
}

void KMLParser::kml(const Ctx &ctx, QList<TrackData> &tracks,
  QList<Area> &areas, QVector<Waypoint> &waypoints)
{
	PointStyleMap pointStyles;
	PolygonStyleMap polyStyles;
	LineStyleMap lineStyles;
	QMap<QString, QString> map;

	while (_reader.readNextStartElement()) {
		if (_reader.name() == QLatin1String("Document"))
			document(ctx, tracks, areas, waypoints);
		else if (_reader.name() == QLatin1String("Folder"))
			folder(ctx, tracks, areas, waypoints, pointStyles, polyStyles,
			  lineStyles, map);
		else if (_reader.name() == QLatin1String("Placemark"))
			placemark(ctx, tracks, areas, waypoints, pointStyles, polyStyles,
			  lineStyles, map);
		else if (_reader.name() == QLatin1String("PhotoOverlay"))
			photoOverlay(ctx, waypoints, pointStyles, map);
		else
			_reader.skipCurrentElement();
	}
}

bool KMLParser::parse(QFile *file, QList<TrackData> &tracks,
  QList<RouteData> &routes, QList<Area> &areas, QVector<Waypoint> &waypoints)
{
	Q_UNUSED(routes);
	QFileInfo fi(*file);

	_reader.clear();

	if (Zip::isZIP(file)) {
		Zip zip(file);

		if (!zip.isValid())
			_reader.raiseError("Invalid/unsupported ZIP file");
		else {
			QString doc(kmlFile(zip.files()));

			if (doc.isEmpty())
				_reader.raiseError("No KML file found in ZIP file");
			else {
				QByteArray data(zip.file(doc));
				QBuffer buffer(&data);
				buffer.open(QIODevice::ReadOnly);

				_reader.setDevice(&buffer);
				if (_reader.readNextStartElement()) {
					if (_reader.name() == QLatin1String("kml"))
						kml(Ctx(fi.absoluteFilePath(), 0, &zip),
						  tracks, areas, waypoints);
					else
						_reader.raiseError("Not a KML file");
				}
			}
		}
	} else {
		_reader.setDevice(file);

		if (_reader.readNextStartElement()) {
			if (_reader.name() == QLatin1String("kml")) {
				QDir dir(fi.absoluteDir());
				kml(Ctx(fi.absoluteFilePath(), &dir, 0), tracks, areas,
				  waypoints);
			} else
				_reader.raiseError("Not a KML file");
		}
	}

	return !_reader.error();
}
