/**------------------------------------------------------------------------
 * @file
 *
 * Copyright 2001 by the University of Toronto (UT).
 *
 *                       All Rights Reserved
 * 
 * Permission to copy and modify this software and its documentation 
 * only for internal use in your organization is hereby granted, 
 * provided that this notice is retained thereon and on all copies.  
 * UT makes no representations as to the suitability and operability 
 * of this software for any purpose.  It is provided "as is" without 
 * express or implied warranty.
 *
 * UT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT
 * SHALL UT BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
 * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 * No other rights, including for example, the right to redistribute 
 * this software and its documentation or the right to prepare 
 * derivative works, are granted unless specifically provided in a 
 * separate license agreement.
 *
 * Author: Diego Macrini
 * 
 * $Log: Stats.cpp,v $
 *
 *-----------------------------------------------------------------------*/
#include <DirWalker.h>
#include "viewpoints.h"
#include "Stats.h"

using namespace std;

void STATINFO::InitStats()
{
	bCompViewStats = false;
	
	queryObj.Clear();
	rightClass.Clear();
	wrongClass.Clear();
	neigType1.Clear();
	neigType2.Clear();
	topRanked.Clear();

	nParamsToSet = 6;

	m_compTime = -1;
}

int STATINFO::FindDAGPos(const DAG* pQueryDag) const
{
	const DAG* pModelDag;
	
	for (int i = 0; i < matchList.size(); i++)
	{
		pModelDag = matchList[i].ptrDAG;
		
		if (pModelDag == NULL)
			continue;
		
		if (pQueryDag->GetObjName() == pModelDag->GetObjName() &&
		    pQueryDag->GetViewNumber() == pModelDag->GetViewNumber())
			return i;
	}

	return NOT_SET;
}

int STATINFO::FindDAGPos(int nDAGId) const
{
	for (int i = 0; i < matchList.size(); i++)
		if (matchList[i].nDAGId == nDAGId)
			return i;

	return NOT_SET;
}

/*!
	@brief Computes the statistics according to a specified data format.
*/

void STATINFO::ComputeStats(const DAG* pQueryDag, DATA_FORMAT fileFormatId)
{
	if (fileFormatId == UOFT_FORMAT)
		ComputeStatsFromUofTData(pQueryDag);
	else if (fileFormatId == PBS_FORMAT)
		ComputeStatsFromPBSData(pQueryDag);
	else if (fileFormatId == KIMIA_FORMAT)
		ComputeStatsFromKimiaData(pQueryDag);
	else
		ASSERT(false);
}

/*static*/
void STATINFO::RemoveQueryExemplar(const DAG* pQueryDag, leda_array<MatchInfo>& rankList)
{
	const DAG* pModelDag;
	int numModels = 0;

	for (int i = 0; i < rankList.size(); i++)
	{
		pModelDag = rankList[i].ptrDAG;

		if (pModelDag != NULL && pQueryDag->GetObjName() != pModelDag->GetObjName())
			numModels++;
	}

	leda_array<MatchInfo> newList(numModels);

	for (int i = 0, j = 0; i < rankList.size(); i++)
	{
		pModelDag = rankList[i].ptrDAG;

		if (pModelDag != NULL && pQueryDag->GetObjName() != pModelDag->GetObjName())
			newList[j++] = rankList[i];
	}

	rankList = newList;
}

void STATINFO::ComputeStatsFromKimiaData(const DAG* pQueryDag)
{
	using namespace std;

	const DAG* pModelDag;

	InitStats();

	// For simplicity, we first find the position of the query DAG
	// so that we can account for the fact that it should not be ranked.
	queryObj.SetRankPos(FindDAGPos(pQueryDag), matchList);

	const int v0 = pQueryDag->GetViewNumber();

	int nParamSet = 0;

	// Now, look for the DAGs that will give the statistics we need.
	for (int i = 0; i < matchList.size() && nParamSet < 2; i++)
	{
		pModelDag = matchList[i].ptrDAG;

		if (matchList[i].dSimilarity == 0.0 || pModelDag == NULL)
			continue;

		if (topRanked.IsNotSet() && matchList[i].nDAGId != pQueryDag->GetDAGId())
			topRanked.SetRankPos(i, matchList);

		if (pQueryDag->GetObjName() == pModelDag->GetObjName())
		{
			int v = pModelDag->GetViewNumber();

			if (rightClass.IsNotSet() && v != v0)
			{
				rightClass.SetRankPos(i, matchList, queryObj);
				nParamSet++;
			}
		}
		else if (wrongClass.IsNotSet()) {
			wrongClass.SetRankPos(i, matchList, queryObj);
			nParamSet++;
		}
	}

	bCompViewStats = true;
}

void STATINFO::ComputeStatsFromPBSData(const DAG* pQueryDag)
{
	using namespace std;
	const DAG* pModelDag;
	char queryClass[MAX_PATH_SIZE];
	char modelClass[MAX_PATH_SIZE];
	int queryId, modelId;
	int queryView0, queryView1, modelView0, modelView1;

	RemoveQueryExemplar(pQueryDag, matchList);

	InitStats();

	DirWalker::SplitPrefixAndNumber(pQueryDag->GetObjName(), queryClass, &queryId);

	pQueryDag->GetViewNumbers(&queryView0, &queryView1);

	DBG_PRINT4(queryClass, queryId, queryView0, queryView1)

	// For simplicity, we first find the position of the query DAG
	// so that we can account for the fact that it should not be ranked.
	ASSERT(FindDAGPos(pQueryDag) == NOT_SET);
	//queryObj.SetRankPos(FindDAGPos(pQueryDag), matchList);

	int nParamSet = 1; // query dag is already set (ie, set to NOT_SET)

	// Now, look for the DAGs that will give the statistics we need.
	for (int i = 0; i < matchList.size() && nParamSet < GetParamCount(); i++)
	{
		pModelDag = matchList[i].ptrDAG;

		if (matchList[i].dSimilarity == 0.0 || pModelDag == NULL)
			continue;

		if (topRanked.IsNotSet() && matchList[i].nDAGId != pQueryDag->GetDAGId())
			topRanked.SetRankPos(i, matchList);

		DirWalker::SplitPrefixAndNumber(pModelDag->GetObjName(), modelClass, &modelId);

		pModelDag->GetViewNumbers(&modelView0, &modelView1);

		if (strcmp(queryClass, modelClass) == 0)
		{
			if (rightClass.IsNotSet() && queryId != modelId)
			{
				rightClass.SetRankPos(i, matchList, queryObj);
				nParamSet++;
			}

			if (neigType1.IsNotSet() && (queryView0 == modelView0 && queryView1 == modelView1))
			{
				neigType1.SetRankPos(i, matchList, queryObj);
				nParamSet++;

				//set also the second degree neighbour
				if (neigType2.IsNotSet())
				{
					neigType2.SetRankPos(i, matchList, queryObj);
					nParamSet++;
				}
			}

			if (neigType2.IsNotSet() && 
				(abs(queryView0 - modelView0) < 20 && abs(queryView1 - modelView1) < 20))
			{
				neigType2.SetRankPos(i, matchList, queryObj);
				nParamSet++;
			}
		}
		else if (wrongClass.IsNotSet()) {
			wrongClass.SetRankPos(i, matchList, queryObj);
			nParamSet++;
		}
	}

	bCompViewStats = true;
}

/*!
	@brief Computes the statistics of neighboring views by ranking them.
*/
void STATINFO::ComputeStatsFromUofTData(const DAG* pQueryDag)
{
	using namespace std;
	
	int i, v, v1 = 0, v2 = 0, v3 = 0;
	const DAG* pModelDag;
	
	const int v0 = pQueryDag->GetViewNumber();
	ASSERT(v0 > 0);

	InitStats();

	GetClosestViews(v0, 3, v1, v2, v3);

	SmartArray<int> secLevClosViews(0, 9);
	GetClosestViews2(v0, 3, secLevClosViews, false);

	ASSERT(v1 != v0 && v2 != v0 && v3 != v0);
	ASSERT(v1 != v2 && v1 != v3 && v2 != v3);
	ASSERT(v1 > 0 && v2 > 0 && v3 > 0);
	ASSERT(v1 <= MAXVIEW && v2 <= MAXVIEW && v3 <= MAXVIEW);

	// For simplicity, we first find the position of the query DAG
	// so that we can account for the fact that it should not be ranked.
	queryObj.SetRankPos(FindDAGPos(pQueryDag), matchList);

	int nParamSet = 1; // query dag is already set

	// Now, look for the DAGs that will give the statistics we need.
	for (i = 0; i < matchList.size() && nParamSet < GetParamCount(); i++)
	{
		pModelDag = matchList[i].ptrDAG;

		if (matchList[i].dSimilarity == 0.0 || pModelDag == NULL)
			continue;

		if (topRanked.IsNotSet() && matchList[i].nDAGId != pQueryDag->GetDAGId())
			topRanked.SetRankPos(i, matchList);

		if (pQueryDag->GetObjName() == pModelDag->GetObjName())
		{
			v = pModelDag->GetViewNumber();

			if (rightClass.IsNotSet() && v != v0)
			{
				rightClass.SetRankPos(i, matchList, queryObj);
				nParamSet++;
			}

			if (neigType1.IsNotSet() && (v == v1 || v == v2 || v == v3))
			{
				neigType1.SetRankPos(i, matchList, queryObj);
				nParamSet++;

				//set also the second degree neighbour
				if (neigType2.IsNotSet())
				{
					neigType2.SetRankPos(i, matchList, queryObj);
					nParamSet++;
				}
			}

			if (neigType2.IsNotSet())
			{
				for (int n = 0; n < secLevClosViews.GetSize(); n++)
				{
					if (secLevClosViews[n] == v)
					{
						neigType2.SetRankPos(i, matchList, queryObj);
						nParamSet++;
						break;
					}
				}
			}
		}
		else if (wrongClass.IsNotSet()) {
			wrongClass.SetRankPos(i, matchList, queryObj);
			nParamSet++;
		}
	}

	bCompViewStats = true;
}

/*!
	@brief Computes the ranking position in say, the indexing ranking, of certain
	matchings coming from the ranking of the matcher. This situation is actually
	the intended use of the function.
*/
void STATINFO::ComputeCrossRankingIdxStats(const STATINFO& matStats)
{
	// For simplicity, we first find the position of the query DAG
	// so that we can account for the fact that it should not be ranked.
	queryObj.SetIdxPos(FindDAGPos(queryObj.GetDAGId()), matchList);

	int i, nDAGId, nParamSet = 1; // query dag is already set

	for (i = 0; i < matchList.size() && nParamSet < GetParamCount(); i++)
	{
		nDAGId = matchList[i].nDAGId;

		if (nDAGId == matStats.topRanked.GetDAGId()) {
			topRanked.SetIdxPos(i, matchList, queryObj);
			nParamSet++;
		}

		if (nDAGId == matStats.rightClass.GetDAGId()) {
			rightClass.SetIdxPos(i, matchList, queryObj);
			nParamSet++;
		}

		if (nDAGId == matStats.wrongClass.GetDAGId()) {
			wrongClass.SetIdxPos(i, matchList, queryObj);
			nParamSet++;
		}

		if (nDAGId == matStats.neigType1.GetDAGId()) {
			neigType1.SetIdxPos(i, matchList, queryObj);
			nParamSet++;
		}

		if (nDAGId == matStats.neigType2.GetDAGId()) {
			neigType2.SetIdxPos(i, matchList, queryObj);
			nParamSet++;
		}
	}
}

/*static*/
void STATINFO::WriteHeader(ostream& os, int nFileIdx, int nIdxKBest, double dMaxOcclusionRate)
{
	os <<
		"\n% (1)idx (2)view# (3)comptime (4)occlusionRate "
		"(5-10)matRightClass (11-16)matNeigType1 (17-22)matNeigType2 "
		"(23-28)matWrongClass (29-34)matTopRanked "
		"(35-40)idxRightClass (41-46)idxNeigType1 (47-52)idxNeigType2 "
		"(53-58)idxWrongClass (59-64)idxTopRanked\n\n";
		
	//os << "% INFOVAR fields: nDAGId, dSimilarity, nBestRankingPosition"
	//	", nWorstRankingPosition, nBestIdxPos, nWorstIdxPos\n\n";

	os << "% INFOVAR fields: nDAGId, dSimilarity, nBestRankingPosition"
		", nWorstRankingPosition, nModelDBId, nQueryDBId\n\n";
	
	os << "%kBest=" << nIdxKBest << ";\n";
	os << "%maxOcclusionRate=" << dMaxOcclusionRate << ";\n";

	ASSERT(nFileIdx >= 0 || nFileIdx == -1);

	if (nFileIdx >= 0)
		os << "matchingStats" << nFileIdx << " = [";
	else
		os << "matchingStats = [";
}

/*static*/
void STATINFO::Write(ostream& os, int i, int nViewNum, const STATINFO& idxSt, const STATINFO& matSt,
	double dCompTime, double dOcclusionRate /*=0*/)
{
	ASSERT(matSt.bCompViewStats);

	os << '\n' << i << ' '
   		<< nViewNum << ' '
   		<< dCompTime << ' '
		<< dOcclusionRate << ' '
   		<< matSt << ' ';

	if (idxSt.bCompViewStats)
   		os << idxSt;

	os << flush;
 }

/*static*/
void STATINFO::WriteTailer(ostream& os)
{
	os << "\n];\n" << flush;
}

ostream& operator<<(ostream& os, const STATINFO& v)
{
	return os << v.rightClass << ' ' << v.neigType1 << ' '
		<< v.neigType2 << ' ' << v.wrongClass << ' ' << v.topRanked;
}

//////////////////////////////////////////////////////////////////
//

void STATINFO::INFOVAR::Clear()
{
	nDAGId      = NOT_SET;
	dSimilarity = NOT_SET;
	nDBId       = NOT_SET;
	nQueryDBId  = NOT_SET;

	nBestRankingPosition  = NOT_SET;
	nWorstRankingPosition = NOT_SET;

	nBestIdxPos  = NOT_SET;
	nWorstIdxPos = NOT_SET;
}

ostream& operator<<(ostream& os, const STATINFO::INFOVAR& v)
{
	return os << v.nDAGId << ' ' << v.dSimilarity << ' '
		<< v.nBestRankingPosition << ' ' << v.nWorstRankingPosition << ' '
		<< v.nDBId << ' ' << v.nQueryDBId;
		//<< v.nBestIdxPos << ' ' << v.nWorstIdxPos;
}

/*!
	@bried Sets the best and worst positions in the ranking along with
	the DAg id and the similarity value.
*/
void STATINFO::INFOVAR::SetRankPos(int currentRankPos, const leda_array<MatchInfo>& rankList)
{
	if (currentRankPos != NOT_SET)
	{
		const MatchInfo& mi = rankList[currentRankPos];

		nDAGId      = mi.nDAGId;
		dSimilarity = mi.dSimilarity;
		nDBId       = mi.nModelDBId;
		nQueryDBId  = mi.nQueryDBId;

		nBestRankingPosition  = GetBestPos(currentRankPos, rankList);
		nWorstRankingPosition = GetWorstPos(currentRankPos, rankList);
	}
	else
	{
		nDAGId      = NOT_SET;
		dSimilarity = NOT_SET;
		nDBId       = NOT_SET;
		nQueryDBId  = NOT_SET;

		nBestRankingPosition  = NOT_SET;
		nWorstRankingPosition = NOT_SET;
	}
}

/*!
	@brief Compensates for the presence of the query object in the MATCHING ranking
	when the query ranks better than the given model view.
*/
void STATINFO::INFOVAR::SetRankPos(int currentRankPos,
	const leda_array<MatchInfo>& rankList, const STATINFO::INFOVAR& queryPos)
{
	SetRankPos(currentRankPos, rankList);

	if (queryPos.nBestRankingPosition != NOT_SET &&
		queryPos.nBestRankingPosition < nBestRankingPosition)
	{
		nBestRankingPosition--;
		nWorstRankingPosition--;
	}
}

/*!
	@brief Sets the best and worst positions in ANOTHER ranking. It is used to
	have the associated index position corresponding to some matching position.
*/
void STATINFO::INFOVAR::SetIdxPos(int currentRankPos, const leda_array<MatchInfo>& rankList)
{
	if (currentRankPos != NOT_SET)
	{
		nBestIdxPos = GetBestPos(currentRankPos, rankList);
		nWorstIdxPos = GetWorstPos(currentRankPos, rankList);
	}
	else
	{
		nBestIdxPos = NOT_SET;
		nWorstIdxPos = NOT_SET;
	}
}

/*!
	@brief Compensates for the presence of the query object in the INDEXING ranking
	when the query ranks better than the given model view.
*/
void STATINFO::INFOVAR::SetIdxPos(int currentRankPos,
	const leda_array<MatchInfo>& rankList, const STATINFO::INFOVAR& queryPos)
{
	SetIdxPos(currentRankPos, rankList);

	if (queryPos.nBestIdxPos != NOT_SET &&
		queryPos.nBestIdxPos < nBestIdxPos)
	{
		nBestIdxPos--;
		nWorstIdxPos--;
	}
}

bool STATINFO::INFOVAR::IsNotSet() const
{
	return nBestRankingPosition == NOT_SET;
}

/*!
	@brief computes the "best position" when accounting for other
	matches with the same similarity value.

	static function
*/
int STATINFO::INFOVAR::GetBestPos(int i, const leda_array<MatchInfo>& matches)
{
	double sim = matches[i].dSimilarity;

	while (i > 0 && matches[i - 1].dSimilarity == sim)
		i--;

	return i;
}

/*!
	@brief computes the "worst position" when accounting for other
	matches with the same similarity value.

	static function
*/
int STATINFO::INFOVAR::GetWorstPos(int i, const leda_array<MatchInfo>& matches)
{
	double sim = matches[i].dSimilarity;

	while (i < matches.size() - 1 && matches[i + 1].dSimilarity == sim)
		i++;

	return i;
}


////////////////////////////////////////////////////////////////////////////
// Helper functions

/*!
	@brief Determines which are the neighbours of the given view in the viewing sphere

	There is a scripts that teselates the viewing sphere of an object. This script numerates
	the resulting views in a complex way, which needs to be considered when trying to determine
	the neighbours of one of the object's view.
*/
bool GetClosestViews(int i, int res, int& a, int& b, int& c)
{
	//CViewPoints pts(128);
	COctahedronPoints pts(res);

	int* pNeigPts = pts.GetNeighbours(i - 1, 3);

	a = pNeigPts[0] + 1;
	b = pNeigPts[1] + 1;
	c = pNeigPts[2] + 1;

	free(pNeigPts);

	return true;
}

bool GetClosestViews2(int i, int res, SmartArray<int>& neigs, bool bIncludeFirstLevel)
{
	int a[3], b[3];
	int j, k;

	GetClosestViews(i, res, a[0], a[1], a[2]);

	if (bIncludeFirstLevel)
		for (j = 0; j < 3; j++)
			neigs.AddTail(a[j]);

	for (j = 0; j < 3; j++)
	{
		GetClosestViews(a[j], res, b[0], b[1], b[2]);

		for (k = 0; k < 3; k++)
			if (b[k] != i)
				neigs.AddTail(b[k]);
	}

	return true;
}

SmartArray<int> Complement128(int tgt, const int* views, int n)
{
	int viewmap[128], i, j;
	SmartArray<int> comp(128 - n - 1);

	memset(viewmap, 0, 128 * sizeof(int));
	viewmap[tgt - 1] = 1;

	for (i = 0; i < n; i++)
	{
		ASSERT(viewmap[views[i]] == 0);
		viewmap[views[i]] = 1;
	}

	for (i = 0, j = 0; i < 128; i++)
		if (viewmap[i] == 0)
			comp[j++] = i;

	ASSERT(j == comp.GetSize());

	return comp;
}



int CompInt(const void *elem1, const void *elem2 )
{
	return int(elem1) - int(elem2);
}

SmartArray<int> GetFarthestViews(int idx, int res, double deg)
{
	SmartArray<int> h1, h2, h, views;

	h1 = GetClosestViewsByRange(idx, 3, 90);
	h2 = Complement128(idx, h1, h1.GetSize());

	h2.Sort(CompInt);
	//DBG_VAL(h2.GetSize());
	h2.Print();
	cout << endl << endl;

	for (int i = 0; i < h2.GetSize(); i++)
	{
		h = GetClosestViewsByRange(h2[i] + 1, 3, 90);
		h.AddTail(h2[i]);
		h.Sort(CompInt);
		cout << endl << h2[i] << endl;
		h.Print();

		if (h == h2)
		{
			DBG_MSG1("GOT IT")
			views.AddTail(h2[i]);
		}
	}
	//DBG_VAL(views.GetSize());
	return views;
}

SmartArray<int> GetClosestViewsByRange(int i, int res, const double& range)
{
	COctahedronPoints pts(res);
	int n = 63;

	int* pNeigPts = pts.GetNeighbours(i - 1, range, n);
	//int* pNeigPts = pts.GetNeighbours(i - 1, n);
	SmartArray<int> views(n);

	for (int j = 0; j < n; j++)
		views.AddTail(pNeigPts[j]);

	free(pNeigPts);

	return views;
}

SmartArray<int> GetSymmetricViewMap()
{
	const int nViewsPerObj = 128, nClosestNeig = 127;
	COctahedronPoints pts(3);
	SmartArray<int> symmap(nViewsPerObj + 1); // view 0 isn't used
	int* pNeigPts;

	for (int i = 1; i <= nViewsPerObj; i++)
	{
		pNeigPts = pts.GetNeighbours(i - 1, nClosestNeig);
		symmap[i] = pNeigPts[nClosestNeig - 1] + 1;
		free(pNeigPts);
	}

	return symmap;
}

/*SmartArray<int> GetSymmetricViewMap()
{
	const int nViewsPerObj = 128;
	SmartArray<int> symmap, views;
	int viewmap[nViewsPerObj], i, j, k;

	symmap.Resize(nViewsPerObj + 1, true); // view 0 isn't used

	for (i = 1; i <= nViewsPerObj; i++)
	{
		views = GetClosestViewsByRange(i, 3, 182); // 178 = a bit less than 180.

		// Turn off all views
		memset(viewmap, 0, nViewsPerObj * sizeof(int));

		// Turn on views that are within 178 deg.
		for (j = 0; j < views.GetSize(); j++)
			viewmap[views[j] - 1] = 1;

		// Turn on the target view
		viewmap[i - 1] = 1;

		// There should be only one view off = 0
		for (k = 0; k < nViewsPerObj; k++)
			if (viewmap[k] == 0 && symmap[i] == 0)
				symmap[i] = k + 1;
			else if (viewmap[k] == 0 && symmap[i] != 0)
			{
				ShowError("Multiple symmetric views.");
				ASSERT(false);
			}
			else if (k == nViewsPerObj - 1 && symmap[i] == 0 && viewmap[k] != 0)
			{
				ShowError("One view is unmapped.");
				ASSERT(false);
			}
	}

	return symmap;
}*/

