/***************************************************************************
 *   Copyright (C) 2010 by Volker Lanz <vl@fidra.de>                       *
 *                                                                         *
 *   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; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   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.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA            *
 ***************************************************************************/

#include "fileimport/gpximporter.h"

#include "core/trackpoint.h"
#include "core/activity.h"
#include "core/lap.h"
#include "core/track.h"

#include "util/xml.h"

#include <QString>
#include <QFile>
#include <QXmlStreamReader>
#include <QDateTime>
#include <QDebug>

GpxImporter::GpxImporter() :
	ImporterInterface("gpx")
{
}

static void parseGarminTrackPointExtension(QXmlStreamReader& xml, TrackPoint& trp)
{
	while (xmlReadNextStartElement(xml))
	{
		if (xml.name() == "hr")
			trp.setHeartRate(xml.readElementText().toUInt());
		else if (xml.name() == "cad")
			trp.setCadence(xml.readElementText().toUInt());
		else
		{
			qDebug() << "unknown tag in <tp1:TrackPointExtension>: " << xml.name();
			xmlSkipCurrentElement(xml);
		}
	}
}

static void parseGpxTrackPoint(QXmlStreamReader& xml, Track& track)
{
	TrackPoint* tp = new TrackPoint();

	if (xml.attributes().hasAttribute("lat") && xml.attributes().hasAttribute("lon"))
	{
		const double latitude = xml.attributes().value("lat").toString().toDouble();
		const double longitude = xml.attributes().value("lon").toString().toDouble();
		tp->setPosition(Position(latitude, longitude));
	}

	while (xmlReadNextStartElement(xml))
	{
		if (xml.name() == "ele")
			tp->setAltitude(xml.readElementText().toDouble() * 1000);
		else if (xml.name() == "time")
			tp->setTimeStamp(QDateTime::fromString(xml.readElementText(), Qt::ISODate));
		else if (xml.name() == "course")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "speed")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "magvar")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "geoidheight")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "name")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "cmt")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "desc")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "src")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "url")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "urlname")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "sym")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "type")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "fix")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "sat")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "hdop")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "vdop")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "pdop")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "ageofdgpsdata")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "dgpsid")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "extensions")
		{
			while (xmlReadNextStartElement(xml))
			{
				if (xml.name() == "TrackPointExtension")
					parseGarminTrackPointExtension(xml, *tp);
				else
				{
					qDebug() << "unknown tag in <extensions>: " << xml.name();
					xmlSkipCurrentElement(xml);
				}
			}
		}
		else
		{
			qDebug() << "unknown tag in <trkpt>: " << xml.name();
			xmlSkipCurrentElement(xml);
		}
	}

	track.addTrackPoint(tp);
}

static void parseGpxTrackSegment(QXmlStreamReader& xml, Track& track)
{
	while (xmlReadNextStartElement(xml))
	{
		if (xml.name() == "trkpt")
			parseGpxTrackPoint(xml, track);
		else
		{
			qDebug() << "unknown tag in <trkseg>: " << xml.name();
			xmlSkipCurrentElement(xml);
		}
	}
}

static void parseGpxTrack(QXmlStreamReader& xml, Lap& lap)
{
	while (xmlReadNextStartElement(xml))
	{
		if (xml.name() == "name")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "cmt")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "desc")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "src")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "url")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "urlname")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "number")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "trkseg")
		{
			Track* t = new Track();
			parseGpxTrackSegment(xml, *t);
			lap.addTrack(t);
		}
		else
		{
			qDebug() << "unknown tag in <trk>: " << xml.name();
			xmlSkipCurrentElement(xml);
		}
	}

	if (!lap.startTime().isValid())
		lap.setStartTime(lap.calcStartTime());

	lap.setAverageCadence(lap.calcAverageCadence());

	quint32 avgHR = 0;
	quint32 maxHR = 0;
	lap.calcHeartRates(avgHR, maxHR);
	lap.setAverageHeartRate(avgHR);
	lap.setMaximumHeartRate(maxHR);

	lap.setDistance(lap.calcDistance());
	lap.setDuration(lap.calcDuration());

	lap.setMaximumSpeed(Lap::calcMaxSpeed(lap));
}

static void parseMetaDataElement(QXmlStreamReader& xml, Activity& activity)
{
	while (xmlReadNextStartElement(xml))
	{
		if (xml.name() == "name")
		{
			activity.setName(xml.readElementText());
			const QDateTime ts = QDateTime::fromString(xml.readElementText(), Qt::ISODate);
			if (ts.isValid())
				activity.setTimeStamp(ts);
		}
		else if (xml.name() == "desc")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "author")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "copyright")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "link")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "time")
			activity.setTimeStamp(QDateTime::fromString(xml.readElementText(), Qt::ISODate));
		else if (xml.name() == "keywords")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "bounds")
			xmlSkipCurrentElement(xml);
		else
		{
			qDebug() << "unknown tag in <metadata>: " << xml.name();
			xmlSkipCurrentElement(xml);
		}
	}
}

void GpxImporter::parseRootElement(QXmlStreamReader& xml, Activities& activities)
{
	enum GPXVersion { gpx10, gpx11 };

	GPXVersion gpxVersion = xml.attributes().value("version") == "1.1" ? gpx11 : gpx10;

	Activity* activity = new Activity();
	QDateTime timeStamp;

	while (xmlReadNextStartElement(xml))
	{
		if (xml.name() == "wpt")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "rte")
			xmlSkipCurrentElement(xml);
		else if (xml.name() == "trk")
		{
			Lap* lap = new Lap();
			parseGpxTrack(xml, *lap);
			activity->addLap(lap);
		}
		else if (gpxVersion == gpx10)
		{
			if (xml.name() == "name")
			{
				activity->setName(xml.readElementText());
				const QDateTime ts = QDateTime::fromString(xml.readElementText(), Qt::ISODate);
				if (ts.isValid())
					activity->setTimeStamp(ts);
			}
			else if (xml.name() == "desc")
				xmlSkipCurrentElement(xml);
			else if (xml.name() == "author")
				xmlSkipCurrentElement(xml);
			else if (xml.name() == "email")
				xmlSkipCurrentElement(xml);
			else if (xml.name() == "url")
				xmlSkipCurrentElement(xml);
			else if (xml.name() == "urlname")
				xmlSkipCurrentElement(xml);
			else if (xml.name() == "time")
				activity->setTimeStamp(QDateTime::fromString(xml.readElementText(), Qt::ISODate));
			else if (xml.name() == "keywords")
				xmlSkipCurrentElement(xml);
			else if (xml.name() == "bounds")
				xmlSkipCurrentElement(xml);
			else
			{
				qDebug() << "unknown tag in <gpx> for version 1.0: " << xml.name();
				xmlSkipCurrentElement(xml);
			}
		}
		else if (gpxVersion == gpx11)
		{
			if (xml.name() == "metadata")
				parseMetaDataElement(xml, *activity);
			else
			{
				qDebug() << "unknown tag in <gpx> for version 1.1: " << xml.name();
				xmlSkipCurrentElement(xml);
			}
		}
	}

	emit progress(1);

	activities.append(activity);
}

bool GpxImporter::canImport(const QString& rootName)
{
	return rootName == m_RootElementTagName;
}

QString GpxImporter::fileFormat() const
{
	return tr("*.gpx|GPX Exchange Format (*.gpx)");
}
