/***************************************************************************
 *   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 "core/lap.h"
#include "core/track.h"
#include "core/activity.h"
#include "core/position.h"

#include <QSqlQuery>
#include <QSqlError>
#include <QVariant>
#include <QDebug>

Lap::Lap() :
	DbItem("lap",
		QStringList() << "id" << "activity" << "start_time" << "duration" << "distance" << "max_speed" <<
		"energy_consumption" << "average_heart_rate" << "max_heart_rate" << "average_cadence" << "ascent" << "descent"),
	m_Activity(NULL),
	m_StartTime(),
	m_Duration(0),
	m_Distance(0),
	m_MaximumSpeed(0),
	m_EnergyConsumption(0),
	m_AverageHeartRate(0),
	m_MaximumHeartRate(0),
	m_AverageCadence(0),
	m_Ascent(0),
	m_Descent(0),
	m_Tracks()
{
}

Lap::Lap(Activity* a, const QSqlQuery& query) :
	DbItem(query.value(0).toULongLong(), "lap",
		QStringList() << "id" << "activity" << "start_time" << "duration" << "distance" << "max_speed" <<
		"energy_consumption" << "average_heart_rate" << "max_heart_rate" << "average_cadence" << "ascent" << "descent"),
	m_Activity(a),
	m_StartTime(query.value(2).toDateTime()),
	m_Duration(query.value(3).toUInt()),
	m_Distance(query.value(4).toUInt()),
	m_MaximumSpeed(query.value(5).toUInt()),
	m_EnergyConsumption(query.value(6).toUInt()),
	m_AverageHeartRate(query.value(7).toUInt()),
	m_MaximumHeartRate(query.value(8).toUInt()),
	m_AverageCadence(query.value(9).toUInt()),
	m_Ascent(query.value(10).toUInt()),
	m_Descent(query.value(11).toUInt()),
	m_Tracks()
{
	m_StartTime.setTimeSpec(Qt::UTC);
}

Lap::~Lap()
{
	clear();
}

void Lap::clear()
{
	qDeleteAll(m_Tracks);
	m_Tracks.clear();
}

bool Lap::serialize()
{
	Q_ASSERT(m_Activity);

	if (m_Activity == NULL)
	{
		qWarning() << "attempt to serialize a lap without an activity";
		return false;
	}

	if (isRemoved())
		return remove();

	if (!DbItem::serialize(
			QVariantList() << id() << m_Activity->id() << m_StartTime << m_Duration << m_Distance <<
			m_MaximumSpeed << m_EnergyConsumption << m_AverageHeartRate << m_MaximumHeartRate <<
			m_AverageCadence << m_Ascent << m_Descent
	))
		return false;

	for (Tracks::iterator i = m_Tracks.begin(); i != m_Tracks.end(); /* NOP */)
	{
		if ((*i)->lap() != this)
			(*i)->setLap(this);

		if (!(*i)->serialize())
			return false;

		if ((*i)->isRemoved())
		{
			delete *i;
			i = m_Tracks.erase(i);
		}
		else
			++i;
	}

	return true;
}

bool Lap::list(Activity* a, Laps& result)
{
	Q_ASSERT(a);

	QSqlQuery query;

	if (!query.prepare("SELECT " + Lap().columnNames().join(",") + " FROM " + Lap().tableName() + " WHERE activity=?"))
	{
		qWarning() << "prepare failed : " << query.lastError().text();
		return false;
	}

	query.addBindValue(a->id());

	if (!query.exec())
	{
		qWarning() << "exec failed : " << query.lastError().text();
		return false;
	}

	while (query.next())
	{
		Lap* lap = new Lap(a, query);
		Track::list(lap, lap->m_Tracks);
		result.append(lap);
	}

	return true;
}

void Lap::addTrack(Track* t)
{
	m_Tracks.append(t);
	t->setLap(this);
}

quint32 Lap::calcTotalAscent(const Lap& lap)
{
	quint32 result = 0;

	foreach(const Track* track, lap.m_Tracks)
		result += track->calcTotalAscent();

	return result;
}

quint32 Lap::calcTotalDescent(const Lap& lap)
{
	quint32 result = 0;

	foreach(const Track* track, lap.m_Tracks)
		result += track->calcTotalDescent();

	return result;
}

quint32 Lap::calcAverageCadence() const
{
	quint64 sumCadence = 0;
	quint64 count = 0;

	foreach(const Track* track, tracks())
	{
		sumCadence += track->calcAverageCadence();
		count++;
	}

	return count == 0 ? 0 : sumCadence / count;
}

void Lap::calcHeartRates(quint32& avg, quint32& max) const
{
	quint64 sumHeartRate = 0;
	quint64 count = 0;

	max = 0;

	foreach(const Track* track, tracks())
	{
		quint32 trackMaxHeartRate;
		quint32 trackAvgHeartRate;

		track->calcHeartRates(trackAvgHeartRate, trackMaxHeartRate);

		if (trackAvgHeartRate > 0)
		{
			sumHeartRate += trackAvgHeartRate;
			count++;
		}

		if (trackMaxHeartRate > max)
			max = trackMaxHeartRate;
	}

	avg = (count == 0) ? 0 : sumHeartRate / count;;
}

quint32 Lap::calcDistance() const
{
	quint32 rval = 0;

	foreach(const Track* track, tracks())
		rval += track->calcDistance();

	return rval;
}

quint32 Lap::calcDuration() const
{
	quint32 rval = 0;

	foreach(const Track* track, tracks())
		rval += track->calcDuration();

	return rval;
}

quint32 Lap::calcMaxSpeed(const Lap& lap)
{
	quint32 maxSpeed = 0;

	foreach(const Track* track, lap.tracks())
	{
		const quint32 trackMaxSpeed = track->calcMaxSpeed();
		if (trackMaxSpeed > maxSpeed)
			maxSpeed = trackMaxSpeed;
	}

	return maxSpeed;
}

QDateTime Lap::calcStartTime() const
{
	QDateTime rval;

	foreach(const Track* track, tracks())
	{
		QDateTime trackStart = track->calcStartTime();

		if (!rval.isValid() || trackStart < rval)
			rval = trackStart;
	}

	return rval;
}

void Lap::calcTrackPointSpeed()
{
	foreach(Track* trk, m_Tracks)
		trk->calcTrackPointSpeed();
}

bool Lap::calcBoundaries(double& minLat_, double& minLon_, double& maxLat_, double& maxLon_) const
{
	minLat_ = Position::Invalid;
	minLon_ = Position::Invalid;
	maxLat_ = Position::Invalid;
	maxLon_ = Position::Invalid;

	foreach(Track* trk, m_Tracks)
	{
		double minLat = Position::Invalid;
		double minLon = Position::Invalid;
		double maxLat = Position::Invalid;
		double maxLon = Position::Invalid;

		if (trk->calcBoundaries(minLat, minLon, maxLat, maxLon))
		{
			if (minLat < minLat_ || minLat_ == Position::Invalid)
				minLat_ = minLat;

			if (maxLat > maxLat_ || maxLat_ == Position::Invalid)
				maxLat_ = maxLat;

			if (minLon < minLon_ || minLon_ == Position::Invalid)
				minLon_ = minLon;

			if (maxLon > maxLon_ || maxLon_ == Position::Invalid)
				maxLon_ = maxLon;
		}
	}

	return minLat_ != Position::Invalid && minLon_ != Position::Invalid && maxLat_ != Position::Invalid && maxLon_ != Position::Invalid;
}
