/***************************************************************************
 *   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 <numeric>
#include <cmath>
#include <algorithm>

#include <yf/random/random.hpp>

#include "FortunesGetter.hpp"
#include "Logic.hpp"
#include "Setup.hpp"
#include "UserProfile.hpp"

Logic::Logic(FortunesGetter& fortunesGetter, UserProfile& userProfile)
	: fortunesGetter(fortunesGetter), userProfile(userProfile)
{
	exerciseState = NoExercise;
}

QChar Logic::randomFiller()
{
	using namespace yf::random;
	if (rnd_uint16(3))
	{
		return spaceChar;
	}
	else
	{
		const Layout& layout = userProfile.getLayout();
		return layout.getRandomChar();
	}
}

void Logic::startExercise()
{
	exerciseState = FirstPartOfExercise;
	mistakes.clear();
	currentTextPosition = 0;
	// preparing training text
	ExerciseResult prevExerciseResult = userProfile.exercises()[userProfile.exercises().size()-1];
	QString trainingText;
	for (size_t j = 0; j < worseTriLetterTrainingCount - 1; ++j)
	{
		trainingText += prevExerciseResult.worseTriLetter + this->randomFiller();
	}
	trainingText += prevExerciseResult.worseTriLetter + spaceChar;
	for (int i = 0; i < prevExerciseResult.mistakes.size(); ++i)
	{
		for (size_t j = 0; j < mistakeTrainingCount - 1; ++j)
		{
			trainingText += prevExerciseResult.mistakes[i] + this->randomFiller();
		}
		trainingText += prevExerciseResult.mistakes[i] + spaceChar;
	}

	this->typingText = trainingText;
	this->renewMaxAllowedMistakeCount();
	qDebug("typingText length = %d", typingText.size());
	emit startTyping(trainingText, "");
	this->signalAboutMistakesState();
}

void Logic::terminateExercise()
{
	exerciseState = NoExercise;
	emit endTyping();
}

void Logic::typedSymbol()
{
	if (exerciseState == SecondPartOfExercise)
	{
		this->touchTimestamp();
	}
	currentTextPosition += 1;
	//qDebug("currentTextPosition = %d", int(currentTextPosition));
	if (static_cast<int>(currentTextPosition) == typingText.size())
	{
		this->typedText();
	}
}

void Logic::errorOnSymbol()
{
	if (this->mistakes.size() == this->maxAllowedMistakeCount)
	{
		this->terminateExercise();
		emit tooManyMistakes();
	}
	else
	{
		mistakes.push_back(currentTextPosition);
		signalAboutMistakesState();
		this->typedSymbol();
	}
}

void Logic::renewMaxAllowedMistakeCount()
{
	using std::min;
	this->maxAllowedMistakeCount = min(maxAllowedMistakes, this->typingText.size() / lineLength + 1);
}

void Logic::signalAboutMistakesState()
{
	emit maxMistakeCount(this->maxAllowedMistakeCount);
	emit mistakeCount(mistakes.size());
}

void Logic::typedText()
{
	//qDebug("Logic::typedText");
	if (exerciseState == FirstPartOfExercise)
	{
		emit endTyping();
		exerciseState = SecondPartOfExercise;
		mistakes.clear();
		currentTextPosition = 0;
		typingTimestamps.clear();
		QString previousTriLetter = userProfile.exercises()[userProfile.exercises().size()-1].worseTriLetter;
		Fortune fortune = fortunesGetter.getFortune(previousTriLetter);
		typingText = fortune.phrase;
		this->renewMaxAllowedMistakeCount();
		emit startTyping(fortune.phrase, fortune.author);
		this->signalAboutMistakesState();
	}
	else if (exerciseState == SecondPartOfExercise)
	{
		exerciseState = NoExercise;

		// generating ExerciseResult
		qDebug("generating exercise result");
		ExerciseResult exerciseResult;
		exerciseResult.symbolCount = typingText.size();
		exerciseResult.millisecsSpent = yf::get_milli_time(
			typingTimestamps[0], typingTimestamps[typingTimestamps.size()-1])
			* yf::millisec_in_second;
		exerciseResult.worseTriLetter = this->getWorseTriLetter();
		exerciseResult.rhythmPercent = this->getRhythmPercent();
		for (size_t i = 0; i < mistakes.size(); ++i)
		{
			if (mistakes[i] == 0)
			{
				exerciseResult.mistakes.push_back(typingText.left(2));
				exerciseResult.mistakes.push_back(typingText.left(2));
			}
			else if (static_cast<int>(mistakes[i]) == typingText.size()-1)
			{
				exerciseResult.mistakes.push_back(typingText.right(2));
				exerciseResult.mistakes.push_back(typingText.right(2));
			}
			else
			{
				exerciseResult.mistakes.push_back(typingText.mid(mistakes[i]-1, 2));
				exerciseResult.mistakes.push_back(typingText.mid(mistakes[i], 2));
			}
		}
		exerciseResult.timestamp = time(NULL);

		userProfile.exerciseDone(exerciseResult);
		this->terminateExercise();

		// request user wait
		emit wait(exerciseResult.millisecsSpent / 10);

		emit exerciseFinished(exerciseResult);
	}
}

void Logic::touchTimestamp()
{
	typingTimestamps.push_back(yf::precise_time());
}

QString Logic::getWorseTriLetter() const
{
	size_t worseTriLetterIndex = -1;
	float maxTime = 0;
	for (size_t i = 1; i < typingTimestamps.size()-1; ++i)
	{
		float curTime = yf::get_milli_time(typingTimestamps[i-1], typingTimestamps[i+1]);
		if (curTime > maxTime)
		{
			maxTime = curTime;
			worseTriLetterIndex = i;
		}
	}
	return typingText.mid(worseTriLetterIndex - 1, 3);
}

size_t Logic::getRhythmPercent() const
{
	using std::fabs;

	std::vector<float> timeDistances;
	for (size_t i = 1; i < typingTimestamps.size()-1; ++i)
	{
		timeDistances.push_back(yf::get_milli_time(typingTimestamps[i], typingTimestamps[i+1]));
	}

	float averageTime = std::accumulate(timeDistances.begin(), timeDistances.end(), 0.0) / timeDistances.size();
	std::vector<float> timeDivergences;
	for (size_t i = 0; i < timeDistances.size(); ++i)
	{
		timeDivergences.push_back(fabs(averageTime - timeDistances[i]));
	}
	float divergence = 0.0;
	for (size_t i = 0; i < timeDivergences.size(); ++i)
	{
		divergence += timeDivergences[i] * timeDivergences[i];
	}
	divergence = sqrt(divergence) / timeDivergences.size();

	return 100.0 * (1.0 - divergence/averageTime);
}

