// ---------------------------------------------------------------------------
// 
//   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 <iostream>
using namespace std;

#include <GL/gl.h>

#include "widgetcontainer.h"

#include "../3rdparty/tinyxml/tinyxml.h"
#include "../tinyxml/xmlstuff.h"

#include "../console/console.h"
using namespace Console;

WidgetContainer::WidgetContainer(void) {
	stretchableRow = 0;
	stretchableColumn = 0;
	rowSizes.push_back(256);
	colSizes.push_back(256);
	stretchableColumnWidth = 256;
	stretchableRowHeight = 256;
	cellSpacing = 0;
}

WidgetContainer::WidgetContainer(TiXmlElement *xml) : WidgetMultiParent(xml) {
	stretchableRow = -1;
	stretchableColumn = -1;
	stretchableColumnWidth = 256;
	stretchableRowHeight = 256;
	cellSpacing = 0;
	
	xml->QueryIntAttribute("stretchableRow", &stretchableRow);
	xml->QueryIntAttribute("stretchableColumn", &stretchableColumn);
	
	int numRows = 1;
	xml->QueryIntAttribute("rows", &numRows);
	setNumRows(numRows);
	
	int numCols = 1;
	xml->QueryIntAttribute("cols", &numCols);
	setNumCols(numCols);
	
	xml->QueryFloatAttribute("cellSpacing", &cellSpacing);

	// There needs to be at least one row and one column.
	if(rowSizes.size() < 1) {
		rowSizes.push_back(256);
	}
	if(colSizes.size() < 1) {
		colSizes.push_back(256);
	}

	// Do one iteration of the width/height adjustment to make
	// everything sane.
	recalcHeights();
	recalcWidths();
}

WidgetContainer::~WidgetContainer(void) {

}

void WidgetContainer::setWidth(float newWidth) {
	Widget::setWidth(newWidth);
	recalcWidths();
}

void WidgetContainer::setHeight(float newHeight) {
	Widget::setHeight(newHeight);
	recalcHeights();
}

void WidgetContainer::draw() {
	
	// We still do basic widget drawing (ninepatch, etc) too.
	Widget::draw();
	
	float offsetx = 0;
	float offsety = 0;
	int col = 0;
	int row = 0;
	
	// Iterate through the children.
	for(unsigned int i = 0; i < children.size(); i++) {
		
		// Figure out what row/col we're actually in and set the draw
		// offset accordingly.
		if(col >= (int)colSizes.size()) {
			offsety += (row == stretchableRow) ? stretchableRowHeight : rowSizes[row];
			offsety += cellSpacing;
			row++;
			col = 0;
			offsetx = 0;
		}
		
		// If we've gone past the highest row number, there's nothing
		// left to draw. Any other children are extra.
		if(row >= (int)rowSizes.size()) return;
		
		if(children[i]) {
			glPushMatrix();
			glTranslatef(offsetx, offsety, 0);
	
			// Push clipping for the column/row.
			pushClipArea(0, 0, 
				(col == stretchableColumn) ? stretchableColumnWidth : colSizes[col],
				(row == stretchableRow) ? stretchableRowHeight : rowSizes[row]);
			
			// Draw the child.
			children[i]->draw();
			
			popClipArea();
			
			glPopMatrix();
		}
		
		// Add the offset for the next Widget.
		offsetx += (col == stretchableColumn) ? stretchableColumnWidth : colSizes[col];
		offsetx += cellSpacing;
		col++;
	}
	
}

void WidgetContainer::addChild(Widget *child) {

	WidgetMultiParent::addChild(child);
	
	// Make everything sane again.
	recalcWidths();
	recalcHeights();
}

void WidgetContainer::removeChild(Widget *child) {
	
	WidgetMultiParent::removeChild(child);
	
	// Make everything sane.
	recalcWidths();
	recalcHeights();
}

void WidgetContainer::disconnectSpecificChild(Widget *child) {
	
	WidgetMultiParent::disconnectSpecificChild(child);
	
	recalcWidths();
	recalcHeights();
}

void WidgetContainer::setNumRows(int numRows) {
	if(numRows <= 0) {
		out("error") << "Tried to set a bad number of rows (" << numRows << ") on a WidgetContainer." << endl;
		return;
	}
	
	// Remove rows until we have the right amount.
	while(numRows < (int)rowSizes.size()) {
		rowSizes.erase(rowSizes.end() - 1);
	}
	
	// Add rows until we have the right amount.
	while(numRows > (int)rowSizes.size()) {
		rowSizes.push_back(32);
	}
	
	recalcWidths();
	recalcHeights();
}

void WidgetContainer::setNumCols(int numCols) {
	if(numCols <= 0) {
		out("error") << "Tried to set a bad number of columns (" << numCols << ") on a WidgetContainer." << endl;
		return;
	}
	
	// Remove columns until we have the right amount.
	while(numCols < (int)colSizes.size()) {
		colSizes.erase(colSizes.end() - 1);
	}
	
	// Add columns until we have the right amount.
	while(numCols > (int)colSizes.size()) {
		colSizes.push_back(32);
	}
	
	recalcWidths();
	recalcHeights();
}

int WidgetContainer::getNumRows(void) {
	return (int)rowSizes.size();
}

int WidgetContainer::getNumCols(void) {
	return (int)colSizes.size();
}

int WidgetContainer::getStretchableRow(void) {
	return stretchableRow;
}

int WidgetContainer::getStretchableCol(void) {
	return stretchableColumn;
}

void WidgetContainer::setCellSpacing(float spacing) {
	if(spacing < 0) spacing = 0;
	cellSpacing = spacing;
}

float WidgetContainer::getCellSpacing(void) {
	return cellSpacing;
}

void WidgetContainer::recalcWidths(void) {

	// First size everything that's NOT in a stretchable column.
	for(int x = 0; x < (int)colSizes.size(); x++) {
		if(x != stretchableColumn) {
			
			// Make the column size equal to the size of the widest
			// thing in the column.
			colSizes[x] = getBiggestThingInCol(x);
			
			// Stretch everything else up to that size.
			for(unsigned int y = 0; y < rowSizes.size(); y++) {
				Widget *w = getSlot(x, y);
				if(w) w->setWidth(colSizes[x]);
			}
		}
	}
	
	if(stretchableColumn >= 0) {
		
		// Figure out the size of our stretchable thing by taking our
		// total width and subtracting all the padding and the sizes of
		// the other columns.
		float resizableSize = width;
		for(int i = 0; i < (int)colSizes.size(); i++) {
			if((int)i != stretchableColumn) {
				resizableSize -= colSizes[i] + cellSpacing;
			}
		}
		
		// Hmmmmm.....
		//colSizes[stretchableColumn] = resizableSize;
		
		stretchableColumnWidth = resizableSize;
		for(int i = 0; i < (int)rowSizes.size(); i++) {
			Widget *w = getSlot(stretchableColumn, i);
			if(w) w->setWidth(stretchableColumnWidth);
		}
	}
}

void WidgetContainer::recalcHeights(void) {
	
	// First size everything that's NOT in a stretchable row.
	for(int y = 0; y < (int)rowSizes.size(); y++) {
		if(y != stretchableRow) {
			
			// Make the row size equal to the size of the tallest
			// thing in the row.
			rowSizes[y] = getBiggestThingInRow(y);
			
			// Stretch everything else up to that size.
			for(int x = 0; x < (int)colSizes.size(); x++) {
				Widget *w = getSlot(x, y);
				if(w) w->setHeight(rowSizes[y]);
			}
		}
	}

	if(stretchableRow >= 0) {
		
		// Figure out the size of our stretchable thing by taking our
		// total height and subtracting all the padding and the sizes of
		// the other rows.
		float resizableSize = height;
		for(int i = 0; i < (int)rowSizes.size(); i++) {
			if(i != stretchableRow) {
				resizableSize -= rowSizes[i] + cellSpacing;
			}
		}
		
		// Hmmmmm.....
		//colSizes[stretchableColumn] = resizableSize;
		
		stretchableRowHeight = resizableSize;
		for(int i = 0; i < (int)colSizes.size(); i++) {
			Widget *w = getSlot(i, stretchableRow);
			if(w) w->setHeight(stretchableRowHeight);
		}
	}
}

void WidgetContainer::setStretchableRow(int rowNum) {
	stretchableRow = rowNum;
	recalcHeights();
}

void WidgetContainer::setStretchableCol(int colNum) {
	stretchableColumn = colNum;
	recalcWidths();
}

float WidgetContainer::getMinHeight(void) {
	float totalMinHeight = 0;
	
	// Add cellspacing sizes.
	totalMinHeight += cellSpacing * (rowSizes.size() - 1);
	
	// Find the biggest (minHeight) thing in any row.
	for(unsigned int y = 0; y < rowSizes.size(); y++) {
		float biggestMinHeight = getBiggestThingInRow(y);
		totalMinHeight += biggestMinHeight;
	}
	
	// Return the bigger out of the calculated min height and the set
	// min height.
	return totalMinHeight > minHeight ? totalMinHeight : minHeight;
}

float WidgetContainer::getMinWidth(void) {
	float totalMinWidth = 0;
	
	// Add cellspacing sizes.
	totalMinWidth += cellSpacing * (colSizes.size() - 1);
	
	// Find the biggest (minWidth) thing in any column.
	for(unsigned int x = 0; x < colSizes.size(); x++) {
		float biggestMinWidth = getBiggestThingInCol(x);
		totalMinWidth += biggestMinWidth;
	}
	
	// Return the bigger out of the calculated min width and the set
	// min width.
	return totalMinWidth > minWidth ? totalMinWidth : minWidth;
}

Widget *WidgetContainer::getSlot(int x, int y) {
	int index = x + y * colSizes.size();
	if(!(index >= 0 && index <= (int)colSizes.size() * (int)rowSizes.size() && index < (int)children.size())) {
		// TODO: Assert?
		return NULL;
	}
	return children[index];
}

void WidgetContainer::setSlot(Widget *w, int x, int y) {
	if(!w) return;
	
	if(widgetIsEventuallyMyParent(w)) {
		out("error") <<
			"Tried to add a child (" <<
			w->getName() <<
			") which is an eventual parent to this WidgetContainer (" <<
			getName() <<
			")." << endl;
			
		return;
	}
	
	if(w->parent || w == widgetRoot) {
		out("error") << "Trying to set a Widget that already has the parent or is the root Widget to a slot in a container." << endl;
		return;
	}
	
	int index = x + y * colSizes.size();
	
	// Fill up space until we have a list long enough to hold this.
	while((int)children.size() < index + 1) {
		children.push_back(NULL);
	}
	
	// Kick out whatever was there.
	if(children[index]) {
		children[index]->parent = NULL;
		autoGenLuaWrapper_Widget_safeDelete(children[index]);
	}
	
	w->parent = this;
	children[index] = w;
}

bool WidgetContainer::handleEvent(const WidgetEvent *event) {

	// So we know we can bail out of the nested loops.
	bool childAteEvent = false;

	bool mouseWasOverAChild = false;
	
	float colOffset = 0;
	float rowOffset = 0;
	
	// Handle focus stuff in WidgetMultiParent.
	if(WidgetMultiParent::handleEvent(event)) {
		return true;
	}
	
	bool isMouseEvent = (
		event->type == WIDGETEVENT_MOUSEMOTION ||
		event->type == WIDGETEVENT_MOUSEDOWN ||
		event->type == WIDGETEVENT_MOUSEUP);
	
	bool isKeyboardEvent = (
		event->type == WIDGETEVENT_KEYREPEAT ||
		event->type == WIDGETEVENT_KEYDOWN ||
		event->type == WIDGETEVENT_KEYUP);
	
	// Mouse events need to only go to the child that the mouse is over
	// or a widget claiming to need mouse events.
	if(isMouseEvent) {
		
		for(int y = 0; y < (int)rowSizes.size(); y++) {
			for(int x = 0; x < (int)colSizes.size(); x++) {
				
				WidgetEvent shiftedEvent = *event;
				bool skipThisChild = false;
				
				// Determine if a mouse event gets skipped for this slot.
				if(isMouseEvent) {
					
					shiftedEvent.mouse.x -= colOffset;
					shiftedEvent.mouse.y -= rowOffset;
					
					if(	shiftedEvent.mouse.x < 0 || 
						shiftedEvent.mouse.y < 0 || 
						shiftedEvent.mouse.x > (x == stretchableColumn ? stretchableColumnWidth : colSizes[x]) ||
						shiftedEvent.mouse.y > (y == stretchableRow    ? stretchableRowHeight   : rowSizes[y]) || !mouseWasOverMe) {
						
						// It's a mouse event but the cursor is outside this
						// widget.
						
						// Exceptions will apply here. Like dragging which
						// must continue on a widget even after the mouse
						// has left it. These are handled by
						// needsMouseEvents().
						
						skipThisChild = true;
					}
				}
				
				Widget *childWidget = getSlot(x, y);
				if(childWidget) {
					if(!skipThisChild || childWidget->needsMouseEvents()) {
						
						// First, see if this means changing focus to
						// the child.
						Widget *focusedChildWidget = getFocusedChild();
						if(childWidget != focusedChildWidget && event->type == WIDGETEVENT_MOUSEDOWN && event->mouse.button == 0) {
							
							// It does. Send an event to both the old
							// focused one and the new focused one.
							
							WidgetEvent focusEvent;
							memset(&focusEvent, 0, sizeof(focusEvent));
							
							// Leaving old.
							focusEvent.type = WIDGETEVENT_FOCUSLEAVE;
							if(focusedChildWidget) focusedChildWidget->handleEvent(&focusEvent);
							
							// Entering new.
							focusEvent.type = WIDGETEVENT_FOCUSENTER;
							childWidget->handleEvent(&focusEvent);
							
							// Set new focus.
							int newSlot = getSlotIndex(x, y);
							focusedChild = newSlot;
						}
						
						// Send the event.	
						debugDrawOffsetX += colOffset;
						debugDrawOffsetY += rowOffset;
						childAteEvent |= childWidget->handleEvent(&shiftedEvent);
						debugDrawOffsetX -= colOffset;
						debugDrawOffsetY -= rowOffset;
						
						// If a child still thinks the mouse is over it, we
						// have to remember this for the next frame so it
						// still gets messages.
						
						// (It's okay to do this for non-mouse events.)
						
						mouseWasOverAChild |= childWidget->mouseWasOverMe;
					}
				}
				
				// Child ate the event? We're done.
				if(childAteEvent) break;
				
				if(x == stretchableColumn) {
					colOffset += stretchableColumnWidth;
				} else {
					colOffset += colSizes[x];
				}
				colOffset += cellSpacing;
			}
			
			// Child ate the event? We're done.
			if(childAteEvent) break;
			
			colOffset = 0;
			if(y == stretchableRow) {
				rowOffset += stretchableRowHeight;
			} else {
				rowOffset += rowSizes[y];
			}
			rowOffset += cellSpacing;
		}
		
		mouseWasOverMe |= mouseWasOverAChild;
	}
	
	// Keyboard events need to only go to the focused Widget.
	// FIXME: If a KEYDOWN event made it to a Widget, but focus was
	//   removed before it got the KEYUP event, it will have the
	//   belief that the key is down until it gets focus again and a
	//   KEYUP is received.
	if(isKeyboardEvent) {
		
		// Only send keyboard events to the focused child.
		Widget *w = getFocusedChild();
		if(w) childAteEvent = w->handleEvent(event);
	}
	
	return childAteEvent;
}

bool WidgetContainer::needsMouseEvents(void) {
	
	return WidgetMultiParent::needsMouseEvents();
}

float WidgetContainer::getBiggestThingInRow(int row) {
	float biggest = 0;
	for(unsigned int i = 0; i < colSizes.size(); i++) {
		Widget *thing = getSlot(i, row);
		if(thing) {
			float thingSize = thing->getMinHeight();
			if(thingSize > biggest) biggest = thingSize;
		}
	}
	return biggest;
}

float WidgetContainer::getBiggestThingInCol(int col) {
	float biggest = 0;
	for(unsigned int i = 0; i < rowSizes.size(); i++) {
		Widget *thing = getSlot(col, i);
		if(thing) {
			float thingSize = thing->getMinWidth();
			if(thingSize > biggest) biggest = thingSize;
		}
	}
	return biggest;
}

bool WidgetContainer::isSizeDirty(void) {
	
	if(WidgetMultiParent::isSizeDirty()) return true;
	
	// Check all the children. If any of them are dirty, this one is
	// treated as being dirty too.
	for(unsigned int i = 0; i < children.size(); i++) {
		if(children[i]) {
			if(children[i]->isSizeDirty()) {
				return true;
			}
		}
	}
	
	return false;
}

int WidgetContainer::getSlotIndex(int x, int y) {
	
	int slot = x + y * colSizes.size();
	
	// Could be out of bounds. But we might also use this to get an
	// idea of what the bounds should be expanded to to accommodate
	// that slot.
	
	return slot;
}

