#include <cmath>
#include <QLocale>
#include "data/data.h"
#include "elevationgraphitem.h"
#include "elevationgraph.h"


static qreal nMin(qreal a, qreal b)
{
	if (std::isnan(a))
		return std::isnan(b) ? NAN : b;
	else
		return std::isnan(b) ? a : qMin(a, b);
}

static qreal nMax(qreal a, qreal b)
{
	if (std::isnan(a))
		return std::isnan(b) ? NAN : b;
	else
		return std::isnan(b) ? a : qMax(a, b);
}

ElevationGraph::ElevationGraph(QWidget *parent) : GraphTab(parent)
{
	_trackAscent = 0;
	_routeAscent = 0;
	_trackDescent = 0;
	_routeDescent = 0;
	_trackMin = NAN;
	_trackMax = NAN;
	_routeMin = NAN;
	_routeMax = NAN;

	_showRoutes = false;
	_showTracks = false;

	setYUnits(Metric);
	setYLabel(tr("Elevation"));
	setMinYRange(50.0);
}

ElevationGraph::~ElevationGraph()
{
	qDeleteAll(_tracks);
	qDeleteAll(_routes);
}

void ElevationGraph::setInfo()
{
	if (std::isnan(max()) || std::isnan(min()))
		clearInfo();
	else {
		QLocale l(QLocale::system());

#ifdef Q_OS_ANDROID
		/*: Use an Unicode arrow (U+2197) when there is no abbreviation or
			extremly short term for "Up" in your language */
		GraphView::addInfo(tr("Up"), l.toString(ascent() * yScale(), 'f', 0)
		  + UNIT_SPACE + yUnits());
		/*: Use an Unicode arrow (U+2198) when there is no abbreviation or
			extremly short term for "Down" in your language */
		GraphView::addInfo(tr("Down"), l.toString(descent() * yScale(), 'f', 0)
		  + UNIT_SPACE + yUnits());
		/*: Use an abbreviation or a extremly short term */
		GraphView::addInfo(tr("Max"), l.toString(max() * yScale(), 'f', 0)
		  + UNIT_SPACE + yUnits());
		/*: Use an abbreviation or a extremly short term */
		GraphView::addInfo(tr("Min"), l.toString(min() * yScale(), 'f', 0)
		  + UNIT_SPACE + yUnits());
#else // Q_OS_ANDROID
		GraphView::addInfo(tr("Ascent"), l.toString(ascent() * yScale(),
		  'f', 0) + UNIT_SPACE + yUnits());
		GraphView::addInfo(tr("Descent"), l.toString(descent() * yScale(),
		  'f', 0) + UNIT_SPACE + yUnits());
		GraphView::addInfo(tr("Maximum"), l.toString(max() * yScale(), 'f',
		  0) + UNIT_SPACE + yUnits());
		GraphView::addInfo(tr("Minimum"), l.toString(min() * yScale(), 'f',
		  0) + UNIT_SPACE + yUnits());
#endif // Q_OS_ANDROID
	}
}

GraphItem *ElevationGraph::loadGraph(const Graph &graph, PathType type,
  const QColor &color, bool primary)
{
	if (graph.isEmpty())
		return 0;

	ElevationGraphItem *gi = new ElevationGraphItem(graph, _graphType, _width,
	  color, primary ? Qt::SolidLine : Qt::DashLine);
	gi->setUnits(_units);

	if (type == TrackPath) {
		_tracks.append(gi);
		if (_showTracks)
			addGraph(gi);

		if (primary) {
			_trackAscent += gi->ascent();
			_trackDescent += gi->descent();
			_trackMax = nMax(_trackMax, gi->max());
			_trackMin = nMin(_trackMin, gi->min());
		}
	} else {
		_routes.append(gi);
		if (_showRoutes)
			addGraph(gi);

		if (primary) {
			_routeAscent += gi->ascent();
			_routeDescent += gi->descent();
			_routeMax = nMax(_routeMax, gi->max());
			_routeMin = nMin(_routeMin, gi->min());
		}
	}

	return gi;
}

QList<GraphItem*> ElevationGraph::loadData(const Data &data, Map *map)
{
	QList<GraphItem*> graphs;
	GraphItem *primary, *secondary;

	for (int i = 0; i < data.tracks().count(); i++) {
		QColor color(_palette.nextColor());
		const GraphPair &gp = data.tracks().at(i).elevation(map);

		primary = loadGraph(gp.primary(), TrackPath, color, true);
		secondary = primary
		  ? loadGraph(gp.secondary(), TrackPath, color, false) : 0;
		if (primary && secondary)
			primary->setSecondaryGraph(secondary);

		graphs.append(primary);
	}
	for (int i = 0; i < data.routes().count(); i++) {
		QColor color(_palette.nextColor());
		const GraphPair &gp = data.routes().at(i).elevation(map);

		primary = loadGraph(gp.primary(), RoutePath, color, true);
		secondary = primary
		  ? loadGraph(gp.secondary(), RoutePath, color, false) : 0;
		if (primary && secondary)
			primary->setSecondaryGraph(secondary);

		graphs.append(primary);
	}
	for (int i = 0; i < data.areas().count(); i++)
		_palette.nextColor();

	setInfo();
	redraw();

	return graphs;
}

void ElevationGraph::clear()
{
	qDeleteAll(_tracks);
	_tracks.clear();
	qDeleteAll(_routes);
	_routes.clear();

	_trackAscent = 0;
	_routeAscent = 0;
	_trackDescent = 0;
	_routeDescent = 0;
	_trackMin = NAN;
	_trackMax = NAN;
	_routeMin = NAN;
	_routeMax = NAN;

	GraphTab::clear();
}

void ElevationGraph::setYUnits(Units units)
{
	if (units == Metric) {
		GraphView::setYUnits(tr("m"));
		setYScale(1);
	} else {
		GraphView::setYUnits(tr("ft"));
		setYScale(M2FT);
	}
}

void ElevationGraph::setUnits(Units units)
{
	setYUnits(units);
	setInfo();

	GraphView::setUnits(units);
}

void ElevationGraph::showItems(const QList<ElevationGraphItem *> &list,
  bool show)
{
	for (int i = 0; i < list.size(); i++) {
		if (show)
			addGraph(list.at(i));
		else
			removeGraph(list.at(i));
	}
}

void ElevationGraph::showTracks(bool show)
{
	_showTracks = show;

	showItems(_tracks, show);
	setInfo();

	redraw();
}

void ElevationGraph::showRoutes(bool show)
{
	_showRoutes = show;

	showItems(_routes, show);
	setInfo();

	redraw();
}

qreal ElevationGraph::ascent() const
{
	qreal val = 0;

	if (_showRoutes)
		val += _routeAscent;
	if (_showTracks)
		val += _trackAscent;

	return val;
}

qreal ElevationGraph::descent() const
{
	qreal val = 0;

	if (_showRoutes)
		val += _routeDescent;
	if (_showTracks)
		val += _trackDescent;

	return val;
}

qreal ElevationGraph::max() const
{
	qreal val;

	if (_showRoutes && _showTracks)
		val = nMax(_routeMax, _trackMax);
	else if (_showTracks)
		val = _trackMax;
	else if (_showRoutes)
		val = _routeMax;
	else
		val = NAN;

	return val;
}

qreal ElevationGraph::min() const
{
	qreal val;

	if (_showRoutes && _showTracks)
		val = nMin(_routeMin, _trackMin);
	else if (_showTracks)
		val = _trackMin;
	else if (_showRoutes)
		val = _routeMin;
	else
		val = NAN;

	return val;
}
