/**------------------------------------------------------------------------
 * @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: Maxim Trokhimtchouk (trokhim@cs.utoronto.ca)
 *
 * $Log: matchingdatabase.cpp,v $
 * Revision 1.1.1.1  2002/03/10 07:38:07  dmac
 * Imported sources
 *
 * Revision 1.2  2002/01/15 16:48:22  dmac
 * Bugs fixed
 *
 * Revision 1.1  2001/10/03 20:19:56  dmac
 * Added file
 *
 *
 *-----------------------------------------------------------------------*/

#include "matchingdatabase.hpp"

ostream& DAGPrototype::write(ostream& os)
{
	os.write(&count,sizeof(count));
	os.write(&view,sizeof(view));
	
	return os;
}

istream& DAGPrototype::read(istream& is)
{
	is.read(&count,sizeof(count));
	is.read(&view, sizeof(view));
	
	return is;
}

ostream& DAGType::write(ostream& os)
{
	size_t t;
	os.write(&count,sizeof(count));

	t = name.length() + 1;
	os.write(&t,sizeof(t));
	os.write(name.c_str(),t);
	
	return os;
}

istream& DAGType::read(istream& is)
{
	size_t t;
	char* namestr;

	is.read(&count, sizeof(count));
	is.read(&t, sizeof(t));

	namestr = new char[t];

	is.read(namestr, t);
	name = string(namestr);
	
	delete [] namestr;

	return is;
}

ostream& DAGView::write(ostream& os)
{
	os.write(&graph,sizeof(graph));
	os.write(&type,sizeof(type));
	os.write(&region,sizeof(region));
	
	return os;
}

istream& DAGView::read(istream& is)
{
	is.read(&graph, sizeof(graph));
	is.read(&type, sizeof(type));
	is.read(&region, sizeof(region));
	
	return is;
}

DAGView DAGRegion::GetPrototype()
{
}

ostream& DAGRegion::write(ostream& os)
{
  size_t size;
  ID id;

  size = views.size();

  os.write(&size, sizeof(size));

  for(; size > 0; size--)
    {
	  id = views[size];
      os.write(&id, sizeof(id));
    }

  return os;
}

istream& DAGRegion::read(istream& is)
{
	size_t size;
	ID id;

	//remove all existing records
	views.clear();
	
	//read in the size of the database
	is.read(&size, sizeof(size));
	views.resize(size);

	for(; size > 0; size--)
	{
		is.read(&id, sizeof(id));
		views[size] = id;
	}

	return is;
}

DAGSearchDatabase::DAGSearchDatabase()
{
	nnd.name = "";
	nnd.data = HnSRTreeFile::null;
	nnd.dimension = 0;
}

DAGSearchDatabase::~DAGSearchDatabase() 
{
  if(nnd.data!=HnSRTreeFile::null)
    {
      nnd.data.close();
    }
}

void DAGSearchDatabase::Add(ID prototype, const DAG& dag)
{
	leda_node v;
	DAGSearchRecord record;
	TSV h;
	HnPoint pn;

	record.prototype = prototype;

	//Check if database has been activated
	if(nnd.data!=HnSRTreeFile::null)
	{
		//for each node in the dag class record its TSV to the NND
		forall_nodes(v, dag)
	    {
			record.node = dag.GetNodeIndex(v);
			h = dag.GetNodeTSV(v, nnd.dimension);
			pn = TSVtoHnPoint(h);
			nnd.data.store(pn, new_HnDataItem((void*)(&record),sizeof(DAGSearchRecord)));
	    }
	}
}

void  DAGSearchDatabase::Remove(ID prototype, DAG& dag)
{
	leda_node v;

	DAGSearchRecord record;
	record.prototype = prototype;

	//Check if database has been activated
	if(nnd.data!=HnSRTreeFile::null)
	{
	  forall_nodes(v, dag)
	  {
		  record.node = dag.GetNodeIndex(v);
		  // I am not sure if I should allocate mdemory for each search record
		  nnd.data.remove(TSVtoHnPoint(dag.GetNodeTSV(v,nnd.dimension)), new_HnDataItem((void*)(&record),sizeof(DAGSearchRecord)));
	  }
	}
}

/*! 
	@brief Executes nearest-neighbour search.

	The `numNeighbors (n)' is not necessarily the maximum number of points to be obtained. 
	If the **farthest point** of a result set has multiple points at the same rank, 
	they are also included in the result set. 
*/
SmartArray<DAGSearchRecordEx> DAGSearchDatabase::GetClosest(const DAG& dag, const leda_node& querynode, int n)
{
	SmartArray<DAGSearchRecordEx> closestDAGs;

	//Check if database has been activated
	if(nnd.data != HnSRTreeFile::null && n >= 1)
	{
		int i, size;
		HnPoint pp;
		TSV tsv;
		DAGSearchRecordEx sr;

		HnPointVector points;
		HnDataItemVector dataItems;

		tsv = dag.GetNodeTSV(querynode, nnd.dimension);

		pp = TSVtoHnPoint(tsv);

		nnd.data.getNeighbors(pp, n, &points, &dataItems);

		closestDAGs.ReSize(size = dataItems.size());

		for(i = 0; i < size; i++)
		{
			sr = *(DAGSearchRecord*)(dataItems.elementAt(i).toCharArray());
			sr.nodeTSV = HnPointToTSV(points.elementAt(i));
			closestDAGs.Add(sr);
		}
	}

	return closestDAGs;
}

bool DAGSearchDatabase::Create(string& name, int dim)
{
    //check if the dimensions are larger or equal to the minimal dimensions allowed
    if(dim < Min_NND_Dimension) return false;

    //Check if database has been activated
	if(nnd.data==HnSRTreeFile::null)
	{
	  //create the near neighbor database
	  nnd.data = new_HnSRTreeFile(name.c_str(), dim, sizeof(DAGSearchRecord), HnProperties::null);

	  //check if the database was created successful
	  if(nnd.data == HnSRTreeFile::null)
	    {
	      //return false if an error occured
	      return false;
	    }
	  else
	    {
	      nnd.name = name;
	      nnd.dimension = dim;
	    }
	}
	else
	{
	  return false;
	}
	return true;
}

bool DAGSearchDatabase::Open(string& name)
{
  //close the indexing database
  if(nnd.data!=HnSRTreeFile::null)
  {
    nnd.data.close();
  }

  nnd.data = new_HnSRTreeFile(name.c_str(), "rw");
  if(nnd.data == HnSRTreeFile::null) return false;

  nnd.name = name;
  nnd.dimension = nnd.data.getDimension();
  return true;
}

istream&  DAGSearchDatabase::Read(istream& is)
{
  char* namestr;
  size_t t;
  string name;
  int dim;
  
  //close the indexing database
  if(nnd.data!=HnSRTreeFile::null)
  {
    nnd.data.close();
  }
  
  is.read(&t,sizeof(t));
  namestr = new char[t];
  
  is.read(namestr, t);
  name = string(namestr);
  delete [] namestr;

  is.read(&dim,sizeof(dim));

  //load the near neighbor database
  nnd.data = new_HnSRTreeFile(name.c_str(), "rw");

  //check if the database was created successfuly
  if(nnd.data == HnSRTreeFile::null)
    {
      //if error occured set stream state to fail and return it
      is.setstate(ios::failbit);
      return is;
    }

     nnd.name = name;
     nnd.dimension = dim;
     
     return is;
}

ostream&  DAGSearchDatabase::Write(ostream& os)
{
  size_t t;

  t = nnd.name.length() + 1;

  os.write(&t,sizeof(t));
  os.write(nnd.name.c_str(), t);
  os.write(&nnd.dimension,sizeof(nnd.dimension));
  
  return os;
}

HnPoint DAGSearchDatabase::TSVtoHnPoint(const TSV& tsv)
{
	int d = tsv.GetSize();
	HnPoint hp = new_HnPoint(d);

	for(int i = 0; i < d; i++)
		hp.setCoordAt(tsv[i], i);

	return hp;
}

TSV DAGSearchDatabase::HnPointToTSV(const HnPoint& pt)
{
	int d = pt.getDimension();
	TSV tsv(d);

	for(int i = 0; i < d; i++) 
		tsv[i] = pt.getCoordAt(i);

	return tsv;
}
