// ---------------------------------------------------------------------------
// 
//   Lily Engine alpha
// 
//   Copyright (c) 2010 Clifford Jolly
//     http://cliffjolly.com
//     cliff@cliffjolly.com
// 
// ---------------------------------------------------------------------------
// 
//   This program 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; either version 2 of the License, or
//   (at your option) any later version.
//  
//   This program 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 this program; if not, write to the Free Software
//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
//   USA
// -------------------------- END HEADER -------------------------------------

#include "../hacks/winhacks.h"

#include <assert.h>
#include <iostream>
using namespace std;

#include "boundingbox.h"
#include "quadtree.h"
#include "entity.h"

QuadTree::QuadTree(const BoundingBox &box, int depth) {
	maxDepth = 0;
	resize(box, depth);
}

QuadTree::~QuadTree(void) {
	
	// Detach all the Entities from the list.
	// They will need to be reinserted!
	for(unsigned int i = 0; i < nodeContents.size(); i++) {
		nodeContents[i]->quadTreeNode = NULL;
		nodeContents[i]->quadTreeListPosition = 0;
	}
	
	// Clean up children.
	if(maxDepth > 0) {
		for(unsigned int i = 0; i < 4; i++) {
			delete children[i];
		}
	}

}

void QuadTree::insert(Entity *ent) {
	assert(ent->quadTreeNode == NULL);
	
	BoundingBox box = ent->getBoundingBox();
	if(maxDepth > 0) {
		for(unsigned int i = 0; i < 4; i++) {
			if(children[i]->area.fits(box)) {
				children[i]->insert(ent);
				return;
			}
		}
	}
	
	// If we've made it this far, it's not fittable in the children.
	ent->quadTreeListPosition = nodeContents.size();
	ent->quadTreeNode = this;
	nodeContents.push_back(ent);
}

void QuadTree::remove(Entity *ent) {
	//assert(ent->quadTreeNode == this);
	if(ent->quadTreeNode != this) {
		if(ent->quadTreeNode) {
			ent->quadTreeNode->remove(ent);
		}
		return;
	}
	
	// Replace the entity's pointer in the list with the last one in the list.
	nodeContents[ent->quadTreeListPosition] = nodeContents[nodeContents.size() - 1];
	
	// Fix that last entity's idea of where it is in the list.
	nodeContents[ent->quadTreeListPosition]->quadTreeListPosition = ent->quadTreeListPosition;
	
	// Erase the end of the list (faster than shifting everything around).
	nodeContents.erase(nodeContents.end() - 1);
	
	// And it's out of the list.
	ent->quadTreeNode = NULL;
}

void QuadTree::reinsert(Entity *ent) {
	if(ent->quadTreeNode) {
		remove(ent);
		insert(ent);
	}
}

void QuadTree::getEntsTouchingBox(const BoundingBox &box, std::vector<Entity *> &ents) const {
	for(unsigned int i = 0; i < nodeContents.size(); i++) {
		if(box.overlap(nodeContents[i]->getBoundingBox())) {
			ents.push_back(nodeContents[i]);
		}
	}
	
	if(maxDepth > 0) {
		for(unsigned int i = 0; i < 4; i++) {
			if(children[i]->area.overlap(box)) {
				children[i]->getEntsTouchingBox(box, ents);
			}
		}
	}
}

//void QuadTree::debugDraw(void) const {
	//if(nodeContents.size()) {
		//glColor3f(0.5, 0.5, 0.5);
		//area.draw();
	//}	
	//if(maxDepth > 0) {
		//for(unsigned int i = 0; i < 4; i++) {
			//children[i]->debugDraw();
		//}
	//}
//}

void QuadTree::getAllEntities(std::vector<Entity *> &ents) const {
	ents.insert(ents.end(), nodeContents.begin(), nodeContents.end());
	if(maxDepth > 0) {
		for(unsigned int i = 0; i < 4; i++) {
			children[i]->getAllEntities(ents);
		}
	}
}

void QuadTree::resize(const BoundingBox &box, int maxDepth) {
	
	// Save all the old Entities.
	vector<Entity *> oldEnts;
	getAllEntities(oldEnts);
	
	// Destroy the old tree.
	if(this->maxDepth > 0) {
		for(unsigned int i = 0; i < 4; i++) {
			delete children[i];
		}
	}
	
	// The destructors handle cleaning up Entity QuadTree references
	// for the children we deleted, but not the ones for this node, so
	// let's do that.
	for(unsigned int i = 0; i < nodeContents.size(); i++) {
		nodeContents[i]->quadTreeNode = NULL;
		nodeContents[i]->quadTreeListPosition = 0;
	}
	nodeContents.clear();	
	
	
	// Create the new tree.
	this->maxDepth = maxDepth;
	area = box;
	
	if(maxDepth > 0) {
		float mx = (box.upperLeft.x + box.lowerRight.x) / 2.0;
		float my = (box.upperLeft.y + box.lowerRight.y) / 2.0;
		children[0] = new QuadTree(BoundingBox(box.upperLeft.x, box.upperLeft.y, mx,               my), maxDepth - 1); // top left
		children[1] = new QuadTree(BoundingBox(mx,              box.upperLeft.y, box.lowerRight.x, my), maxDepth - 1); // top right
		children[2] = new QuadTree(BoundingBox(box.upperLeft.x, my,              mx,               box.lowerRight.y), maxDepth - 1); // bottom left
		children[3] = new QuadTree(BoundingBox(mx,              my,              box.lowerRight.x, box.lowerRight.y), maxDepth - 1); // bottom right
	}
	
	// Reinsert all the Entities.
	for(unsigned int i = 0; i < oldEnts.size(); i++) {
		insert(oldEnts[i]);
	}
	
}

void QuadTree::resizeToFitAll(int maxDepth) {
	if(maxDepth == -1) maxDepth = this->maxDepth;
	
	vector<Entity*> allEnts;
	getAllEntities(allEnts);
	
	BoundingBox box(-16, -16, 16, 16);
	for(unsigned int i = 0; i < allEnts.size(); i++) {
		BoundingBox entBox = allEnts[i]->getBoundingBox();
		if(entBox.upperLeft.x  < box.upperLeft.x) box.upperLeft.x = entBox.upperLeft.x;
		if(entBox.upperLeft.y  > box.upperLeft.y) box.upperLeft.y = entBox.upperLeft.y;
		
		if(entBox.lowerRight.x > box.lowerRight.x) box.lowerRight.x = entBox.lowerRight.x;
		if(entBox.lowerRight.y < box.lowerRight.y) box.lowerRight.y = entBox.lowerRight.y;
	}
	
	// mout << "Resizing QuadTree from " << area << " to " << box << endl;
	resize(box, maxDepth);
}
