/***************************************************************************
 *   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/activity.h"
#include "core/lap.h"
#include "core/equipment.h"
#include "core/profile.h"
#include "core/category.h"
#include "core/types.h"
#include "core/position.h"

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

Activity::Activity() :
	DbItem("activity", QStringList() << "id" << "profile" << "name" <<
			"category" << "equipment" << "ts" << "temperature" <<
			"weather" << "wind" << "notes"),
	m_Profile(NULL),
	m_Category(NULL),
	m_Equipment(NULL),
	m_Name(),
	m_TimeStamp(),
	m_Temperature(0),
	m_Weather(Weather::unknown),
	m_WeatherDescription(),
	m_Notes(),
	m_Laps()
{
}

Activity::Activity(Profile* p, const QSqlQuery& q) :
	DbItem(q.value(0).toULongLong(),
			"activity", QStringList() << "id" << "profile" << "name" <<
			"category" << "equipment" << "ts" << "temperature" <<
			"weather" << "wind" << "notes"),
	m_Profile(p),
	m_Category(m_Profile->findCategory(q.value(3).toULongLong())),
	m_Equipment(m_Profile->findEquipment(q.value(4).toULongLong())),
	m_Name(q.value(2).toString()),
	m_TimeStamp(q.value(5).toDateTime()),
	m_Temperature(q.value(6).toInt()),
	m_Weather(static_cast<Weather::Condition>(q.value(7).toInt())),
	m_WeatherDescription(q.value(8).toString()),
	m_Notes(q.value(9).toString()),
	m_Laps()
{
	m_TimeStamp.setTimeSpec(Qt::UTC);
}

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

void Activity::clear()
{
	qDeleteAll(m_Laps);
	m_Laps.clear();
}

bool Activity::serialize()
{
	Q_ASSERT(m_Profile);
	if (m_Profile == NULL)
	{
		qWarning() << "attempt to serialize an activity without profile";
		return false;
	}

	Q_ASSERT(m_Category);
	if (m_Category == NULL)
	{
		qWarning() << "attempt to serialize an activity without category";
		return false;
	}

	if (isRemoved())
		return remove();

	if (!DbItem::serialize(
			QVariantList() << id() << m_Profile->id() << m_Name << m_Category->id() <<
			(m_Equipment ? m_Equipment->id() : QVariant()) << m_TimeStamp <<
			m_Temperature << m_Weather << m_WeatherDescription << m_Notes
	))
		return false;

	for (Laps::iterator i = m_Laps.begin(); i != m_Laps.end(); /* NOP */)
	{
		if ((*i)->activity() != this)
			(*i)->setActivity(this);

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

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

	return true;
}

bool Activity::list(Profile* profile, Activities& result)
{
	QSqlQuery query;

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

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

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

	while (query.next())
	{
		Activity* a = new Activity(profile, query);
		Lap::list(a, a->m_Laps);
		result.append(a);
	}

	return true;
}

quint32 Activity::totalDistance() const
{
	quint32 result = 0;
	foreach (const Lap* lap, m_Laps)
		result += lap->distance();

	return result;
}

quint32 Activity::totalDuration() const
{
	quint32 result = 0;
	foreach (const Lap* lap, m_Laps)
		result += lap->duration();

	return result;
}

quint32 Activity::averagePace() const
{
	if (m_Laps.size() == 0)
		return 0;

	quint32 result = 0;

	foreach (const Lap* lap, m_Laps)
		result += lap->duration() / (lap->distance() / 1000.0 / 1000.0);

	return result / m_Laps.size();
}

quint32 Activity::averageSpeed() const
{
	if (m_Laps.size() == 0)
		return 0;

	quint32 result = 0;

	foreach (const Lap* lap, m_Laps)
		result += lap->distance() / (lap->duration() / 3600.0);

	return result / m_Laps.size();
}

quint32 Activity::maxSpeed() const
{
	quint32 result = 0;

	foreach (const Lap* lap, m_Laps)
		if (lap->maximumSpeed() > result)
			result = lap->maximumSpeed();

	return result;
}

quint32 Activity::energyConsumption() const
{
	quint32 result = 0;

	foreach (const Lap* lap, m_Laps)
		result += lap->energyConsumption();

	return result;
}

quint32 Activity::averageHeartRate() const
{
	if (m_Laps.size() == 0)
		return 0;

	quint32 result = 0;

	foreach (const Lap* lap, m_Laps)
		result += lap->averageHeartRate();

	return result / m_Laps.size();
}

quint32 Activity::maxHeartRate() const
{
	quint32 result = 0;

	foreach (const Lap* lap, m_Laps)
		if (lap->maximumHeartRate() > result)
			result = lap->maximumHeartRate();

	return result;
}

quint32 Activity::averageCadence() const
{
	if (m_Laps.size() == 0)
		return 0;

	quint32 result = 0;

	foreach (const Lap* lap, m_Laps)
		result += lap->averageCadence();

	return result / m_Laps.size();
}

quint32 Activity::totalAscent() const
{
	quint32 result = 0;

	foreach (const Lap* lap, m_Laps)
		result += lap->ascent();

	return result;
}

quint32 Activity::totalDescent() const
{
	quint32 result = 0;

	foreach (const Lap* lap, m_Laps)
		result += lap->descent();

	return result;
}

quint64 Activity::totalEquipmentDistance(const Activities& activities, const Equipment* e)
{
	if (e == NULL)
		return 0;

	quint64 result = 0;

	foreach(const Activity* a, activities)
		if (!a->isRemoved() && a->equipment() == e)
			result += a->totalDistance();

	return result;
}

QDate Activity::equipmentLastused(const Activities& activities, const Equipment* e)
{
	if (e == NULL)
		return QDate();

	QDate result;

	foreach(const Activity* a, activities)
		if (!a->isRemoved() && a->equipment() == e && (!result.isValid() || a->timeStamp().date() > result))
			result = a->timeStamp().date();

	return result;
}

bool Activity::hasActivity(const Activities& haystack, const Activity& needle)
{
	foreach(Activity* activity, haystack)
		if (!activity->isRemoved() && activity->timeStamp() == needle.timeStamp())
			return true;

	return false;
}

void Activity::calcTrackPointSpeed(Activities& acts)
{
	foreach(Activity* a, acts)
		foreach(Lap* lap, a->laps())
			lap->calcTrackPointSpeed();
}

void Activity::calcLapTotalAscentAndDescent(Activities& acts)
{
	foreach(Activity* a, acts)
		foreach(Lap* lap, a->laps())
		{
			lap->setAscent(Lap::calcTotalAscent(*lap));
			lap->setDescent(Lap::calcTotalDescent(*lap));
		}
}

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

	foreach(Lap* lap, m_Laps)
	{
		double minLat = Position::Invalid;
		double minLon = Position::Invalid;
		double maxLat = Position::Invalid;
		double maxLon = Position::Invalid;

		if (lap->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;
}
