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

#include "util/helpers.h"

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

Profile::Profile() :
	DbItem("profile", QStringList() << "id" << "name" << "birth_date" << "gender" << "height" << "photo"),
	m_Name(),
	m_BirthDate(QDate::currentDate()),
	m_Gender(Male),
	m_Height(1800),
	m_Photo(),
	m_Activities(),
	m_AthleteLogs(),
	m_Categories(),
	m_Equipments()
{
	Category* topCategory = new Category(this, 0, QObject::tr("My Activities"));
	m_Categories.append(topCategory);
	topCategory->subCategories().append(new Category(this, topCategory, QObject::tr("Cycling", "Category name")));
	topCategory->subCategories().append(new Category(this, topCategory, QObject::tr("Running", "Category name")));
	topCategory->subCategories().append(new Category(this, topCategory, QObject::tr("Swimming", "Category name")));
	topCategory->subCategories().append(new Category(this, topCategory, QObject::tr("Other", "Category name")));
}

Profile::Profile(const QSqlQuery& query) :
	DbItem(query.value(0).toULongLong(), "profile",
		QStringList() << "id" << "name" << "birth_date" << "gender" << "height" << "photo"),
	m_Name(query.value(1).toString()),
	m_BirthDate(query.value(2).toDate()),
	m_Gender(static_cast<Gender>(query.value(3).toInt())),
	m_Height(query.value(4).toUInt()),
	m_Photo(),
	m_Activities(),
	m_AthleteLogs(),
	m_Categories(),
	m_Equipments()
{
	m_Photo.loadFromData(query.value(5).toByteArray());
}

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

void Profile::clear()
{
	qDeleteAll(m_Activities);
	m_Activities.clear();

	qDeleteAll(m_AthleteLogs);
	m_AthleteLogs.clear();

	qDeleteAll(m_Equipments);
	m_Equipments.clear();

	qDeleteAll(m_Categories);
	m_Categories.clear();
}

bool Profile::list(Profiles& result)
{
	QSqlQuery query;

	if (!query.exec("SELECT " + Profile().columnNames().join(",") + " FROM " + Profile().tableName()))
	{
		qWarning() << "exec failed : " << query.lastError().text();
		return false;
	}

	while (query.next())
		result.append(new Profile(query));

	return true;
}

Profile* Profile::unserialize(const QString& name)
{
	QSqlQuery query;

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

	query.addBindValue(name);

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

	if (!query.next())
	{
		qDebug() << "profile with name " << name << " not found in database";
		return NULL;
	}

	Profile* rval = new Profile(query);

	Category::list(rval, NULL, rval->m_Categories);
	Equipment::list(rval, rval->m_Equipments);
	AthleteLog::list(rval, rval->m_AthleteLogs);
	Activity::list(rval, rval->m_Activities);

	return rval;
}

bool Profile::serialize()
{
	if (isRemoved())
		return remove();

	if (!DbItem::serialize(
			QVariantList() << id() << m_Name << m_BirthDate << m_Gender << m_Height << pixmapToByteArray(m_Photo)
	))
		return false;

	for (Categories::iterator i = m_Categories.begin(); i != m_Categories.end(); /* NOP */)
	{
		if ((*i)->profile() != this)
			(*i)->setProfile(this);

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

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

	for (Equipments::iterator i = m_Equipments.begin(); i != m_Equipments.end();  /* NOP */)
	{
		if ((*i)->profile() != this)
			(*i)->setProfile(this);

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

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

	for (AthleteLogs::iterator i = m_AthleteLogs.begin(); i != m_AthleteLogs.end(); /* NOP */)
	{
		if ((*i)->profile() != this)
			(*i)->setProfile(this);

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

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

	for (Activities::iterator i = m_Activities.begin(); i != m_Activities.end(); /* NOP */)
	{
		if ((*i)->profile() != this)
			(*i)->setProfile(this);

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

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

	return true;
}

quint32 Profile::currentWeight() const
{
	quint32 result = 0;
	QDate latest;

	foreach(const AthleteLog* entry, athleteLogs())
		if (entry->date() > latest && entry->weight() > 0)
		{
			latest = entry->date();
			result = entry->weight();
		}

	return result;
}

quint32 Profile::calcEnergyConsumption(const Profile& profile, const Lap& lap)
{
	if (lap.averageHeartRate() == 0 || profile.athleteLogs().size() == 0)
		return 0;

	const quint32 age = QDate::currentDate().year() - profile.birthDate().year();
	const double weight = profile.currentWeight() / 1000.0;

	if (profile.gender() == Profile::Male)
		return -55.0969 + 0.6309 * lap.averageHeartRate() * 0.1988 * weight * 0.2017 * age;

	return -20.4022 + 0.4472 * lap.averageHeartRate() - 0.1263 * weight + 0.074 * age;
}

bool Profile::removeActivity(Activity* a)
{
	return m_Activities.removeAll(a);
}

void Profile::setValidCategory(Activities& a) const
{
	if (categories().size() == 0)
	{
		qWarning() << "no categories";
		return;
	}

	foreach(Activity* activity, a)
		if (activity->category() == NULL)
			activity->setCategory(const_cast<Profile*>(this)->rootCategory());
}

void Profile::setValidName(Activities& a) const
{
	foreach(Activity* activity, a)
		if (activity->name().isEmpty())
		{
			if (activity->timeStamp().isValid())
				activity->setName(activity->timeStamp().toString(Qt::ISODate));
			else
				activity->setName(QObject::tr("(unnamed)", "activity name"));
		}
}

void Profile::setValidStartTime(Activities& a) const
{
	foreach(Activity* activity, a)
		if (!activity->timeStamp().isValid())
		{
			// try the start time of the first lap as time stamp
			if (activity->laps().size() > 0)
				activity->setTimeStamp(activity->laps()[0]->startTime());
			else
				activity->setTimeStamp(QDateTime::currentDateTime());
		}
}

void Profile::calcEnergyConsumption(Activities& a) const
{
	foreach(Activity* activity, a)
		if (activity->energyConsumption() == 0)
		{
			foreach(Lap* lap, activity->laps())
				lap->setEnergyConsumption(Profile::calcEnergyConsumption(*this, *lap));
		}
}

bool Profile::hasActivity(const Activity& other) const
{
	return Activity::hasActivity(activities(), other);
}

double Profile::bmi(const AthleteLog& log) const
{
	return (log.weight() / 1000.0) / (height() / 1000.0 * height() / 1000.0);
}

Equipment* Profile::findEquipment(quint64 id)
{
	if (id == 0)
		return NULL;

	foreach(Equipment* e, m_Equipments)
		if (e->id() == id)
			return e;

	return NULL;
}

Category* Profile::findCategory(quint64 id)
{
	return Category::findCategory(id, m_Categories);
}
