// -*-c++-*- osgWidget - Code by: Jeremy Moles (cubicool) 2007-2008
// $Id: WindowManager.cpp 66 2008-07-14 21:54:09Z cubicool $

#include <iostream>
#include <algorithm>
#include <osg/io_utils>
#include <osgWidget/Types>
#include <osgWidget/Util>
#include <osgWidget/WindowManager>
#include <osgWidget/Lua>
#include <osgWidget/Python>
#include <osgWidget/Box>
#include <osgWidget/Label>

namespace osgWidget {

WindowManager::WindowManager(
	osgViewer::View* view,
	point_type       width,
	point_type       height,
	unsigned int     nodeMask,
	unsigned int     flags
):
_width          (width),
_height         (height),
_zNear          (0.0f),
_zFar           (-1.0f),
_numForeground  (0.0f),
_numBackground  (0.0f),
_flags          (flags),
_nodeMask       (nodeMask),
_view           (view),
_lastX          (0.0f),
_lastY          (0.0f),
_lastEvent      (0),
_lastPush       (0),
_lastVertical   (PD_NONE),
_lastHorizontal (PD_NONE),
_focusMode      (PFM_FOCUS),
_leftDown       (false),
_middleDown     (false),
_rightDown      (false),
_scrolling      (osgGA::GUIEventAdapter::SCROLL_NONE),
_styleManager   (new StyleManager()) {
	_name = generateRandomName("WindowManager");

	if(_flags & WM_USE_LUA) {
		_lua = new LuaEngine(this);

		if(!_lua->initialize()) warn() << "Error creating LuaEngine." << std::endl;
	}

	if(_flags & WM_USE_PYTHON) {
		_python = new PythonEngine(this);

		if(!_python->initialize()) warn() << "Error creating PythonEngine." << std::endl;
	}

	// Setup our picking debug (is debug the right word here?) Window...
	if(_flags & WM_PICK_DEBUG) {
		_pickWindow = new Box("PickWindow", Box::VERTICAL);
		
		Label* label = new Label("PickLabel");

		label->setFontSize(13);
		label->setFontColor(1.0f, 1.0f, 1.0f, 1.0f);
		label->setFont("fonts/VeraMono.ttf");
		label->setPadding(5.0f);
		label->setCanFill(true);

		_pickWindow->getBackground()->setColor(0.0f, 0.0f, 0.0f, 0.85f);
		_pickWindow->addWidget(label);
		_pickWindow->setNodeMask(~_nodeMask);
		_pickWindow->removeEventMask(EVENT_MASK_FOCUS);
		_pickWindow->setStrata(Window::STRATA_FOREGROUND);

		addChild(_pickWindow.get());

		_updatePickWindow(0, 0, 0);
	}

	if(!(_flags & WM_NO_BETA_WARN)) {
		Box*   box   = new Box("BetaWarningBox", Box::VERTICAL);
		Label* label = new Label("BetaWarning");

		label->setFontSize(15);
		label->setFontColor(0.0f, 0.0f, 1.0f, 1.0f);
		label->setFont("fonts/arial.ttf");
		label->setPadding(5.0f);
		label->setCanFill(true);
		label->setLabel("This is BETA software! Please see: http://osgwidget.googlecode.com");

		box->getBackground()->setColor(1.0f, 0.7f, 0.0f, 1.0f);
		box->addWidget(label);
		box->setNodeMask(~_nodeMask);
		box->removeEventMask(EVENT_MASK_FOCUS);
		box->setStrata(Window::STRATA_BACKGROUND);
		box->setOrigin(0.0f, 0.0f);

		addChild(box);
		
		box->resizePercent(100.0f, 0.0f);
	}
}

WindowManager::WindowManager(const WindowManager& wm, const osg::CopyOp& co):
osg::Switch(wm, co) {
}

WindowManager::~WindowManager() {
	if(_flags & WM_USE_LUA) _lua->close();
	
	if(_flags & WM_USE_PYTHON) _python->close();
}

void WindowManager::setEventFromInterface(Event& ev, EventInterface* ei) {
	Widget* widget = dynamic_cast<Widget*>(ei);
	Window* window = dynamic_cast<Window*>(ei);

	if(widget) {
		ev._window = widget->getParent();
		ev._widget = widget;
	}

	else if(window) ev._window = window;
}

bool WindowManager::_handleMousePushed(float x, float y, bool& down) {
	down = true;

	Event ev(this, EVENT_MOUSE_PUSH);

	WidgetList widgetList;

	if(!pickAtXY(x, y, widgetList)) return false;

	ev.makeMouse(x, y);

	_lastPush = getFirstEventInterface(widgetList, ev);

	if(!_lastPush) return false;

	bool handled = _lastPush->callMethodAndCallbacks(ev);

	if(_focusMode != PFM_SLOPPY) {
		if(ev._window) {
			Window* topmostWindow = ev._window->getTopmostParent();

			setFocused(topmostWindow);
			
			if(ev._widget) topmostWindow->setFocused(ev._widget);
		}

		// If the user wants to be able to "unfocus" the last Window.
		else if(_focusMode == PFM_UNFOCUS) setFocused(0);
	}

	return handled;
}

bool WindowManager::_handleMouseReleased(float x, float y, bool& down) {
	down = false;

	// If were were in a drag state, reset our boolean flag.
	// if(_lastDrag) _lastDrag = 0;

	if(!_lastPush) return false;

	// By design, we can only release an EventInterface we previously pressed.
	// Whether or not we're ON the EventInterface when the release occurs isn't important.
	Event ev(this, EVENT_MOUSE_RELEASE);

	setEventFromInterface(ev, _lastPush);

	bool handled = _lastPush->callMethodAndCallbacks(ev);

	_lastPush = 0;

	return handled;
}

void WindowManager::_getPointerXYDiff(float& x, float& y) {
	x -= _lastX;

	if(isInvertedY()) y = -(y - _lastY);

	else y -= _lastY;
}

void WindowManager::_updatePickWindow(const WidgetList* wl, point_type x, point_type y) {
	Label* label = dynamic_cast<Label*>(_pickWindow->getByName("PickLabel"));

	if(!wl) {
		setValue(0, false);

		return;
	}

	setValue(0, true);

	std::stringstream ss;

	point_type xdiff = x;
	point_type ydiff = y;

	_getPointerXYDiff(xdiff, ydiff);

	ss
		<< "At XY Coords: " << x << ", " << _height - y
		<< " ( diff " << xdiff << ", " << ydiff << " )"
		<< std::endl
	;

	const Window* parent = wl->back()->getParent();

	ss
		<< "Window: " << parent->getName()
		<< " ( xyz " << parent->getPosition() << " )"
		<< " { zRange " << parent->getZRange() << " }"
		<< " < size " << parent->getSize() << " >"
		<< " EventMask: " << std::hex << parent->getEventMask()
		<< std::endl
	;

	for(WidgetList::const_iterator i = wl->begin(); i != wl->end(); i++) {
		Widget* widget = i->get();

		ss
			<< "   - " << widget->getName()
			<< " ( xyz " << widget->getPosition() << " )"
			<< " [ XYZ " << widget->getPosition() * parent->getMatrix()
			<< " ] < size " << widget->getSize() << " >"
			<< " EventMask: " << std::hex << widget->getEventMask()
			<< std::endl
		;
	}

	label->setLabel(ss.str());

	XYCoord size = label->getTextSize();

	_pickWindow->resize(size.x() + 10.0f, size.y() + 10.0f);
	_pickWindow->setOrigin(5.0f, _height - _pickWindow->getHeight() - 5.0f);
	_pickWindow->update();
}

void WindowManager::childInserted(unsigned int i) {
	Window* window = dynamic_cast<Window*>(getChild(i));

	if(!window) return;

	_objects.push_back(window);

	window->_index = i;

	setFocused(window);

	window->setNodeMask(_nodeMask);
	window->managed(this);

	for(Window::Iterator w = window->begin(); w != window->end(); w++) if(w->valid()) {
		_styleManager->applyStyles(w->get());
	}

	_styleManager->applyStyles(window);
}
	
void WindowManager::childRemoved(unsigned int start, unsigned int end) {
	while(start < end) {
		Window* window = getByIndex(start);

		if(!window) continue;

		if(_remove(window)) {
			window->_index = -1;

			window->unmanaged(this);
		}

		start++;
	}
}

// This method performs intersection testing at the given XY coords, and returns true if
// any intersections were found. It will break after processing the first pickable Window
// it finds.
bool WindowManager::pickAtXY(float x, float y, WidgetList& wl) {
	Intersections intr;

	if(_view->computeIntersections(x, y, intr, _nodeMask)) {
		// Get the first Window at the XY coordinates; if you want a Window to be
		// non-pickable, set the NodeMask to something else.
		Window* activeWin = 0;

		// Iterate over every picked result and create a list of Widgets that belong
		// to that Window.
		for(Intersections::iterator i = intr.begin(); i != intr.end(); i++) {
			Window* win = dynamic_cast<Window*>(i->nodePath.back()->getParent(0));

			// Make sure that our window is valid, and that our pick is within the
			// "visible area" of the Window.
			if(
				(!win || win->getVisibilityMode() == Window::VM_PARTIAL) &&
				!win->isPointerXYWithinVisible(x, y)
			) continue;

			// Set our activeWin, so that we know when we've got all the Widgets
			// that belong to it.
			if(!activeWin) activeWin = win;

			// If we've found a new Widnow, break out!
			else if(activeWin != win) break;

			Widget* widget = dynamic_cast<Widget*>(i->drawable.get());

			if(!widget) continue;

			// We need to return a list of every Widget that was picked, so
			// that the handler can operate on it accordingly.
			else wl.push_back(widget);
		}

		if(wl.size()) {
			// Potentially VERY expensive; only to be used for debugging. :)
			if(_flags & WM_PICK_DEBUG) _updatePickWindow(&wl, x, y);

			return true;
		}
	}

	if(_flags & WM_PICK_DEBUG) _updatePickWindow(0, x, y);
	
	return false;
}

bool WindowManager::setFocused(Window* window) {
	Event ev(this);

	ev._window = window;
	
	// Inform the previously focused Window that it is going to be unfocused.
	if(_focused.valid()) _focused->callMethodAndCallbacks(ev.makeType(EVENT_UNFOCUS));

	_focused = window;

	if(!window || !window->canFocus()) return false;

	// Build a vector of every Window that is focusable, in the foreground, and in the
	// background. All these Windows are handled differently.
	Vector focusable;
	Vector bg;
	Vector fg;

	for(ConstIterator it = begin(); it != end(); it++) if(it->valid()) {
		Window* w = it->get();

		if(w->getStrata() == Window::STRATA_FOREGROUND) fg.push_back(w);

		else if(w->getStrata() == Window::STRATA_BACKGROUND) bg.push_back(w);
		
		else focusable.push_back(w);
	}

	// After this call to sort, the internal objects will be arranged such that the
	// previously focused window is the first, followed by all other Windows in
	// descending order.
	std::sort(focusable.begin(), focusable.end(), WindowZCompare());

	// This is the depth range for each Window. Each Window object must be informed of
	// the Z space allocated to it so that it can properly arrange it's children. We
	// add 2 additional Windows here for anything that should appear in the background
	// and foreground areas.
	matrix_type zRange = (_zNear - _zFar) / (focusable.size() + 2.0f);

	// Our offset for the following for() loop.
	unsigned int i = 3;

	// Handle all of our focusable Windows.
	for(Iterator w = focusable.begin(); w != focusable.end(); w++) {
		Window* win = w->get();

		// Set our newly focused Window as the topmost element.
		if(*w == window) win->_z = -zRange * 2.0f;

		// Set the current Z of the remaining Windows and set their zRange so that
		// they can update their own children.
		else {
			win->_z = -zRange * i;

			i++;
		}
	}

	// Handled our special BACKGROUND Windows.
	for(Iterator w = bg.begin(); w != bg.end(); w++) w->get()->_z = -zRange * i;
	
	// Handle our special FOREGOUND Windows.
	for(Iterator w = fg.begin(); w != fg.end(); w++) w->get()->_z = -zRange;
	
	// Update every window, regardless.
	for(Iterator w = begin(); w != end(); w++) {
		Window* win = w->get();
		
		win->_zRange = zRange;

		win->update();
	}

	_focused->callMethodAndCallbacks(ev.makeType(EVENT_FOCUS));

	return true;
}

void WindowManager::setPointerXY(float x, float y) {
	float xdiff = x;
	float ydiff = y;

	_getPointerXYDiff(xdiff, ydiff);

	// If ydiff isn't NEAR 0 (floating point booleans aren't 100% reliable, but that
	// doesn't matter in our case), assume we have either up or down movement.
	if(ydiff != 0.0f) _lastVertical = ydiff > 0.0f ? PD_UP : PD_DOWN;
		
	else _lastVertical = PD_NONE;

	// If xdiff isn't 0, assume we have either left or right movement.
	if(xdiff != 0.0f) _lastHorizontal = xdiff > 0.0f ? PD_RIGHT : PD_LEFT;

	else _lastHorizontal = PD_NONE;

	_lastX = x;
	_lastY = y;
}

void WindowManager::setStyleManager(StyleManager* sm) {
	_styleManager = sm;

	for(Iterator i = begin(); i != end(); i++) if(i->valid()) {
		Window* window = i->get();

		for(Window::Iterator w = window->begin(); w != window->end(); w++) {
			if(!w->valid()) continue;

			_styleManager->applyStyles(w->get());
		}

		_styleManager->applyStyles(window);
	}
}

void WindowManager::resizeAllWindows(bool visible) {
	for(Iterator i = begin(); i != end(); i++) if(i->valid()) {
		if(visible && !getValue(i->get()->_index)) continue;
		
		i->get()->resize();
	}
}

// This is called by a ViewerEventHandler/MouseHandler (or whatever) as the pointer moves
// around and intersects with objects. It also resets our state data (_widget, _leftDown,
// etc.) The return value of this method is mostly useless.
bool WindowManager::pointerMove(float x, float y) {
	WidgetList wl;
	Event      ev(this);

	if(!pickAtXY(x, y, wl)) {
		if(_lastEvent) {
			setEventFromInterface(ev.makeMouse(x, y, EVENT_MOUSE_LEAVE), _lastEvent);

			_lastEvent->callMethodAndCallbacks(ev);
		}

		if(_focusMode == PFM_SLOPPY) setFocused(0);

		_lastEvent  = 0;
		_leftDown   = 0;
		_middleDown = 0;
		_rightDown  = 0;

		return false;
	}

	EventInterface* ei = getFirstEventInterface(wl, ev.makeMouse(x, y, EVENT_MOUSE_OVER));

	if(!ei) return false;

	if(_lastEvent != ei) {
		if(_lastEvent) {
			Event evLeave(this);

			evLeave.makeMouse(x, y, EVENT_MOUSE_LEAVE);
			
			setEventFromInterface(evLeave, _lastEvent);

			_lastEvent->callMethodAndCallbacks(evLeave);
		}

		_lastEvent = ei;

		if(_focusMode == PFM_SLOPPY && ev._window) setFocused(ev._window);

		_lastEvent->callMethodAndCallbacks(ev.makeMouse(x, y, EVENT_MOUSE_ENTER));
	}

	ei->callMethodAndCallbacks(ev.makeMouse(x, y, EVENT_MOUSE_OVER));

	return true;
}

bool WindowManager::pointerDrag(float x, float y) {
	WidgetList widgetList;
	Event      ev(this);

	float xdiff = x;
	float ydiff = y;

	_getPointerXYDiff(xdiff, ydiff);

	ev.makeMouse(xdiff, ydiff, EVENT_MOUSE_DRAG);

	// If we're still in the drag state...
	if(_lastPush) {
		setEventFromInterface(ev, _lastPush);

		return _lastPush->callMethodAndCallbacks(ev);
	}

	return false;
}

bool WindowManager::mouseScroll(float x, float y) {
	WidgetList wl;

	if(!pickAtXY(x, y, wl)) return false;

	Event ev(this, EVENT_MOUSE_SCROLL);

	EventInterface* ei = getFirstEventInterface(wl, ev);

	if(!ei) return false;

	return ei->callMethodAndCallbacks(ev);
}

// Keypresses only go the focused Window.
bool WindowManager::keyDown(int key, int mask) {
	if(_focused.valid()) {
		Event ev(this, EVENT_KEY_DOWN);

		ev.makeKey(key, mask);

		Widget* focusedWidget = _focused->getFocused();

		ev._window = _focused.get();
		ev._widget = focusedWidget;

		bool handled = false;

		if(focusedWidget) handled = focusedWidget->callMethodAndCallbacks(ev);

		if(!handled) return _focused->callMethodAndCallbacks(ev);

		else return true;
	}

	return false;
}

bool WindowManager::keyUp(int key, int mask) {
	return true;
}

// A convenience wrapper for creating a proper orthographic camera using the current
// width and height.
osg::Camera* WindowManager::createParentOrthoCamera() {
	osg::Camera* camera = 0;

	if(isInvertedY()) camera = createInvertedYOrthoCamera(_width, _height);

	else camera = createOrthoCamera(_width, _height);

	camera->addChild(this);

	return camera;
}

}
