/**------------------------------------------------------------------------
 * @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: ViewSubsampler.cpp,v $
 *
 *
 *-----------------------------------------------------------------------*/
#include <fcntl.h> // for open()
#include <cerrno> // for errno
#include <cstdio> // for perror()

#include "ViewSubsampler.h"
//#include <UnixFuncPrototypes.h>

using namespace std;

#ifdef WIN32
#include <io.h>
#include <time.h>
int open(const char *filename, int oflag ) { return -1; }
void close(int fd) {}
void sleep(int time) {} 
#endif

#define RETRY_TIME_SECONDS 2
#define MAX_GOOD_MATCH_POS 10

int VWCompare(const void* elem1, const void* elem2 )
{
	const double& w1 = ((const VIEWWEIGHT*)elem1)->dVoteWeight;
	const double& w2 = ((const VIEWWEIGHT*)elem2)->dVoteWeight;
	
	return w1 == w2 ? 0:(w1 < w2 ? 1:-1);
}

bool LockFile::Lock(const char* szLockFileName, unsigned int nSleepTime)
{
	while (true)
    {
		fd = open(szLockFileName, O_WRONLY | O_CREAT | O_EXCL);
		
		if (fd < 0 && errno == EEXIST)
			sleep(nSleepTime); // the file already exist; another process is holding the lock
		else if (fd < 0)
		{
			perror("locking failed for the following reason");
			return m_bLocked = false;
		}
		else
			break;
	}

     // if we got here, we own the lock
     m_strFileName = szLockFileName;
     return m_bLocked = true;
}

void LockFile::Unlock()
{
	if (m_bLocked)
	{
		remove(m_strFileName);
		close(fd);
		m_bLocked = false;
	}
}

void ViewSubsampler::AddViewWeights(int nViewsPerObject, const DAG* pQueryDag,
	const leda_array<MatchInfo>& matchList, const SmartArray<int>& neigViews, int kBest)
{
	SmartArray<int> viewPos(nViewsPerObject);
	Record rec;
	VIEWWEIGHT vw;
	int i;
	
	rec.nIndexedWithKBest = kBest;
	rec.strObjName = pQueryDag->GetObjName();    	
	viewPos.Set(kBest);
	const int nViewNum = pQueryDag->GetViewNumber();
    	
	// Collect from the matching list all the views of a given object.
	for (i = 0; i < matchList.size(); i++)
	{
		const DAGPtr ptrDag = matchList[i].ptrDAG;
		
		// Stop searching when null ptr is reached
		if (ptrDag.IsNull())
			break;
    	
		// if it's a view of the target obj, save its position
		if (rec.strObjName == ptrDag->GetObjName())
		{
			viewPos[ptrDag->GetViewNumber() - 1] = i;
			
			// let's also add views that help for object recognition with negative position
			if (nViewNum != ptrDag->GetViewNumber() && i < MAX_GOOD_MATCH_POS)
			{
				vw.nViewNumber = ptrDag->GetViewNumber();
				vw.nViewRankPos = -i - 1; //-1 to avoid 0
				vw.dVoteWeight = CompVoteWeight(vw.nViewRankPos, kBest);
				rec.bmviews.AddTail(vw);
			}
		}
	}
    	
	// Check the ranking of the neghbouring views
	// and compute a vote according to it.
	for (i = 0; i < neigViews.GetSize(); i++)
	{
		vw.nViewNumber = neigViews[i];
		vw.nViewRankPos = viewPos[vw.nViewNumber - 1];		
		vw.dVoteWeight = CompVoteWeight(vw.nViewRankPos, kBest);
    	rec.bmviews.AddTail(vw);
	}

	m_viewVotes.append(rec);
}

void ViewSubsampler::SaveViewWeights(const char* szDataFileName)
{
	LockFile lockfile;
	
	// Proteck the writting operation from multiple-processes conflict
	if (!lockfile.Lock("lockfile.lck", RETRY_TIME_SECONDS))
	{
		cerr << "\nCan't lock 'lockfile.lck'. Job is cancelled" << endl;
		exit(1);
	}
	
	fstream outfile(szDataFileName, ios::out | ios::app);
	leda_list_item it;
	
	forall_items(it, m_viewVotes)
		m_viewVotes[it].Write(outfile);
	
	outfile.close();
	
	lockfile.Unlock();
}

bool ViewSubsampler::RetrieveViewWeights(const char* szDataFileName, int nViewsPerObject, bool bRankByObject)
{
	ObjViewArray objs;
	Record rec;
	int i;
	
	fstream infile(szDataFileName, ios::in);
	
	if (infile.fail())
		return false;
		
	cerr << "Retriving subsampling info from " << szDataFileName << endl;

	while (!infile.eof())
	{
		rec.Read(infile);
				
		// if it's the first time we see the obj name, prepare the array
		if (!objs.defined((const char*)rec.strObjName))
		{
			VWArray& cumVotes = objs[(const char*)rec.strObjName];
			cumVotes.ReSize(nViewsPerObject);
			
			for (i = 0; i < nViewsPerObject; i++)
				cumVotes[i].nViewNumber = i + 1;
		}
		
		// for all the views associated to the obj, add their vote to the unsorted-vote array
   		for (i = 0; i < rec.bmviews.GetSize(); i++)		
   		{
   			VWArray& cumVotes = objs[(const char*)rec.strObjName];
   			VIEWWEIGHT& vw = rec.bmviews[i];
   			VIEWWEIGHT& cumVote = cumVotes[vw.nViewNumber - 1];
				
   			ASSERT(cumVote.nViewNumber == vw.nViewNumber);
   			cumVote.dVoteWeight += CompVoteWeight(vw.nViewRankPos, rec.nIndexedWithKBest); //vw.dVoteWeight
   		}
	}
	
	leda_string strKey;
	
	// Sort views by object name or by their overall position
	if (bRankByObject)
	{
    	forall_defined(strKey, objs)
    	{
    		VWArray& cumVotes = objs[strKey];
    		cumVotes.Sort(VWCompare);
    		
    		VWArray& ranViews = m_rankedObjViews[strKey];
    		ranViews.ReSize(nViewsPerObject);
    		
    		for (i = 0; i < cumVotes.GetSize(); i++)
    		{
    			VIEWWEIGHT& vw = cumVotes[i];
    			
    			ranViews[vw.nViewNumber - 1].nViewRankPos = i;
    			ranViews[vw.nViewNumber - 1].dVoteWeight = vw.dVoteWeight;
    		}
    	}
    }
    else
    {
    	SmartArray<OBJVIEWWEIGHT> ovws(objs.size() * nViewsPerObject);
    	int j = 0;
    	
    	// collect all the votes in a sigle array and assoc each one to
    	// the corresponding obj name
    	forall_defined(strKey, objs)
    	{
    		m_rankedObjViews[strKey].ReSize(nViewsPerObject);
    		VWArray& cumVotes = objs[strKey];
    		String strObjName((const char*)strKey);
    		
    		for (i = 0; i < cumVotes.GetSize(); i++, j++)
    			ovws[j].Set(strObjName, cumVotes[i]);
    	}

    	ovws.Sort(VWCompare);
    	
    	for (i = 0; i < ovws.GetSize(); i++)
    	{
    		OBJVIEWWEIGHT& vw = ovws[i];
    		VWArray& ranViews = m_rankedObjViews[(const char*)vw.strObjName];
    			
    		ranViews[vw.nViewNumber - 1].nViewRankPos = i;
    		ranViews[vw.nViewNumber - 1].dVoteWeight = vw.dVoteWeight;
    	}
    }
	
	return true;
}

double ViewSubsampler::CompVoteWeight(int nPos, int kBest) const
{
	double w;
	
	if (nPos >= 0)
	{
    	if (nPos < 9)
    		w = 0.0;
    	/*else if (nPos < 30)
    		w = 1;
    	else if (nPos < kBest)
    		w = 1.5;
    	else
    		w = 2;*/
    	else
    		w = 1 + double(nPos) / double(kBest);
	}
	else
	{
		// nPos goes from -1 to -MAX_GOOD_MATCH_POS
		nPos = -nPos - 1; //make it positive, to simplify reading the eq.
		
		// if its well ranked, we want a negative weight, so that is a reward
		if (nPos < 4)
			w = -2 + (double(nPos) / double(3));
		else
			w = 0.0;
	}
		
	return w;
}

int RandInRange(int a, int b)
{
	return (int)(a + (1 + b-a )*(double)rand()/(RAND_MAX+1.0));
}

SmartArray<int> ViewSubsampler::RandomSubsampling(int nCurViewNum, int nNewViewNum)
{
	SmartArray<int> viewToRem(nCurViewNum);
	leda_list<int> list;
	leda_list_item it;

	ASSERT(nCurViewNum > nNewViewNum);

	viewToRem.Set(0);

	for (int i = 0; i < nCurViewNum; i++)
		list.push(i);
	
	srand((unsigned) time(NULL));

	while (list.size() != nNewViewNum)
	{
		it = list.get_item(RandInRange(0, list.size() - 1));
		viewToRem[list[it]] = 1;
		list.del_item(it);
	}

	return viewToRem;
}


