/*
  This file is part of the Astrometry.net suite.
  Copyright 2006, 2007 Dustin Lang, Keir Mierle and Sam Roweis.

  The Astrometry.net suite is free software; you can redistribute
  it and/or modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation, version 2.

  The Astrometry.net suite is distributed in the hope that it will be
  useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with the Astrometry.net suite ; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
*/

#include "dualtree.h"
#include "bl.h"

/*
 At each step of the recursion, we have a query node ("ynode") and a
 list of candidate search nodes ("nodes" and "leaves" in the "xtree").
 
 General idea:
 - if we've hit a leaf in the "ytree", callback results; done.
 - if there are only leaves, no "x"/search nodes left, callback results; done.
 - for each element in x node list:
 -   if decision(xnode, ynode)
 -     add children of xnode to child search list
 - recurse on ynode's children with the child search list
 - empty the child search list

 The search order is depth-first, left-to-right in the "y" tree.

*/
static void dualtree_recurse(kdtree_t* xtree, kdtree_t* ytree,
							 il* nodes, il* leaves,
							 int ynode, dualtree_callbacks* callbacks) {

	// annoyances:
	//   -trees are of different heights, so we can reach the leaves of one
	//    before the leaves of the other.  if we hit a leaf in the query
	//    tree, just call the result function with all the search nodes,
	//    leaves or not.  if we hit a leaf in the search tree, add it to
	//    the leaf-list.
	//   -we want to share search lists between the children, but that means
	//    that the children can't modify the lists - or if they do, they
	//    have to undo any changes they make.  if we only append items, then
	//    we can undo changes by remembering the original list length and removing
	//    everything after it when we're done.
	int leafmarker;
	il* childnodes;
	decision_function decision;
	void* decision_extra;
	int i, N;

	// if the query node is a leaf...
	if (KD_IS_LEAF(ytree, ynode)) {
		// ... then run the result function on each search node.
		result_function result = callbacks->result;
		void* result_extra = callbacks->result_extra;

		if (callbacks->start_results)
			callbacks->start_results(callbacks->start_extra, ytree, ynode);

		if (result) {
			// non-leaf nodes
			N = il_size(nodes);
			for (i=0; i<N; i++)
				result(result_extra, xtree, il_get(nodes, i), ytree, ynode);
		    // leaf nodes
			N = il_size(leaves);
			for (i=0; i<N; i++)
				result(result_extra, xtree, il_get(leaves, i), ytree, ynode);
		}
		if (callbacks->end_results)
			callbacks->end_results(callbacks->end_extra, ytree, ynode);

		return;
	}

	// if there are search leaves but no search nodes, run the result
	// function on each leaf.  (Note that the query node is not a leaf!)
	if (!il_size(nodes)) {
	    result_function result = callbacks->result;
	    void* result_extra = callbacks->result_extra;

		if (callbacks->start_results)
			callbacks->start_results(callbacks->start_extra, ytree, ynode);

		// leaf nodes
		if (result) {
			N = il_size(leaves);
			for (i=0; i<N; i++)
				result(result_extra, xtree, il_get(leaves, i), ytree, ynode);
		}

		if (callbacks->end_results)
			callbacks->end_results(callbacks->end_extra, ytree, ynode);

		return;
	}

	leafmarker = il_size(leaves);
	childnodes = il_new(32);
	decision = callbacks->decision;
	decision_extra = callbacks->decision_extra;


	N = il_size(nodes);
	for (i=0; i<N; i++) {
		int child1, child2;
		int xnode = il_get(nodes, i);
		if (!decision(decision_extra, xtree, xnode, ytree, ynode))
			continue;

		child1 = KD_CHILD_LEFT(xnode);
		child2 = KD_CHILD_RIGHT(xnode);

		if (KD_IS_LEAF(xtree, child1)) {
			il_append(leaves, child1);
			il_append(leaves, child2);
		} else {
			il_append(childnodes, child1);
			il_append(childnodes, child2);
		}
	}

	//printf("dualtree: start left child of y node %i is %i\n", ynode, KD_CHILD_LEFT(ynode));
	// recurse on the Y children!
	dualtree_recurse(xtree, ytree, childnodes, leaves,
					 KD_CHILD_LEFT(ynode), callbacks);
	//printf("dualtree: done left child of y node %i is %i\n", ynode, KD_CHILD_LEFT(ynode));
	//printf("dualtree: start right child of y node %i is %i\n", ynode, KD_CHILD_RIGHT(ynode));
	dualtree_recurse(xtree, ytree, childnodes, leaves,
					 KD_CHILD_RIGHT(ynode), callbacks);
	//printf("dualtree: done right child of y node %i is %i\n", ynode, KD_CHILD_LEFT(ynode));

	// put the "leaves" list back the way it was...
	il_remove_index_range(leaves, leafmarker, il_size(leaves)-leafmarker);
	il_free(childnodes);
}

void dualtree_search(kdtree_t* xtree, kdtree_t* ytree,
					 dualtree_callbacks* callbacks) {
	int xnode, ynode;
	il* nodes = il_new(32);
	il* leaves = il_new(32);
	// root nodes.
	xnode = ynode = 0;
	if (KD_IS_LEAF(xtree, xnode))
		il_append(leaves, xnode);
	else
		il_append(nodes, xnode);

	dualtree_recurse(xtree, ytree, nodes, leaves, ynode, callbacks);

	il_free(nodes);
	il_free(leaves);
}

