/**********************************************************************
 * LeechCraft - modular cross-platform feature rich internet client.
 * Copyright (C) 2006-2014  Georg Rudoy
 *
 * Boost Software License - Version 1.0 - August 17th, 2003
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **********************************************************************/

#include "plotitem.h"
#include <cmath>
#include <limits>
#include <vector>
#include <memory>
#include <QStyleOption>
#include <QColor>
#include <qwt_plot.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_renderer.h>
#include <qwt_plot_grid.h>
#include <qwt_scale_draw.h>
#include <qwt_text_label.h>
#include <qwt_plot_canvas.h>
#include <util.h>

Q_DECLARE_METATYPE (QList<QPointF>)

namespace LeechCraft
{
namespace Util
{
#if QT_VERSION < 0x050000
	PlotItem::PlotItem (QDeclarativeItem *parent)
	: QDeclarativeItem { parent }
#else
	PlotItem::PlotItem (QQuickItem *parent)
	: QQuickPaintedItem { parent }
#endif
	, Color_ { "#FF4B10" }
	{
#if QT_VERSION < 0x050000
		setFlag (QGraphicsItem::ItemHasNoContents, false);
#else
		setFlag (ItemHasContents, true);
#endif
	}

	QList<QPointF> PlotItem::GetPoints () const
	{
		return Points_;
	}

	void PlotItem::SetPoints (const QList<QPointF>& pts)
	{
		if (pts == Points_)
			return;

		Points_ = pts;
		emit pointsChanged ();
		update ();
	}

	QVariant PlotItem::GetMultipoints () const
	{
		QVariantList result;
		for (const auto& set : Multipoints_)
			result << Util::MakeMap<QString, QVariant> ({
					{ "color", QVariant::fromValue (set.Color_) },
					{ "points", QVariant::fromValue (set.Points_) }
				});
		return result;
	}

	void PlotItem::SetMultipoints (const QVariant& variant)
	{
		Multipoints_.clear ();

		for (const auto& set : variant.toList ())
		{
			const auto& map = set.toMap ();

			const auto& colorVar = map ["color"];
			const auto& pointsVar = map ["points"];

			if (!colorVar.canConvert<QString> () ||
				!pointsVar.canConvert<QList<QPointF>> ())
			{
				qWarning () << Q_FUNC_INFO
						<< "invalid map"
						<< map;
				qWarning () << Q_FUNC_INFO
						<< "ignoring this point";
				continue;
			}

			Multipoints_.append ({
					map ["color"].toString (),
					map ["points"].value<QList<QPointF>> ()
				});
		}
		update ();
	}

	double PlotItem::GetMinXValue () const
	{
		return MinXValue_;
	}

	void PlotItem::SetMinXValue (double val)
	{
		SetNewValue (val, MinXValue_, [this] { emit minXValueChanged (); });
	}

	double PlotItem::GetMaxXValue () const
	{
		return MaxXValue_;
	}

	void PlotItem::SetMaxXValue (double val)
	{
		SetNewValue (val, MaxXValue_, [this] { emit maxXValueChanged (); });
	}

	double PlotItem::GetMinYValue () const
	{
		return MinYValue_;
	}

	void PlotItem::SetMinYValue (double val)
	{
		SetNewValue (val, MinYValue_, [this] { emit minYValueChanged (); });
	}

	double PlotItem::GetMaxYValue () const
	{
		return MaxYValue_;
	}

	void PlotItem::SetMaxYValue (double val)
	{
		SetNewValue (val, MaxYValue_, [this] { emit maxYValueChanged (); });
	}

	bool PlotItem::GetYGridEnabled () const
	{
		return YGridEnabled_;
	}

	void PlotItem::SetYGridEnabled (bool val)
	{
		SetNewValue (val, YGridEnabled_, [this] { emit yGridChanged (); });
	}

	bool PlotItem::GetYMinorGridEnabled () const
	{
		return YMinorGridEnabled_;
	}

	void PlotItem::SetYMinorGridEnabled (bool val)
	{
		SetNewValue (val, YMinorGridEnabled_, [this] { emit yMinorGridChanged (); });
	}

	double PlotItem::GetAlpha () const
	{
		return Alpha_;
	}

	void PlotItem::SetAlpha (double a)
	{
		Alpha_ = a;
		emit alphaChanged ();
	}

	QColor PlotItem::GetColor () const
	{
		return Color_;
	}

	void PlotItem::SetColor (const QColor& color)
	{
		SetNewValue (color, Color_, [this] { emit colorChanged (); });
	}

	bool PlotItem::GetLeftAxisEnabled () const
	{
		return LeftAxisEnabled_;
	}

	void PlotItem::SetLeftAxisEnabled (bool enabled)
	{
		SetNewValue (enabled, LeftAxisEnabled_, [this] { emit leftAxisEnabledChanged (); });
	}

	bool PlotItem::GetBottomAxisEnabled () const
	{
		return BottomAxisEnabled_;
	}

	void PlotItem::SetBottomAxisEnabled (bool enabled)
	{
		SetNewValue (enabled, BottomAxisEnabled_, [this] { emit bottomAxisEnabledChanged (); });
	}

	QString PlotItem::GetLeftAxisTitle () const
	{
		return LeftAxisTitle_;
	}

	void PlotItem::SetLeftAxisTitle (const QString& title)
	{
		SetNewValue (title, LeftAxisTitle_, [this] { emit leftAxisTitleChanged (); });
	}

	QString PlotItem::GetBottomAxisTitle () const
	{
		return BottomAxisTitle_;
	}

	void PlotItem::SetBottomAxisTitle (const QString& title)
	{
		SetNewValue (title, BottomAxisTitle_, [this] { emit bottomAxisTitleChanged (); });
	}

	QString PlotItem::GetPlotTitle () const
	{
		return PlotTitle_;
	}

	void PlotItem::SetPlotTitle (const QString& title)
	{
		SetNewValue (title, PlotTitle_, [this] { emit plotTitleChanged (); });
	}

	QColor PlotItem::GetBackground () const
	{
		return BackgroundColor_;
	}

	void PlotItem::SetBackground (const QColor& bg)
	{
		SetNewValue (bg, BackgroundColor_, [this] { emit backgroundChanged (); });
	}

	QColor PlotItem::GetTextColor () const
	{
		return TextColor_;
	}

	void PlotItem::SetTextColor (const QColor& color)
	{
		SetNewValue (color, TextColor_, [this] { emit textColorChanged (); });
	}

	QColor PlotItem::GetGridLinesColor () const
	{
		return GridLinesColor_;
	}

	void PlotItem::SetGridLinesColor (const QColor& color)
	{
		SetNewValue (color, GridLinesColor_, [this] { emit gridLinesColorChanged (); });
	}

	int PlotItem::GetXExtent () const
	{
		return XExtent_;
	}

	int PlotItem::GetYExtent () const
	{
		return YExtent_;
	}

#if QT_VERSION < 0x050000
	void PlotItem::paint (QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget*)
	{
		const auto& rect = option->rect;
#else
	void PlotItem::paint (QPainter *painter)
	{
		const auto& rect = contentsBoundingRect ().toRect ();
#endif

		if (!Plot_)
		{
			Plot_ = std::make_shared<QwtPlot> ();
			Plot_->setFrameShape (QFrame::NoFrame);
			Plot_->setFrameShadow (QFrame::Plain);
			Plot_->setLineWidth (0);
			Plot_->setMidLineWidth (0);

			if (const auto canvas = qobject_cast<QwtPlotCanvas*> (Plot_->canvas ()))
				canvas->setBorderRadius (0);
		}

		auto& plot = *Plot_;
		plot.enableAxis (QwtPlot::yLeft, LeftAxisEnabled_);
		plot.enableAxis (QwtPlot::xBottom, BottomAxisEnabled_);
		plot.setAxisTitle (QwtPlot::yLeft, LeftAxisTitle_);
		plot.setAxisTitle (QwtPlot::xBottom, BottomAxisTitle_);

		if (plot.size () != rect.size ())
			plot.resize (rect.size ());

		auto setPaletteColor = [&plot] (const QColor& color, QPalette::ColorRole role)
		{
			if (!color.isValid ())
				return;

			auto pal = plot.palette ();
			pal.setColor (role, { color });
			plot.setPalette (pal);
		};

		setPaletteColor (BackgroundColor_, QPalette::Window);
		setPaletteColor (TextColor_, QPalette::WindowText);
		setPaletteColor (TextColor_, QPalette::Text);

		if (!PlotTitle_.isEmpty ())
			plot.setTitle (QwtText { PlotTitle_ });

		if (MinYValue_ < MaxYValue_)
		{
			plot.setAxisAutoScale (QwtPlot::yLeft, false);
			plot.setAxisScale (QwtPlot::yLeft, MinYValue_, MaxYValue_);
		}
		plot.setAutoFillBackground (false);
		plot.setCanvasBackground (Qt::transparent);

		if (YGridEnabled_)
		{
			auto grid = new QwtPlotGrid;
			grid->enableYMin (YMinorGridEnabled_);
			grid->enableX (false);
#if QWT_VERSION >= 0x060100
			grid->setMajorPen (QPen (GridLinesColor_, 1, Qt::SolidLine));
			grid->setMinorPen (QPen (GridLinesColor_, 1, Qt::DashLine));
#else
			grid->setMajPen (QPen (GridLinesColor_, 1, Qt::SolidLine));
			grid->setMinPen (QPen (GridLinesColor_, 1, Qt::DashLine));
#endif
			grid->attach (&plot);
		}

		auto items = Multipoints_;
		if (items.isEmpty ())
			items.push_back ({ Color_, Points_ });

		if (MinXValue_ < MaxXValue_)
			plot.setAxisScale (QwtPlot::xBottom, MinXValue_, MaxXValue_);
		else if (const auto ptsCount = items.first ().Points_.size ())
			plot.setAxisScale (QwtPlot::xBottom, 0, ptsCount - 1);

		std::vector<std::unique_ptr<QwtPlotCurve>> curves;
		for (const auto& item : items)
		{
			curves.emplace_back (new QwtPlotCurve);
			const auto curve = curves.back ().get ();

			curve->setPen (QPen (item.Color_));
			auto transpColor = item.Color_;
			transpColor.setAlphaF (Alpha_);
			curve->setBrush (transpColor);

			curve->setRenderHint (QwtPlotItem::RenderAntialiased);
			curve->attach (&plot);

			curve->setSamples (item.Points_.toVector ());
		}

		plot.replot ();

		QwtPlotRenderer {}.render (&plot, painter, rect);

		const auto xExtent = CalcXExtent (plot);
		const auto yExtent = CalcYExtent (plot);
		if (xExtent != XExtent_ || yExtent != YExtent_)
		{
			XExtent_ = xExtent;
			YExtent_ = yExtent;
			emit extentsChanged ();
		}
	}

	template<typename T>
	void PlotItem::SetNewValue (T val, T& ourVal, const std::function<void ()>& notifier)
	{
		if (val == ourVal)
			return;

		ourVal = val;
		notifier ();
		update ();
	}

	int PlotItem::CalcXExtent (QwtPlot& plot) const
	{
		int result = 0;
		if (LeftAxisEnabled_)
			result += plot.axisScaleDraw (QwtPlot::yLeft)->
					extent (plot.axisFont (QwtPlot::yLeft));
		return result;
	}

	int PlotItem::CalcYExtent (QwtPlot& plot) const
	{
		int result = 0;
		if (BottomAxisEnabled_)
			result += plot.axisScaleDraw (QwtPlot::xBottom)->
					extent (plot.axisFont (QwtPlot::xBottom));
		if (!PlotTitle_.isEmpty ())
			result += plot.titleLabel ()->sizeHint ().height ();
		return result;
	}
}
}
