/***************************************************************************
 *   Copyright (C) 2005-2008 by Eugene V. Lyubimkin aka jackyf             *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License                  *
 *   (version 3 or above) as published by the Free Software Foundation.    *
 *                                                                         *
 *   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 GPL                        *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA               *
 ***************************************************************************/
#include <algorithm>

#include <QGridLayout>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QDialogButtonBox>
#include <QLabel>
#include <QComboBox>
#include <QCheckBox>
#include <QTableWidget>
#include <QMap>
#include <QPushButton>

#include <qwt/qwt_plot.h>
#include <qwt/qwt_plot_curve.h>
#include <qwt/qwt_plot_grid.h>
#include <qwt/qwt_text_label.h>
#include <qwt/qwt_scale_engine.h>

#include "StatsWidget.hpp"
#include "Setup.hpp"

StatsWidget::StatsWidget(const UserProfile& profile, QWidget* parent)
	: QDialog(parent), statsLogic(profile), profilePath(profile.getPath())
{
	QDialogButtonBox* okButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
	connect(okButtonBox, SIGNAL(accepted()), this, SLOT(accept()));

	initTable();
	initPlots();

	QLabel* categorizeByLabel = new QLabel(tr("Categorize by:"));
	QLabel* summarizeByLabel = new QLabel(tr("Summarize by:"));
	QLabel* categoryLabel = new QLabel(tr("Category:"));

	initCombos();

	QHBoxLayout* topLayout = new QHBoxLayout;
	topLayout->addWidget(categorizeByLabel);
	topLayout->addWidget(this->categorizeByCombo, 1);
	topLayout->addWidget(summarizeByLabel);
	topLayout->addWidget(this->summarizeByCombo, 1);
	topLayout->addWidget(categoryLabel);
	topLayout->addWidget(this->categoryCombo, 1);

	QHBoxLayout* checkBoxesLayout = new QHBoxLayout;
	checkBoxesLayout->addWidget(this->showGridsOnPlotsCheckBox);
	checkBoxesLayout->addWidget(this->setMinimumYValueOnPlotsToZeroCheckBox);

	QGridLayout* plotLayout = new QGridLayout;
	plotLayout->addLayout(checkBoxesLayout, 0, 0, 1, -1);
	plotLayout->addWidget(this->exerciseCountPlot, 1, 0);
	plotLayout->addWidget(this->symbolCountPlot, 2, 0);
	plotLayout->addWidget(this->timePlot, 1, 1);
	plotLayout->addWidget(this->speedAveragePlot, 2, 1);
	plotLayout->addWidget(this->mistakeAveragePlot, 1, 2);
	plotLayout->addWidget(this->rhythmAveragePlot, 2, 2);
	plotLayout->addWidget(this->indexPlot, 3, 0, 1, -1);
	plotLayout->setRowStretch(0, 0);
	plotLayout->setRowStretch(1, 3);
	plotLayout->setRowStretch(2, 3);
	plotLayout->setRowStretch(3, 4);

	QWidget* plotsTabWidget = new QWidget;
	plotsTabWidget->setLayout(plotLayout);


	QDialogButtonBox* tableButtonsWidget = new QDialogButtonBox;
	tableButtonsWidget->addButton(this->scrollDownStatsTableButton,
			QDialogButtonBox::ActionRole);

	QVBoxLayout* tableTabLayout = new QVBoxLayout;
	tableTabLayout->addWidget(this->statsTable);
	tableTabLayout->addWidget(tableButtonsWidget);

	QWidget* tableTabWidget = new QWidget;
	tableTabWidget->setLayout(tableTabLayout);


	initCombinationsAndMistakesTables();

	this->tabs = new QTabWidget;
	this->tabs->addTab(tableTabWidget, tr("Table"));
	this->tabs->addTab(plotsTabWidget, tr("Plots"));
	this->tabs->addTab(this->mistakesTable, tr("Mistakes"));
	this->tabs->addTab(this->combinationsTable, tr("Combinations"));

	QVBoxLayout* layout = new QVBoxLayout;
	layout->addLayout(topLayout);
	layout->addWidget(tabs);
	layout->addWidget(okButtonBox);

	this->setLayout(layout);

	this->initPlotsFormat();

	//this->renewStatsViews();
	this->setWindowTitle(tr("Statistics"));

	this->readGuiSettings();

	connect(this, SIGNAL(finished(int)), this, SLOT(writeGuiSettings()));
}

void StatsWidget::initTable()
{
	this->statsTable = new QTableWidget;
	this->statsTable->setColumnCount(7);

	QStringList captions;
	captions
		<< tr("Exercise count")
		<< tr("Symbol count")
		<< tr("Time, sec.")
		<< tr("Speed average, symb./sec.")
		<< tr("Mistake average")
		<< tr("Rhythm average, %")
		<< tr("Index")
	;
	this->statsTable->setHorizontalHeaderLabels(captions);
	this->statsTable->resizeColumnsToContents();

	this->scrollDownStatsTableButton = new QPushButton(tr("Scroll down"));
	QString scrollDownKeySequence("Ctrl+J");
	this->scrollDownStatsTableButton->setShortcut(QKeySequence(scrollDownKeySequence));
	this->scrollDownStatsTableButton->setToolTip(scrollDownKeySequence);
	connect(this->scrollDownStatsTableButton, SIGNAL(clicked()),
			this->statsTable, SLOT(scrollToBottom()));
}

void StatsWidget::initPlots()
{
	this->exerciseCountPlot = new QwtPlot(tr("Exercise count"));
	this->exerciseCountCurve = new QwtPlotCurve();
	this->exerciseCountCurve->setPen(QPen(Qt::yellow));
	this->exerciseCountCurve->attach(exerciseCountPlot);

	this->symbolCountPlot = new QwtPlot(tr("Symbol count"));
	this->symbolCountCurve = new QwtPlotCurve();
	this->symbolCountCurve->setPen(QPen(Qt::blue));
	this->symbolCountCurve->attach(symbolCountPlot);

	this->timePlot = new QwtPlot(tr("Time, sec."));
	this->timeCurve = new QwtPlotCurve();
	this->timeCurve->setPen(QPen(Qt::cyan));
	this->timeCurve->attach(timePlot);

	this->speedAveragePlot = new QwtPlot(tr("Speed average, symb./sec."));
	this->speedAverageCurve = new QwtPlotCurve();
	this->speedAverageCurve->setPen(QPen(Qt::red));
	this->speedAverageCurve->attach(speedAveragePlot);

	this->mistakeAveragePlot = new QwtPlot(tr("Mistake average"));
	this->mistakeAverageCurve = new QwtPlotCurve();
	this->mistakeAverageCurve->setPen(QPen(Qt::magenta));
	this->mistakeAverageCurve->attach(mistakeAveragePlot);

	this->rhythmAveragePlot = new QwtPlot(tr("Rhythm average, %"));
	this->rhythmAverageCurve = new QwtPlotCurve();
	this->rhythmAverageCurve->setPen(QPen(Qt::green));
	this->rhythmAverageCurve->attach(rhythmAveragePlot);

	this->indexPlot = new QwtPlot(tr("Index"));
	this->indexCurve = new QwtPlotCurve();
	this->indexCurve->setPen(QPen(Qt::darkCyan));
	this->indexCurve->attach(indexPlot);

	this->showGridsOnPlotsCheckBox = new QCheckBox(tr("Show grids on plots"));
	connect(this->showGridsOnPlotsCheckBox, SIGNAL(stateChanged(int)), this, SLOT(renewGridsOnPlots()));

	this->setMinimumYValueOnPlotsToZeroCheckBox = new QCheckBox(tr("Set minimum Y value to 0"));
	// this slot updates plots as part of its work
	connect(this->setMinimumYValueOnPlotsToZeroCheckBox, SIGNAL(stateChanged(int)),
			this, SLOT(renewCategoryView()));
}

void StatsWidget::initPlotsFormat()
{
	foreach (QwtPlot* plot, this->findChildren<QwtPlot*>())
	{
		plot->setMinimumSize(200, 150);
		QFont font = plot->titleLabel()->font();
		font.setPointSize(10);
		plot->titleLabel()->setFont(font);
		plot->enableAxis(QwtPlot::xBottom, false);
		plot->setAxisAutoScale(QwtPlot::yLeft);
		plot->setAxisMaxMajor(QwtPlot::yLeft, 6);

		plot->setAutoReplot(true);

		QwtPlotGrid* indexGrid = new QwtPlotGrid;
		QPen indexGridPen;
		indexGridPen.setColor(QColor(128, 128, 128, 96));
		indexGrid->setPen(indexGridPen);
		indexGrid->enableX(false);
		indexGrid->attach(plot);
		this->gridList.append(indexGrid);
	}


	QwtPlotCurve* curves[] = {
		this->exerciseCountCurve,
		this->symbolCountCurve,
		this->timeCurve,
		this->speedAverageCurve,
		this->mistakeAverageCurve,
		this->rhythmAverageCurve,
		this->indexCurve
	};
	for (size_t i = 0; i < sizeof(curves)/sizeof(curves[0]); ++i)
	{
		curves[i]->setRenderHint(QwtPlotItem::RenderAntialiased);

		QPen pen = curves[i]->pen();
		pen.setWidth(2);
		curves[i]->setPen(pen);
		QColor penColor = pen.color();
		QColor brushColor(penColor.red(), penColor.green(), penColor.blue(), 64);
		curves[i]->setBrush(brushColor);
	}
}

void StatsWidget::initCombos()
{
	this->categorizeByCombo = new QComboBox;
	connect(this->categorizeByCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(renewSummarizeByCombo()));
	this->summarizeByCombo = new QComboBox;
	connect(this->summarizeByCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(renewStatsViews()));
	this->categoryCombo = new QComboBox;
	connect(this->categoryCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(renewCategoryView()));

	const vector<StatsLogic::CategorizeInfo>& categorizeInfo = statsLogic.categorizeInfo();

	this->categorizeByCombo->blockSignals(true);
	for (size_t i = 1; i < categorizeInfo.size(); ++i) // except "by exercise"
	{
		this->categorizeByCombo->addItem(categorizeInfo[i].caption);
	}
	this->categorizeByCombo->blockSignals(false);
}

void StatsWidget::initCombinationsAndMistakesTables()
{
	this->mistakesTable = new QTableWidget;
	this->mistakesTable->setColumnCount(2);

	QStringList mistakesTableHorizontalHeaders;
	mistakesTableHorizontalHeaders
		<< tr("Mistake")
		<< tr("Count")
	;
	this->mistakesTable->setHorizontalHeaderLabels(mistakesTableHorizontalHeaders);

	this->combinationsTable = new QTableWidget;
	this->combinationsTable->setColumnCount(2);
	QStringList combinationsTableHorizontalHeaders;
	combinationsTableHorizontalHeaders
		<< tr("Combination")
		<< tr("Count")
	;
	this->combinationsTable->setHorizontalHeaderLabels(combinationsTableHorizontalHeaders);
}

void StatsWidget::renewStatsViews()
{
	qDebug("StatsWidget::renewStatsViews()");

	StatsLogic::CategorizeType categorizeType =
		statsLogic.categorizeInfo()[this->categorizeByCombo->currentIndex() + 1].type;

	StatsLogic::CategorizeType summarizeType =
		statsLogic.categorizeInfo()[this->summarizeByCombo->currentIndex()].type;

	this->cachedStats = statsLogic.categorize(
		categorizeType,
		summarizeType);

	if (this->cachedStats.empty())
	{
		this->categoryCombo->setEnabled(false);
		this->statsTable->setEnabled(false);
		foreach (QwtPlot* plot, this->findChildren<QwtPlot*>())
		{
			plot->setEnabled(false);
		}
	}
	else
	{
		this->categoryCombo->blockSignals(true);
		this->categoryCombo->setEnabled(true);
		this->categoryCombo->clear();
		for (size_t i = 0; i < cachedStats.size(); ++i)
		{
			this->categoryCombo->addItem(cachedStats[i].first);
		}
		this->categoryCombo->blockSignals(false);

		foreach (QwtPlot* plot, this->findChildren<QwtPlot*>())
		{
			plot->setEnabled(true);
		}
		this->statsTable->setEnabled(true);

		this->renewCategoryView();
	}
}

void StatsWidget::renewCategoryView()
{
	int categoryIndex = this->categoryCombo->currentIndex();
	qDebug("StatsWidget::renewCategoryView(): categoryIndex=%d", categoryIndex);
	const vector<StatsLogic::DisplayStat>* categoryStatsPointer =
		&cachedStats[categoryIndex].second;

	size_t size = categoryStatsPointer->size();
	// updating common table
	{
		this->statsTable->setRowCount(size);
		for (size_t i = 0; i < size; ++i)
		{
			const StatsLogic::DisplayStat& displayStat = (*categoryStatsPointer)[i];
			this->statsTable->setItem(i, 0, new QTableWidgetItem(
						QString::number(displayStat.stat.exerciseCount)));
			this->statsTable->setItem(i, 1, new QTableWidgetItem(
						QString::number(displayStat.stat.symbolCount)));
			this->statsTable->setItem(i, 2, new QTableWidgetItem(
						QString::number(displayStat.stat.time, 'f', 1)));
			this->statsTable->setItem(i, 3, new QTableWidgetItem(
						QString::number(displayStat.stat.speedAverage, 'f', 1)));
			this->statsTable->setItem(i, 4, new QTableWidgetItem(
						QString::number(displayStat.stat.mistakeAverage, 'f', 1)));
			this->statsTable->setItem(i, 5, new QTableWidgetItem(
						QString::number(displayStat.stat.rhythmAverage, 'f', 1)));
			this->statsTable->setItem(i, 6, new QTableWidgetItem(
						QString::number(int(displayStat.stat.index()))));

			this->statsTable->setVerticalHeaderItem(i, new QTableWidgetItem(displayStat.caption));
		}
		// settting items alignment
		int columnCount = this->statsTable->columnCount();
		for (size_t i = 0; i < size; ++i)
		{
			for (int j = 0; j < columnCount; ++j)
			{
				this->statsTable->item(i, j)->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
			}
		}

		StatsLogic::CategorizeType summarizeType =
				statsLogic.categorizeInfo()[this->summarizeByCombo->currentIndex()].type;
		this->statsTable->setColumnHidden(0, (summarizeType == StatsLogic::byExercise));
	}
	// updating mistakes
	{
		QStringList mistakesList;
		for (size_t i = 0; i < size; ++i)
		{
			mistakesList += (*categoryStatsPointer)[i].stat.mistakes;
		}
		loadCountedStringsIntoTable(mistakesList, this->mistakesTable);
	}
	// updating combinations
	{
		QStringList combinationsList;
		for (size_t i = 0; i < size; ++i)
		{
			combinationsList += (*categoryStatsPointer)[i].stat.combinations;
		}
		loadCountedStringsIntoTable(combinationsList, this->combinationsTable);
	}
	// updating plots
	{
		size_t& plotSize = size;

		double* x = new double[plotSize];
		for (size_t i = 0; i < plotSize; ++i)
		{
			x[i] = static_cast<double>(i+1);
		}

		double* y = new double[plotSize];

		#define NLKT_DISPLAY_STAT_EXT(STAT_STRING, STAT_VALUE) \
			{ \
				double y_max = 0.0; \
				for (size_t i = 0; i < plotSize; ++i) \
				{ \
					y[i] = static_cast<double>((*categoryStatsPointer)[i].stat. STAT_VALUE ); \
					if (y[i] > y_max) \
					{ \
						y_max = y[i]; \
					} \
				} \
				this-> STAT_STRING##Curve->setSamples(x, y, plotSize); \
				this-> STAT_STRING##Plot->axisScaleEngine(QwtPlot::yLeft)->setAttribute( \
						QwtScaleEngine::IncludeReference, \
						this->setMinimumYValueOnPlotsToZeroCheckBox->isChecked()); \
				this-> STAT_STRING##Plot->setAxisScale(QwtPlot::xBottom, 1.0, static_cast<double>(plotSize)); \
			}
		#define NLKT_DISPLAY_STAT(STAT_STRING) NLKT_DISPLAY_STAT_EXT(STAT_STRING, STAT_STRING)

		NLKT_DISPLAY_STAT(exerciseCount)
		NLKT_DISPLAY_STAT(symbolCount)
		NLKT_DISPLAY_STAT(time)
		NLKT_DISPLAY_STAT(speedAverage)
		NLKT_DISPLAY_STAT(mistakeAverage)
		NLKT_DISPLAY_STAT(rhythmAverage)
		NLKT_DISPLAY_STAT_EXT(index, index())

		#undef NLKT_DISPLAY_STAT
		#undef NLKT_DISPLAY_STAT_EXT

		delete [] x;
		delete [] y;
	}
}

void StatsWidget::renewSummarizeByCombo()
{
	qDebug("StatsWidget::renewSummarizeByCombo");

	this->summarizeByCombo->blockSignals(true);
	this->summarizeByCombo->clear();

	const vector<StatsLogic::CategorizeInfo>& categorizeInfo = statsLogic.categorizeInfo();

	for (int i = 0; i <= this->categorizeByCombo->currentIndex(); ++i)
	{
		this->summarizeByCombo->addItem(categorizeInfo[i].caption);
	}
	this->summarizeByCombo->setCurrentIndex(0); // one item - "by exercise" - exists anyway
	this->summarizeByCombo->blockSignals(false);
	this->renewStatsViews();
}

void StatsWidget::writeGuiSettings()
{
	QSettings settings;

	settings.beginGroup("StatsWidget");
		settings.setValue("pos", pos());
		settings.setValue("size", size());
		settings.setValue("show_grids", this->showGridsOnPlotsCheckBox->isChecked());
		settings.setValue("set_min_y_zero", this->setMinimumYValueOnPlotsToZeroCheckBox->isChecked());

		settings.beginGroup(profilePath);
			settings.setValue("tab_index", this->tabs->currentIndex());
			settings.setValue("categorize_index", this->categorizeByCombo->currentIndex());
			settings.setValue("summarize_index", this->summarizeByCombo->currentIndex());
			settings.setValue("category_index", this->categoryCombo->currentIndex());
		settings.endGroup();

	settings.endGroup();
}

void StatsWidget::readGuiSettings()
{
	QSettings settings;

	settings.beginGroup("StatsWidget");

		QPoint badPos(-1, -1);
		QPoint savedPoint = settings.value("pos", badPos).toPoint();
		if (savedPoint != badPos)
		{
			// this is avanturistic way to provide correct geometry restroing on X11 :(
			QRect geom = this->geometry();
			this->setGeometry(savedPoint.x(), savedPoint.y(), geom.width(), geom.height());
		}
		this->resize(settings.value("size", QSize(760, 560)).toSize());

		this->showGridsOnPlotsCheckBox->blockSignals(true);
		this->showGridsOnPlotsCheckBox->setChecked(settings.value("show_grids").toBool());
		this->showGridsOnPlotsCheckBox->blockSignals(false);
		this->renewGridsOnPlots();

		this->setMinimumYValueOnPlotsToZeroCheckBox->blockSignals(true);
		this->setMinimumYValueOnPlotsToZeroCheckBox->setChecked(settings.value("set_min_y_zero").toBool());
		this->setMinimumYValueOnPlotsToZeroCheckBox->blockSignals(false);

		settings.beginGroup(profilePath);
			this->tabs->setCurrentIndex(settings.value(("tab_index"), 0).toInt());

			int categorizeByIndex = settings.value("categorize_index", this->categorizeByCombo->count() - 1).toInt();
			this->categorizeByCombo->blockSignals(true);
			this->categorizeByCombo->setCurrentIndex(categorizeByIndex);
			this->categorizeByCombo->blockSignals(false);
			this->renewSummarizeByCombo();

			int summarizeByIndex = settings.value("summarize_index", 0).toInt();
			this->summarizeByCombo->setCurrentIndex(summarizeByIndex);

			int categoryIndex = settings.value("category_index", this->categoryCombo->count() - 1).toInt();
			this->categoryCombo->setCurrentIndex(categoryIndex);

		settings.endGroup();

	settings.endGroup();
}

void StatsWidget::loadCountedStringsIntoTable(const QStringList& strings, QTableWidget* tableWidget)
{
	QMap<QString, size_t> stringsMap;
	foreach (const QString str, strings)
	{
		stringsMap[str] += 1;
	}
	tableWidget->setRowCount(stringsMap.size());

	QMultiMap<size_t, QString> countedStringsMap;
	{
		QMap<QString, size_t>::const_iterator cit;
		for (cit = stringsMap.constBegin(); cit != stringsMap.constEnd(); ++cit)
		{
			countedStringsMap.insert(cit.value(), cit.key());
		}
	}
	{
		int i = tableWidget->rowCount() - 1;
		QMultiMap<size_t, QString>::const_iterator cit;
		for (cit = countedStringsMap.constBegin(); cit != countedStringsMap.constEnd(); ++cit, --i)
		{
			tableWidget->setItem(i, 0, new QTableWidgetItem(cit.value()));
			tableWidget->setItem(i, 1, new QTableWidgetItem(QString::number(cit.key())));
		}
	}

}

void StatsWidget::renewGridsOnPlots()
{
	qDebug("renewGridsOnPlots");
	bool visible = this->showGridsOnPlotsCheckBox->isChecked();
	foreach (QwtPlotGrid* grid, this->gridList)
	{
		grid->setVisible(visible);
	}
}

