/**------------------------------------------------------------------------
 * @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
 *
 *-----------------------------------------------------------------------*/

#include <algorithm>
#include <DAGDatabase.h>
#include <DirWalker.h>
#include "DAGMemoryDBSet.h"
#include <BasicUtils.h>

#ifndef MAX_PATH_SIZE
#define MAX_PATH_SIZE 260
#endif

using namespace dml;

typedef std::list<int> IntList;
typedef std::vector<IntList> IntListVector;

class Combinations : public std::vector<IntListVector>
{
public:
	Combinations() { }

	void Init(const int n, const int k)
	{
		resize(n);

		for (int i = 0; i < n; i++)
		{
			IntList set;

			for (int j = 0; j < n; j++)
				if (j != i)
					set.push_back(j);

			IntListVector& v = operator[](i);

			NChooseK(v, IntList(), k, set.begin(), set.end());
		}
	}

	bool HasElement(unsigned int idx, unsigned int subIdx, int elem) const
	{
		ASSERT(idx < size());
		ASSERT(subIdx < (*this)[idx].size());

		const IntList& l = (*this)[idx][subIdx];

		return (std::find(l.begin(), l.end(), elem) != l.end());
	}

	static void NChooseK(IntListVector& v, const IntList& prefix, int k, 
			  IntList::iterator first, IntList::iterator last)
	{
		if (k == 0)
			v.push_back(prefix);
		else
		{
			for (IntList::iterator it = first; it != last; )
			{
				IntList newPrefix = prefix;

				newPrefix.push_back(*it);

				NChooseK(v, newPrefix, k - 1, ++it, last);
			}
		}
	}
};

bool DAGMemoryDBSet::LoadDAGs(const char* szPrefix, int from, int to)
{
	char szDBName[MAX_PATH_SIZE];
	const int dbCount = to - from + 1;
	int i, dbID;

	if (dbCount <= 0)
		return false;

	delete[] m_dbs;
	m_dbs = new DAGDatabase[dbCount];

	// Open each database
	for (i = 0; i < dbCount; i++)
	{
		dbID = from + i;

		sprintf(szDBName, "%s%d.db", szPrefix, dbID);

		ShowStatus1(szDBName, "is open");

		if (!m_dbs[i].Open(szDBName))
		{
			ShowOpenFileError(szDBName);
			return false;
		}
	}

	// Set the memory to hold all DAG arrays
	resize(m_dbs[0].GetDAGCount());

	unsigned int j;

	// Init each DAG array to the number of DBs
	for (j = 0; j < size(); j++)
		(*this)[j].resize(dbCount);

	// Read each dag in each database
	for (j = 0; j < size(); j++)
	{
		for (i = 0; i < dbCount; i++)
		{
			(*this)[j][i] = m_dbs[i].ReadDAG(j, true);
		}
	}

	return true;
}

void DAGMemoryDBSet::SetClassMap()
{
	const DAG* pDag;
	char className[MAX_PATH_SIZE];
	int exemplarId;

	for (unsigned int i = 0; i < DAGCount(); i++)
	{
		pDag = GetDAG(i);

		DirWalker::SplitPrefixAndNumber(pDag->GetObjName(), className, &exemplarId);

		std::vector<int>& inds = m_classMap[className];

		if (std::find(inds.begin(), inds.end(), exemplarId) == inds.end())
			inds.push_back(exemplarId);
	}

	/*ClassMap::iterator it;

	for (it = m_classMap.begin(); it != m_classMap.end(); it++)
	{
		std::vector<int>& inds = it->second;

		for (unsigned int i = 0; i < inds.size(); i++)
			std::cout << it->first << ", " << i << "(" << inds[i] << ")" << std::endl;
	}*/
}

bool DAGMemoryDBSet::SetDBDagPresence(int experimentId, int step, int param)
{
	const DAG* pQueryDag;
	const DAG* pModelDag;
	char queryClass[MAX_PATH_SIZE];
	char modelClass[MAX_PATH_SIZE];
	int queryId, modelId, queryIdx, modelIdx;
	int modelView0, modelView1;
	unsigned int i, j;
	bool bSameClass, bSameExemplar;
	double minDist = -1;

	//ASSERT(experimentId > 0);

	// Inits the presence of all dags to true
	InitDBDagPresence();

	SetClassMap();

	if (experimentId <= 0)
		return true; // we only needed to init the DAG presence

	Combinations combs;

	if (experimentId == 1)
	{
		ASSERT(step < 4);

		combs.Init(5, 4 - step);
	}
	else if (experimentId == 2)
	{
		minDist = (step + 1) / 2.0; // ie, 0.5, 1, 1.5, 2, 2.5, ... 
	}

	for (i = 0; i < DAGCount(); i++)
	{
		pQueryDag = GetDAG(i);

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

		queryIdx = FindExemplarIndex(queryClass, queryId);

		for (j = 0; j < DAGCount(); j++)
		{
			pModelDag = GetDAG(j);

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

			modelIdx = FindExemplarIndex(modelClass, modelId);

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

			// Check if the query and the model belong to the same class
			bSameClass = (strcmp(queryClass, modelClass) == 0);

			// Check if the model and query are the same exemplar too
			bSameExemplar = (bSameClass) ? (queryId == modelId) : false;

			// Reduce the number of model exemplars for the query class
			if (experimentId == 1 && bSameClass)
			{
				if (bSameExemplar)
					m_dbDagPresence[i][j] = false;
				else if (!combs.HasElement(queryIdx, param, modelIdx))
					m_dbDagPresence[i][j] = false;
			}
			// Reduce number of views for query class (param 0) or for 
			// ALL classes (param 1)
			else if (experimentId == 2)
			{
				if (bSameExemplar)
					m_dbDagPresence[i][j] = false;
				else if (bSameClass || param == 1)
				{
					double x = abs(modelView0);
					double y = abs(modelView1);

					if (x == 157 || x == 112)
						x += 0.5;

					x = (x - 135) / 22.5;
					y = y / 15;

					double d = sqrt(x * x + y * y);

					// minDist= 0.5 should not remove any view, as d == 0 or d >= 1
					if (d > 0 && d <= minDist)
						m_dbDagPresence[i][j] = false;
				}
			}
		}
	}

	return true;
}
