//
// C++ Implementation: complexscorecalculationstrategy
//
// Description: 
//
//
// Author: Benjamin Mesing <bensmail@gmx.net>, (C) 2005
//
// Copyright: See COPYING file that comes with this distribution
//
//
#include "complexscorecalculationstrategy.h"

#include <vector>
#include <iostream>
#include <cassert>


// NApt
#include <ipackagedb.h>

// NPlugin
#include <packagenotfoundexception.h>


namespace NApt {

const float ComplexScoreCalculationStrategy::Scores::DIRECT_CS_MATCH = 20;
const float ComplexScoreCalculationStrategy::Scores::DIRECT_MATCH = 18;
const float ComplexScoreCalculationStrategy::Scores::WHOLE_CS_WORD = 15;
const float ComplexScoreCalculationStrategy::Scores::WHOLE_WORD = 14;
const float ComplexScoreCalculationStrategy::Scores::BORDER_MATCH = 8;
const float ComplexScoreCalculationStrategy::Scores::INNER_MATCH = 3;


float ComplexScoreCalculationStrategy::ScoreInformation::_maximumDescriptionScore = 0;

ComplexScoreCalculationStrategy::ComplexScoreCalculationStrategy(IPackageDB* pPackageDb)
{
	_pPackageDb = pPackageDb;
}


ComplexScoreCalculationStrategy::~ComplexScoreCalculationStrategy()
{
}

/////////////////////////////////////////////////////
// IScoreCalculationStrategy Interface
/////////////////////////////////////////////////////


void ComplexScoreCalculationStrategy::calculateScore(const set<string>& packages)
{
	qDebug("Calculating Score");
	assert(_includePatterns.size() != 0);
	vector<ScoreInformation> scoreInformation;
	scoreInformation.reserve(packages.size());
	// reset the maximum description count
	ScoreInformation::clearMaximumDescriptionScore();
	// collect the relevant information for each package
	for (set<string>::const_iterator it = packages.begin(); it != packages.end(); ++it)
	{
		try
		{
			ScoreInformation si = getScoreInformation(*it, _cs);
			scoreInformation.push_back(si);
		}
		// if the package was not found, add it with a score of 1
		catch (NPlugin::PackageNotFoundException e)
		{
			qDebug("PackageNotFoundException");
			setScore(*it, 1.0f);
		}
	}
	// calculate the scores for each package
	{
		const float includeNum = _includePatterns.size();
		float maxDescriptionScore = ScoreInformation::getMaximumDescriptionScore();
		// if maxDescriptionScore is 0 every description score is null, so we can choose any number
		// except 0 here...
		if (maxDescriptionScore == 0)
			maxDescriptionScore = 1;
		for (vector<ScoreInformation>::const_iterator it = scoreInformation.begin(); 
			it != scoreInformation.end(); ++it )
		{
			const ScoreInformation& si = *it;
			// normalize the scores by the maximum scores possible
			float nameScores = si.getNameScore() / (includeNum * Scores::DIRECT_CS_MATCH);
			float descriptionScores = si.getDescriptionScore() / maxDescriptionScore;
			float score = (3 * nameScores + descriptionScores) / 4.0f;
			setScore(si.package(), score);
		}
	}
}

/////////////////////////////////////////////////////
// Helper Methods
/////////////////////////////////////////////////////


ComplexScoreCalculationStrategy::ScoreInformation 
	ComplexScoreCalculationStrategy::getScoreInformation(const string& package, bool)  const
{
	ScoreInformation info(package);
	const IPackage& packageStruct = _pPackageDb->getPackageRecord(package);
	
	for (QStringList::const_iterator it = _includePatterns.begin(); it != _includePatterns.end(); ++it)
	{
		info.addNameScore(getNameScore(packageStruct, *it));
		info.addDescriptionScore(getDescriptionScore(packageStruct, *it));
	}
	return info;
}


float ComplexScoreCalculationStrategy::getNameScore(const IPackage& package, const QString& pattern)  const
{
	// if the pattern does not match at all
	if (package.name().indexOf(pattern, 0, Qt::CaseInsensitive) == -1)
		return 0;
	// if the package name equals (case insensitve) the pattern
	// (this is because it was found in the pattern and they have the same size)
	if (package.name().length() == pattern.length())
	{
		// if it equals case sensitive
		if (package.name() == pattern)
			return Scores::DIRECT_CS_MATCH;
		else
			return Scores::DIRECT_MATCH;
	}
	
	// the pattern was found, but does not equal the package name
	
	// take the highest rated match we found
	Matches m = findMatches(package.name(), pattern);
	if (m.wholeWordCsMatches > 0)
		return Scores::WHOLE_CS_WORD;
	if (m.wholeWordMatches > 0)
		return Scores::WHOLE_WORD;
	if (m.borderMatches)
		return Scores::BORDER_MATCH;
	if (m.innerMatches)
		return Scores::INNER_MATCH;
	assert(false);
	return 0;
}

float ComplexScoreCalculationStrategy::getDescriptionScore(const IPackage& package, const QString& pattern) const
{
	Matches m = findMatches(package.description(), pattern);
	return (m.wholeWordCsMatches * Scores::WHOLE_CS_WORD + m.wholeWordMatches * Scores::WHOLE_WORD
		+ m.innerMatches * Scores::INNER_MATCH + m.borderMatches * Scores::BORDER_MATCH) / pattern.length();
}

ComplexScoreCalculationStrategy::Matches 
	ComplexScoreCalculationStrategy::findMatches(const QString& string, const QString& pattern) const
{
	Matches matches;
	int i = 0;
	while ( (i = string.indexOf(pattern, i, Qt::CaseInsensitive)) != -1 )
	{
		// holds if the pattern was matched at the front boundary of a word
		bool front = false;
		if (i==0)
			front = true;
		else
		{
			QChar c = string.at(i-1);
			// if the character before is not a letter we have a boundary
			front = !c.isLetter();
		}
		// holds if the pattern was matched at the back boundary of a word
		bool back = false;
		if ( i + pattern.length() == string.length() )
			back = true;
		else
		{
			QChar c = string.at(i + pattern.length());
			back = !c.isLetter();
		}
		if (front && back)
		{
			QString substring = string.mid(i, pattern.length());
			if (substring == pattern)
				++matches.wholeWordCsMatches;
			else
				++matches.wholeWordMatches;
		}
		else if (front || back)
			++matches.borderMatches;
		else
			++matches.innerMatches;
 		i += pattern.length();
	}
	return matches;
}



}
