#ifndef PLOTERRFUNC_H
#define PLOTERRFUNC_H

#include <KLib/math/statistics/Statistics.h>

#include <KLib/misc/gnuplot/Gnuplot.h>
#include <KLib/misc/gnuplot/GnuplotPlot.h>
#include <KLib/misc/gnuplot/GnuplotPlotElementLines.h>


/**
 * helper class to plot error development stored within Statistics<T>.
 * statistics are given as pointers and may be altered outside.
 * when plot() is called, everything is rebuild using the current contents
 * of each Statistics<T> object
 */
class PlotErrFunc {

	/** group name and statistics together */
	struct Entry {
		std::string name;
		const K::Statistics<float>* stats;
		K::GnuplotPlotElementLines* line = nullptr;
		Entry(const std::string& name, const K::Statistics<float>* stats) : name(name), stats(stats) {;}
	};


	std::vector<Entry> entries;

	K::Gnuplot gp;
	K::GnuplotPlot gplot;

	//std::vector<std::string> colors = {"#000000", "#ff0000", "#00bb00", "#0000ff"};
	std::vector<std::string> colors = {"#000000", "#ff0000", "#00ff00", "#0000ff", "#00aaaa", "#aa00aa"};

	struct Markers {
		bool median = false;
		bool quantil75 = false;
	} markers;

	std::string codeFile;

	struct Range {
		int fromPercent = 0;
		int toPercent = 0;
		int increment = 0;
	} range;


public:

	/** empty ctor */
	PlotErrFunc() {
		setYRange(0, 90, 5);
	}

	/** ctor with x-axis label */
	PlotErrFunc(const std::string& xLabel, const std::string& yLabel) {
		gplot.getAxisX().setLabel(xLabel);
		gplot.getAxisY().setLabel(yLabel);
		setYRange(0, 90, 5);
		//gplot.getAxisX().setRange(K::GnuplotAxis::Range(0, K::GnuplotAxis::Range::AUTO));
		gplot.getAxisX().setRange(K::GnuplotAxis::Range(K::GnuplotAxis::Range::AUTO, K::GnuplotAxis::Range::AUTO));
	}

	const std::vector<Entry>& getEntries() {
		return entries;
	}

	/** set the percentage range to show */
	void setYRange(const int fromPercent, const int toPercent, const int increment = 5) {
		this->range.fromPercent = fromPercent;
		this->range.toPercent = toPercent;
		this->range.increment = increment;
		gplot.getAxisY().setRange(K::GnuplotAxis::Range(this->range.fromPercent, K::GnuplotAxis::Range::AUTO));
	}

	/** add one curve. Statistics<T> are allowed to be altered outside afterwards! */
	void add(const std::string name, const K::Statistics<float>* stats) {
		Entry entry(name, stats);
		entry.line = new K::GnuplotPlotElementLines();
		entry.line->setTitle(name);
		entry.line->getStroke().setWidth(2);
		gplot.add(entry.line);
		entries.push_back(entry);
	}

	/** remove all previously added entries */
	void clear() {
		for (Entry& e : entries) {
			gplot.remove(e.line);
			delete e.line;
		}
		entries.clear();
	}

	K::Gnuplot& getGP() {
		return gp;
	}

	K::GnuplotPlot& getPlot() {
		return gplot;
	}

	/** write the gnuplot commands to file for later re-use */
	void writePlotToFile(const std::string& file) {
		this->gp.writePlotToFile(file);
	}

	/** whether to show additional markers */
	void showMarkers(const bool median, const bool quantil75) {
		this->markers.median = median;
		this->markers.quantil75 = quantil75;
	}


	/** plot all curves */
	void plot() {

		gp << "set grid\n";

		for (size_t i = 0; i < entries.size(); ++i) {

			const Entry& e = entries[i];

			e.line->clear();
			e.line->getStroke().getColor().setHexStr(colors[i]);

			// distancen between min and max
			const float minX = e.stats->getQuantile(0);
			const float range = e.stats->getQuantile(0.85) - minX;
			const float space = range * 0.07;

			// [from:stepsSize:to]
			//for (int j = this->range.fromPercent; j <= this->range.toPercent; j += this->range.increment) {
			for (int j = 0; j <= 100; j += this->range.increment) {

				const float y = j / 100.0f;
				const float x = e.stats->getQuantile(y);
				K::GnuplotPoint2 gp2(x, y*100);
				e.line->add(gp2);

				// show additional markers?
				int id = (i+1) * 100;
				if (j == 50 && markers.median) {
					gp << "set object " << id+1 << " circle at " << x << "," << j << " size screen 0.02,0.02\n";
					gp << "set label " << id+2 << " at " << x+space << "," << j << " '" << x << "'\n";
					gp << "set arrow " << id+3 << " from " << x << "," << 0 << " to " << x << "," << j << " nohead\n";
					gp << "set arrow " << id+4 << " from " << 0 << "," << j << " to " << x << "," << j << " nohead\n";
				} else if (j == 75 && markers.quantil75) {
					gp << "set object " << id+5 << " circle at " << x << "," << j << " size screen 0.02,0.02\n";
					gp << "set label " << id+6 << " at " << x+space << "," << j << " '" << x << "'\n";
					gp << "set arrow " << id+7 << " from " << x << "," << 0 << " to " << x << "," << j << " nohead\n";
					gp << "set arrow " << id+8 << " from " << 0 << "," << j << " to " << x << "," << j << " nohead\n";
				}

			}

		}

		// render
		gp.draw(gplot);
		gp.flush();

	}

};

#endif // PLOTERRFUNC_H
