
// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#include "CGUIEnvironment.h"

#ifdef _IRR_COMPILE_WITH_GUI_

#include "IVideoDriver.h"

#include "CGUISkin.h"
#include "CGUIButton.h"
#include "CGUIWindow.h"
#include "CGUIScrollBar.h"
#include "CGUIFont.h"
#include "CGUISpriteBank.h"
#include "CGUIImage.h"
#include "CGUIMeshViewer.h"
#include "CGUICheckBox.h"
#include "CGUIListBox.h"
#include "CGUITreeView.h"
#include "CGUIImageList.h"
#include "CGUIFileOpenDialog.h"
#include "CGUIColorSelectDialog.h"
#include "CGUIStaticText.h"
#include "CGUIEditBox.h"
#include "CGUISpinBox.h"
#include "CGUIInOutFader.h"
#include "CGUIMessageBox.h"
#include "CGUIModalScreen.h"
#include "CGUITabControl.h"
#include "CGUIContextMenu.h"
#include "CGUIComboBox.h"
#include "CGUIMenu.h"
#include "CGUIToolBar.h"
#include "CGUITable.h"
#include "CGUIProfiler.h"

#include "CDefaultGUIElementFactory.h"
#include "IWriteFile.h"
#include "IXMLWriter.h"

#include "BuiltInFont.h"
#include "os.h"

namespace irr
{
namespace gui
{

const wchar_t IRR_XML_FORMAT_GUI_ENV[]			= L"irr_gui";
const wchar_t IRR_XML_FORMAT_GUI_ELEMENT[]		= L"element";
const wchar_t IRR_XML_FORMAT_GUI_ELEMENT_ATTR_TYPE[]	= L"type";

const io::path CGUIEnvironment::DefaultFontName = "#DefaultFont";

//! constructor
CGUIEnvironment::CGUIEnvironment(io::IFileSystem* fs, video::IVideoDriver* driver, IOSOperator* op)
: IGUIElement(EGUIET_ROOT, 0, 0, 0, core::rect<s32>(driver ? core::dimension2d<s32>(driver->getScreenSize()) : core::dimension2d<s32>(0,0))),
	Driver(driver), Hovered(0), HoveredNoSubelement(0), Focus(0), LastHoveredMousePos(0,0), CurrentSkin(0),
	FileSystem(fs), UserReceiver(0), Operator(op), FocusFlags(EFF_SET_ON_LMOUSE_DOWN|EFF_SET_ON_TAB)
{
	if (Driver)
		Driver->grab();

	if (FileSystem)
		FileSystem->grab();

	if (Operator)
		Operator->grab();

	#ifdef _DEBUG
	IGUIEnvironment::setDebugName("CGUIEnvironment");
	#endif

	// gui factory
	IGUIElementFactory* factory = new CDefaultGUIElementFactory(this);
	registerGUIElementFactory(factory);
	factory->drop();

	loadBuiltInFont();

	IGUISkin* skin = createSkin( gui::EGST_WINDOWS_METALLIC );
	setSkin(skin);
	skin->drop();

	//set tooltip default
	ToolTip.LastTime = 0;
	ToolTip.EnterTime = 0;
	ToolTip.LaunchTime = 1000;
	ToolTip.RelaunchTime = 500;
	ToolTip.Element = 0;

	// environment is root tab group
	Environment = this;
	setTabGroup(true);
}


//! destructor
CGUIEnvironment::~CGUIEnvironment()
{
	if ( HoveredNoSubelement && HoveredNoSubelement != this )
	{
		HoveredNoSubelement->drop();
		HoveredNoSubelement = 0;
	}

	if (Hovered && Hovered != this)
	{
		Hovered->drop();
		Hovered = 0;
	}

	if (Focus)
	{
		Focus->drop();
		Focus = 0;
	}

	if (ToolTip.Element)
	{
		ToolTip.Element->drop();
		ToolTip.Element = 0;
	}

	// drop skin
	if (CurrentSkin)
	{
		CurrentSkin->drop();
		CurrentSkin = 0;
	}

	u32 i;

	// delete all sprite banks
	for (i=0; i<Banks.size(); ++i)
		if (Banks[i].Bank)
			Banks[i].Bank->drop();

	// delete all fonts
	for (i=0; i<Fonts.size(); ++i)
		Fonts[i].Font->drop();

	// remove all factories
	for (i=0; i<GUIElementFactoryList.size(); ++i)
		GUIElementFactoryList[i]->drop();

	if (Operator)
	{
		Operator->drop();
		Operator = 0;
	}

	if (FileSystem)
	{
		FileSystem->drop();
		FileSystem = 0;
	}

	if (Driver)
	{
		Driver->drop();
		Driver = 0;
	}
}


void CGUIEnvironment::loadBuiltInFont()
{
	io::IReadFile* file = FileSystem->createMemoryReadFile(BuiltInFontData,
				BuiltInFontDataSize, DefaultFontName, false);

	CGUIFont* font = new CGUIFont(this, DefaultFontName );
	if (!font->load(file))
	{
		os::Printer::log("Error: Could not load built-in Font. Did you compile without the BMP loader?", ELL_ERROR);
		font->drop();
		file->drop();
		return;
	}

	SFont f;
	f.NamedPath.setPath(DefaultFontName);
	f.Font = font;
	Fonts.push_back(f);

	file->drop();
}


//! draws all gui elements
void CGUIEnvironment::drawAll()
{
	if (Driver)
	{
		core::dimension2d<s32> dim(Driver->getScreenSize());
		if (AbsoluteRect.LowerRightCorner.X != dim.Width ||
			AbsoluteRect.LowerRightCorner.Y != dim.Height)
		{
			// resize gui environment
			DesiredRect.LowerRightCorner = dim;
			AbsoluteClippingRect = DesiredRect;
			AbsoluteRect = DesiredRect;
			updateAbsolutePosition();
		}
	}

	// make sure tooltip is always on top
	if (ToolTip.Element)
		bringToFront(ToolTip.Element);

	draw();
	OnPostRender ( os::Timer::getTime () );
}


//! sets the focus to an element
bool CGUIEnvironment::setFocus(IGUIElement* element)
{
	if (Focus == element)
	{
		return false;
	}

	// GUI Environment should just reset the focus to 0
	if (element == this)
		element = 0;

	// stop element from being deleted
	if (element)
		element->grab();

	// focus may change or be removed in this call
	IGUIElement *currentFocus = 0;
	if (Focus)
	{
		currentFocus = Focus;
		currentFocus->grab();
		SEvent e;
		e.EventType = EET_GUI_EVENT;
		e.GUIEvent.Caller = Focus;
		e.GUIEvent.Element = element;
		e.GUIEvent.EventType = EGET_ELEMENT_FOCUS_LOST;
		if (Focus->OnEvent(e))
		{
			if (element)
				element->drop();
			currentFocus->drop();
			return false;
		}
		currentFocus->drop();
		currentFocus = 0;
	}

	if (element)
	{
		currentFocus = Focus;
		if (currentFocus)
			currentFocus->grab();

		// send focused event
		SEvent e;
		e.EventType = EET_GUI_EVENT;
		e.GUIEvent.Caller = element;
		e.GUIEvent.Element = Focus;
		e.GUIEvent.EventType = EGET_ELEMENT_FOCUSED;
		if (element->OnEvent(e))
		{
			if (element)
				element->drop();
			if (currentFocus)
				currentFocus->drop();
			return false;
		}
	}

	if (currentFocus)
		currentFocus->drop();

	if (Focus)
		Focus->drop();

	// element is the new focus so it doesn't have to be dropped
	Focus = element;

	return true;
}


//! returns the element with the focus
IGUIElement* CGUIEnvironment::getFocus() const
{
	return Focus;
}

//! returns the element last known to be under the mouse cursor
IGUIElement* CGUIEnvironment::getHovered() const
{
	return Hovered;
}


//! removes the focus from an element
bool CGUIEnvironment::removeFocus(IGUIElement* element)
{
	if (Focus && Focus==element)
	{
		SEvent e;
		e.EventType = EET_GUI_EVENT;
		e.GUIEvent.Caller = Focus;
		e.GUIEvent.Element = 0;
		e.GUIEvent.EventType = EGET_ELEMENT_FOCUS_LOST;
		if (Focus->OnEvent(e))
		{
			return false;
		}
	}
	if (Focus)
	{
		Focus->drop();
		Focus = 0;
	}

	return true;
}


//! Returns whether the element has focus
bool CGUIEnvironment::hasFocus(const IGUIElement* element, bool checkSubElements) const
{
	if (element == Focus)
		return true;

	if ( !checkSubElements || !element )
		return false;

	IGUIElement* f = Focus;
	while ( f && f->isSubElement() )
	{
		f = f->getParent();
		if ( f == element )
			return true;
	}
	return false;
}


//! returns the current video driver
video::IVideoDriver* CGUIEnvironment::getVideoDriver() const
{
	return Driver;
}


//! returns the current file system
io::IFileSystem* CGUIEnvironment::getFileSystem() const
{
	return FileSystem;
}


//! returns a pointer to the OS operator
IOSOperator* CGUIEnvironment::getOSOperator() const
{
	return Operator;
}


//! clear all GUI elements
void CGUIEnvironment::clear()
{
	// Remove the focus
	if (Focus)
	{
		Focus->drop();
		Focus = 0;
	}

	if (Hovered && Hovered != this)
	{
		Hovered->drop();
		Hovered = 0;
	}
	if ( HoveredNoSubelement && HoveredNoSubelement != this)
	{
		HoveredNoSubelement->drop();
		HoveredNoSubelement = 0;
	}

	// get the root's children in case the root changes in future
	const core::list<IGUIElement*>& children = getRootGUIElement()->getChildren();

	while (!children.empty())
		(*children.getLast())->remove();
}


//! called by ui if an event happened.
bool CGUIEnvironment::OnEvent(const SEvent& event)
{
	bool ret = false;
	if (UserReceiver
		&& (event.EventType != EET_MOUSE_INPUT_EVENT)
		&& (event.EventType != EET_KEY_INPUT_EVENT)
		&& (event.EventType != EET_GUI_EVENT || event.GUIEvent.Caller != this))
	{
		ret = UserReceiver->OnEvent(event);
	}

	return ret;
}

//
void CGUIEnvironment::OnPostRender( u32 time )
{
	// launch tooltip
	if ( ToolTip.Element == 0 &&
		HoveredNoSubelement && HoveredNoSubelement != this &&
		(time - ToolTip.EnterTime >= ToolTip.LaunchTime
		|| (time - ToolTip.LastTime >= ToolTip.RelaunchTime && time - ToolTip.LastTime < ToolTip.LaunchTime)) &&
		HoveredNoSubelement->getToolTipText().size() &&
		getSkin() &&
		getSkin()->getFont(EGDF_TOOLTIP)
		)
	{
		core::rect<s32> pos;

		pos.UpperLeftCorner = LastHoveredMousePos;
		core::dimension2du dim = getSkin()->getFont(EGDF_TOOLTIP)->getDimension(HoveredNoSubelement->getToolTipText().c_str());
		dim.Width += getSkin()->getSize(EGDS_TEXT_DISTANCE_X)*2;
		dim.Height += getSkin()->getSize(EGDS_TEXT_DISTANCE_Y)*2;

		pos.UpperLeftCorner.Y -= dim.Height+1;
		pos.LowerRightCorner.Y = pos.UpperLeftCorner.Y + dim.Height-1;
		pos.LowerRightCorner.X = pos.UpperLeftCorner.X + dim.Width;

		pos.constrainTo(getAbsolutePosition());

		ToolTip.Element = addStaticText(HoveredNoSubelement->getToolTipText().c_str(), pos, true, true, this, -1, true);
		ToolTip.Element->setOverrideColor(getSkin()->getColor(EGDC_TOOLTIP));
		ToolTip.Element->setBackgroundColor(getSkin()->getColor(EGDC_TOOLTIP_BACKGROUND));
		ToolTip.Element->setOverrideFont(getSkin()->getFont(EGDF_TOOLTIP));
		ToolTip.Element->setSubElement(true);
		ToolTip.Element->grab();

		s32 textHeight = ToolTip.Element->getTextHeight();
		pos = ToolTip.Element->getRelativePosition();
		pos.LowerRightCorner.Y = pos.UpperLeftCorner.Y + textHeight;
		ToolTip.Element->setRelativePosition(pos);
	}

	if (ToolTip.Element && ToolTip.Element->isVisible() )	// (isVisible() check only because we might use visibility for ToolTip one day)
	{
		ToolTip.LastTime = time;

		// got invisible or removed in the meantime?
		if ( !HoveredNoSubelement ||
			!HoveredNoSubelement->isVisible() ||
			!HoveredNoSubelement->getParent()
			)	// got invisible or removed in the meantime?
		{
			ToolTip.Element->remove();
			ToolTip.Element->drop();
			ToolTip.Element = 0;
		}
	}

	IGUIElement::OnPostRender ( time );
}


//
void CGUIEnvironment::updateHoveredElement(core::position2d<s32> mousePos)
{
	IGUIElement* lastHovered = Hovered;
	IGUIElement* lastHoveredNoSubelement = HoveredNoSubelement;
	LastHoveredMousePos = mousePos;

	Hovered = getElementFromPoint(mousePos);

	if ( ToolTip.Element && Hovered == ToolTip.Element )
	{
		// When the mouse is over the ToolTip we remove that so it will be re-created at a new position.
		// Note that ToolTip.EnterTime does not get changed here, so it will be re-created at once.
		ToolTip.Element->remove();
		ToolTip.Element->drop();
		ToolTip.Element = 0;

		// Get the real Hovered
		Hovered = getElementFromPoint(mousePos);
	}

	// for tooltips we want the element itself and not some of it's subelements
	HoveredNoSubelement = Hovered;
	while ( HoveredNoSubelement && HoveredNoSubelement->isSubElement() )
	{
		HoveredNoSubelement = HoveredNoSubelement->getParent();
	}

	if (Hovered && Hovered != this)
		Hovered->grab();
	if ( HoveredNoSubelement && HoveredNoSubelement != this)
		HoveredNoSubelement->grab();

	if (Hovered != lastHovered)
	{
		SEvent event;
		event.EventType = EET_GUI_EVENT;

		if (lastHovered)
		{
			event.GUIEvent.Caller = lastHovered;
			event.GUIEvent.Element = 0;
			event.GUIEvent.EventType = EGET_ELEMENT_LEFT;
			lastHovered->OnEvent(event);
		}

		if ( Hovered )
		{
			event.GUIEvent.Caller  = Hovered;
			event.GUIEvent.Element = Hovered;
			event.GUIEvent.EventType = EGET_ELEMENT_HOVERED;
			Hovered->OnEvent(event);
		}
	}

	if ( lastHoveredNoSubelement != HoveredNoSubelement )
	{
		if (ToolTip.Element)
		{
			ToolTip.Element->remove();
			ToolTip.Element->drop();
			ToolTip.Element = 0;
		}

		if ( HoveredNoSubelement )
		{
			u32 now = os::Timer::getTime();
			ToolTip.EnterTime = now;
		}
	}

	if (lastHovered && lastHovered != this)
		lastHovered->drop();
	if (lastHoveredNoSubelement && lastHoveredNoSubelement != this)
		lastHoveredNoSubelement->drop();
}


//! This sets a new event receiver for gui events. Usually you do not have to
//! use this method, it is used by the internal engine.
void CGUIEnvironment::setUserEventReceiver(IEventReceiver* evr)
{
	UserReceiver = evr;
}


//! posts an input event to the environment
bool CGUIEnvironment::postEventFromUser(const SEvent& event)
{
	switch(event.EventType)
	{
	case EET_GUI_EVENT:
		// hey, why is the user sending gui events..?
		break;
	case EET_MOUSE_INPUT_EVENT:

		updateHoveredElement(core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y));

		if ( Hovered != Focus )
		{
			IGUIElement * focusCandidate = Hovered;

			// Only allow enabled elements to be focused (unless EFF_CAN_FOCUS_DISABLED is set)
			if ( Hovered && !Hovered->isEnabled() && !(FocusFlags & EFF_CAN_FOCUS_DISABLED))
				focusCandidate = NULL;	// we still remove focus from the active element

			// Please don't merge this into a single if clause, it's easier to debug the way it is
			if (FocusFlags & EFF_SET_ON_LMOUSE_DOWN &&
				event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN )
			{
				setFocus(focusCandidate);
			}
			else if ( FocusFlags & EFF_SET_ON_RMOUSE_DOWN &&
				event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN )
			{
				setFocus(focusCandidate);
			}
			else if ( FocusFlags & EFF_SET_ON_MOUSE_OVER &&
				event.MouseInput.Event == EMIE_MOUSE_MOVED )
			{
				setFocus(focusCandidate);
			}
		}

		// sending input to focus
		if (Focus && Focus->OnEvent(event))
			return true;

		// focus could have died in last call
		if (!Focus && Hovered)
		{
			return Hovered->OnEvent(event);
		}

		break;
	case EET_KEY_INPUT_EVENT:
		{
			if (Focus && Focus->OnEvent(event))
				return true;

			// For keys we handle the event before changing focus to give elements the chance for catching the TAB
			// Send focus changing event
			if (FocusFlags & EFF_SET_ON_TAB &&
				event.EventType == EET_KEY_INPUT_EVENT &&
				event.KeyInput.PressedDown &&
				event.KeyInput.Key == KEY_TAB)
			{
				IGUIElement *next = getNextElement(event.KeyInput.Shift, event.KeyInput.Control);
				if (next && next != Focus)
				{
					if (setFocus(next))
						return true;
				}
			}
		}
		break;
	default:
		break;
	} // end switch

	return false;
}


//! returns the current gui skin
IGUISkin* CGUIEnvironment::getSkin() const
{
	return CurrentSkin;
}


//! Sets a new GUI Skin
void CGUIEnvironment::setSkin(IGUISkin* skin)
{
	if (CurrentSkin==skin)
		return;

	if (CurrentSkin)
		CurrentSkin->drop();

	CurrentSkin = skin;

	if (CurrentSkin)
		CurrentSkin->grab();
}


//! Creates a new GUI Skin based on a template.
/** \return Returns a pointer to the created skin.
If you no longer need the skin, you should call IGUISkin::drop().
See IReferenceCounted::drop() for more information. */
IGUISkin* CGUIEnvironment::createSkin(EGUI_SKIN_TYPE type)
{
	IGUISkin* skin = new CGUISkin(type, Driver);

	IGUIFont* builtinfont = getBuiltInFont();
	IGUIFontBitmap* bitfont = 0;
	if (builtinfont && builtinfont->getType() == EGFT_BITMAP)
		bitfont = (IGUIFontBitmap*)builtinfont;

	IGUISpriteBank* bank = 0;
	skin->setFont(builtinfont);

	if (bitfont)
		bank = bitfont->getSpriteBank();

	skin->setSpriteBank(bank);

	return skin;
}


//! Returns the default element factory which can create all built in elements
IGUIElementFactory* CGUIEnvironment::getDefaultGUIElementFactory() const
{
	return getGUIElementFactory(0);
}


//! Adds an element factory to the gui environment.
/** Use this to extend the gui environment with new element types which it should be
able to create automaticly, for example when loading data from xml files. */
void CGUIEnvironment::registerGUIElementFactory(IGUIElementFactory* factoryToAdd)
{
	if (factoryToAdd)
	{
		factoryToAdd->grab();
		GUIElementFactoryList.push_back(factoryToAdd);
	}
}


//! Returns amount of registered scene node factories.
u32 CGUIEnvironment::getRegisteredGUIElementFactoryCount() const
{
	return GUIElementFactoryList.size();
}


//! Returns a scene node factory by index
IGUIElementFactory* CGUIEnvironment::getGUIElementFactory(u32 index) const
{
	if (index < GUIElementFactoryList.size())
		return GUIElementFactoryList[index];
	else
		return 0;
}


//! adds a GUI Element using its name
IGUIElement* CGUIEnvironment::addGUIElement(const c8* elementName, IGUIElement* parent)
{
	IGUIElement* node=0;

	if (!parent)
		parent = this;

	for (s32 i=GUIElementFactoryList.size()-1; i>=0 && !node; --i)
		node = GUIElementFactoryList[i]->addGUIElement(elementName, parent);


	return node;
}


//! Saves the current gui into a file.
//! \param filename: Name of the file .
bool CGUIEnvironment::saveGUI(const io::path& filename, IGUIElement* start)
{
	io::IWriteFile* file = FileSystem->createAndWriteFile(filename);
	if (!file)
	{
		return false;
	}

	bool ret = saveGUI(file, start);
	file->drop();
	return ret;
}


//! Saves the current gui into a file.
bool CGUIEnvironment::saveGUI(io::IWriteFile* file, IGUIElement* start)
{
	if (!file)
	{
		return false;
	}

	io::IXMLWriter* writer = FileSystem->createXMLWriter(file);
	if (!writer)
	{
		return false;
	}

	writer->writeXMLHeader();
	writeGUIElement(writer, start ? start : this);
	writer->drop();

	return true;
}


//! Loads the gui. Note that the current gui is not cleared before.
//! \param filename: Name of the file.
bool CGUIEnvironment::loadGUI(const io::path& filename, IGUIElement* parent)
{
	io::IReadFile* read = FileSystem->createAndOpenFile(filename);
	if (!read)
	{
		os::Printer::log("Unable to open gui file", filename, ELL_ERROR);
		return false;
	}

	bool ret = loadGUI(read, parent);
	read->drop();

	return ret;
}


//! Loads the gui. Note that the current gui is not cleared before.
bool CGUIEnvironment::loadGUI(io::IReadFile* file, IGUIElement* parent)
{
	if (!file)
	{
		os::Printer::log("Unable to open GUI file", ELL_ERROR);
		return false;
	}

	io::IXMLReader* reader = FileSystem->createXMLReader(file);
	if (!reader)
	{
		os::Printer::log("GUI is not a valid XML file", file->getFileName(), ELL_ERROR);
		return false;
	}

	// read file
	while(reader->read())
	{
		readGUIElement(reader, parent);
	}

	// finish up

	reader->drop();
	return true;
}


//! reads an element
void CGUIEnvironment::readGUIElement(io::IXMLReader* reader, IGUIElement* node)
{
	if (!reader)
		return;

	io::EXML_NODE nodeType = reader->getNodeType();

	if (nodeType == io::EXN_NONE || nodeType == io::EXN_UNKNOWN || nodeType == io::EXN_ELEMENT_END)
		return;

	IGUIElement* deferedNode = 0;
	if (!wcscmp(IRR_XML_FORMAT_GUI_ENV, reader->getNodeName()))
	{
		// GuiEnvironment always must be this as it would serialize into a wrong element otherwise.
		// So we use the given node next time
		if ( node && node != this )
			deferedNode = node;
		node = this; // root
	}
	else if	(!wcscmp(IRR_XML_FORMAT_GUI_ELEMENT, reader->getNodeName()))
	{
		// find node type and create it
		const core::stringc attrName = reader->getAttributeValue(IRR_XML_FORMAT_GUI_ELEMENT_ATTR_TYPE);

		node = addGUIElement(attrName.c_str(), node);

		if (!node)
			os::Printer::log("Could not create GUI element of unknown type", attrName.c_str());
	}

	// read attributes

	while(reader->read())
	{
		bool endreached = false;

		switch (reader->getNodeType())
		{
		case io::EXN_ELEMENT_END:
			if (!wcscmp(IRR_XML_FORMAT_GUI_ELEMENT,  reader->getNodeName()) ||
				!wcscmp(IRR_XML_FORMAT_GUI_ENV, reader->getNodeName()))
			{
				endreached = true;
			}
			break;
		case io::EXN_ELEMENT:
			if (!wcscmp(L"attributes", reader->getNodeName()))
			{
				// read attributes
				io::IAttributes* attr = FileSystem->createEmptyAttributes(Driver);
				attr->read(reader, true);

				if (node)
					node->deserializeAttributes(attr);

				attr->drop();
			}
			else
			if (!wcscmp(IRR_XML_FORMAT_GUI_ELEMENT, reader->getNodeName()) ||
				!wcscmp(IRR_XML_FORMAT_GUI_ENV, reader->getNodeName()))
			{
				if ( deferedNode )
					readGUIElement(reader, deferedNode);
				else
					readGUIElement(reader, node);
			}
			else
			{
				os::Printer::log("Found unknown element in irrlicht GUI file",
						core::stringc(reader->getNodeName()).c_str());
			}

			break;
		default:
			break;
		}

		if (endreached)
			break;
	}
}


//! writes an element
void CGUIEnvironment::writeGUIElement(io::IXMLWriter* writer, IGUIElement* node)
{
	if (!writer || !node )
		return;

	const wchar_t* name = 0;

	// write properties

	io::IAttributes* attr = FileSystem->createEmptyAttributes();
	node->serializeAttributes(attr);

	// all gui elements must have at least one attribute
	// if they have nothing then we ignore them.
	if (attr->getAttributeCount() != 0)
	{
		if (node == this)
		{
			name = IRR_XML_FORMAT_GUI_ENV;
			writer->writeElement(name, false);
		}
		else
		{
			name = IRR_XML_FORMAT_GUI_ELEMENT;
			writer->writeElement(name, false, IRR_XML_FORMAT_GUI_ELEMENT_ATTR_TYPE,
				core::stringw(node->getTypeName()).c_str());
		}

		writer->writeLineBreak();

		attr->write(writer);
	}

	// write children

	core::list<IGUIElement*>::ConstIterator it = node->getChildren().begin();
	for (; it != node->getChildren().end(); ++it)
	{
		if (!(*it)->isSubElement())
		{
			writer->writeLineBreak();
			writeGUIElement(writer, (*it));
		}
	}

	// write closing brace if required
	if (attr->getAttributeCount() != 0)
	{
		writer->writeClosingTag(name);
		writer->writeLineBreak();
	}

	attr->drop();
}


//! Writes attributes of the environment
void CGUIEnvironment::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const
{
	IGUISkin* skin = getSkin();

	if (skin)
	{
		out->addEnum("Skin", getSkin()->getType(), GUISkinTypeNames);
		skin->serializeAttributes(out, options);
	}
}


//! Reads attributes of the environment
void CGUIEnvironment::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options)
{
	if (in->existsAttribute("Skin"))
	{
		IGUISkin *skin = getSkin();

		EGUI_SKIN_TYPE t = (EGUI_SKIN_TYPE) in->getAttributeAsEnumeration("Skin",GUISkinTypeNames);
		if ( !skin || t != skin->getType())
		{
			skin = createSkin(t);
			setSkin(skin);
			skin->drop();
		}

		skin = getSkin();

		if (skin)
		{
			skin->deserializeAttributes(in, options);
		}

	}

	RelativeRect = AbsoluteRect =
			core::rect<s32>(Driver ? core::dimension2di(Driver->getScreenSize()) : core::dimension2d<s32>(0,0));
}


//! adds a button. The returned pointer must not be dropped.
IGUIButton* CGUIEnvironment::addButton(const core::rect<s32>& rectangle, IGUIElement* parent, s32 id, const wchar_t* text, const wchar_t *tooltiptext)
{
	IGUIButton* button = new CGUIButton(this, parent ? parent : this, id, rectangle);
	if (text)
		button->setText(text);

	if ( tooltiptext )
		button->setToolTipText ( tooltiptext );

	button->drop();
	return button;
}


//! adds a window. The returned pointer must not be dropped.
IGUIWindow* CGUIEnvironment::addWindow(const core::rect<s32>& rectangle, bool modal,
		const wchar_t* text, IGUIElement* parent, s32 id)
{
	parent = parent ? parent : this;

	IGUIWindow* win = new CGUIWindow(this, parent, id, rectangle);
	if (text)
		win->setText(text);
	win->drop();

	if (modal)
	{
		// Careful, don't just set the modal as parent above. That will mess up the focus (and is hard to change because we have to be very
		// careful not to get virtual function call, like OnEvent, in the window.
		CGUIModalScreen * modalScreen = new CGUIModalScreen(this, parent, -1);
		modalScreen->drop();
		modalScreen->addChild(win);
	}

	return win;
}


//! adds a modal screen. The returned pointer must not be dropped.
IGUIElement* CGUIEnvironment::addModalScreen(IGUIElement* parent)
{
	parent = parent ? parent : this;

	IGUIElement *win = new CGUIModalScreen(this, parent, -1);
	win->drop();

	return win;
}


//! Adds a message box.
IGUIWindow* CGUIEnvironment::addMessageBox(const wchar_t* caption, const wchar_t* text,
	bool modal, s32 flag, IGUIElement* parent, s32 id, video::ITexture* image)
{
	if (!CurrentSkin)
		return 0;

	parent = parent ? parent : this;

	core::rect<s32> rect;
	core::dimension2d<u32> screenDim, msgBoxDim;

	screenDim.Width = parent->getAbsolutePosition().getWidth();
	screenDim.Height = parent->getAbsolutePosition().getHeight();
	msgBoxDim.Width = 2;
	msgBoxDim.Height = 2;

	rect.UpperLeftCorner.X = (screenDim.Width - msgBoxDim.Width) / 2;
	rect.UpperLeftCorner.Y = (screenDim.Height - msgBoxDim.Height) / 2;
	rect.LowerRightCorner.X = rect.UpperLeftCorner.X + msgBoxDim.Width;
	rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + msgBoxDim.Height;

	IGUIWindow* win = new CGUIMessageBox(this, caption, text, flag,
		parent, id, rect, image);
	win->drop();

	if (modal)
	{
		// Careful, don't just set the modal as parent above. That will mess up the focus (and is hard to change because we have to be very
		// careful not to get virtual function call, like OnEvent, in the CGUIMessageBox.
		CGUIModalScreen * modalScreen = new CGUIModalScreen(this, parent, -1);
		modalScreen->drop();
		modalScreen->addChild( win );
	}


	return win;
}


//! adds a scrollbar. The returned pointer must not be dropped.
IGUIScrollBar* CGUIEnvironment::addScrollBar(bool horizontal, const core::rect<s32>& rectangle, IGUIElement* parent, s32 id)
{
	IGUIScrollBar* bar = new CGUIScrollBar(horizontal, this, parent ? parent : this, id, rectangle);
	bar->drop();
	return bar;
}

//! Adds a table to the environment
IGUITable* CGUIEnvironment::addTable(const core::rect<s32>& rectangle, IGUIElement* parent, s32 id, bool drawBackground)
{
	CGUITable* b = new CGUITable(this, parent ? parent : this, id, rectangle, true, drawBackground, false);
	b->drop();
	return b;
}

	//! Adds an element to display the information from the Irrlicht profiler
IGUIProfiler* CGUIEnvironment::addProfilerDisplay(const core::rect<s32>& rectangle, IGUIElement* parent, s32 id)
{
	CGUIProfiler* p = new CGUIProfiler(this, parent ? parent : this, id, rectangle, NULL);
	p->drop();
	return p;
}

//! Adds an image element.
IGUIImage* CGUIEnvironment::addImage(video::ITexture* image, core::position2d<s32> pos,
	bool useAlphaChannel, IGUIElement* parent, s32 id, const wchar_t* text)
{
	core::dimension2d<s32> sz(0,0);
	if (image)
		sz = core::dimension2d<s32>(image->getOriginalSize());

	IGUIImage* img = new CGUIImage(this, parent ? parent : this,
		id, core::rect<s32>(pos, sz));

	if (text)
		img->setText(text);

	if (useAlphaChannel)
		img->setUseAlphaChannel(true);

	if (image)
		img->setImage(image);

	img->drop();
	return img;
}


//! adds an image. The returned pointer must not be dropped.
IGUIImage* CGUIEnvironment::addImage(const core::rect<s32>& rectangle, IGUIElement* parent, s32 id, const wchar_t* text, bool useAlphaChannel)
{
	IGUIImage* img = new CGUIImage(this, parent ? parent : this,
		id, rectangle);

	if (text)
		img->setText(text);

	if ( useAlphaChannel )
		img->setUseAlphaChannel(true);

	img->drop();
	return img;
}


//! adds an mesh viewer. The returned pointer must not be dropped.
IGUIMeshViewer* CGUIEnvironment::addMeshViewer(const core::rect<s32>& rectangle, IGUIElement* parent, s32 id, const wchar_t* text)
{
	IGUIMeshViewer* v = new CGUIMeshViewer(this, parent ? parent : this,
		id, rectangle);

	if (text)
		v->setText(text);

	v->drop();
	return v;
}


//! adds a checkbox
IGUICheckBox* CGUIEnvironment::addCheckBox(bool checked, const core::rect<s32>& rectangle, IGUIElement* parent, s32 id, const wchar_t* text)
{
	IGUICheckBox* b = new CGUICheckBox(checked, this,
		parent ? parent : this , id , rectangle);

	if (text)
		b->setText(text);

	b->drop();
	return b;
}


//! adds a list box
IGUIListBox* CGUIEnvironment::addListBox(const core::rect<s32>& rectangle,
					IGUIElement* parent, s32 id, bool drawBackground)
{
	IGUIListBox* b = new CGUIListBox(this, parent ? parent : this, id, rectangle,
		true, drawBackground, false);

	if (CurrentSkin && CurrentSkin->getSpriteBank())
	{
		b->setSpriteBank(CurrentSkin->getSpriteBank());
	}
	else if (getBuiltInFont() && getBuiltInFont()->getType() == EGFT_BITMAP)
	{
		b->setSpriteBank( ((IGUIFontBitmap*)getBuiltInFont())->getSpriteBank());
	}

	b->drop();
	return b;
}

//! adds a tree view
IGUITreeView* CGUIEnvironment::addTreeView(const core::rect<s32>& rectangle,
					 IGUIElement* parent, s32 id,
					 bool drawBackground,
					 bool scrollBarVertical, bool scrollBarHorizontal)
{
	IGUITreeView* b = new CGUITreeView(this, parent ? parent : this, id, rectangle,
		true, drawBackground, scrollBarVertical, scrollBarHorizontal);

	b->setIconFont ( getBuiltInFont () );
	b->drop();
	return b;
}

//! adds a file open dialog. The returned pointer must not be dropped.
IGUIFileOpenDialog* CGUIEnvironment::addFileOpenDialog(const wchar_t* title,
				bool modal, IGUIElement* parent, s32 id,
				bool restoreCWD, io::path::char_type* startDir)
{
	parent = parent ? parent : this;

	IGUIFileOpenDialog* d = new CGUIFileOpenDialog(title, this, parent, id,
			restoreCWD, startDir);
	d->drop();

	if (modal)
	{
		// Careful, don't just set the modal as parent above. That will mess up the focus (and is hard to change because we have to be very
		// careful not to get virtual function call, like OnEvent, in the window.
		CGUIModalScreen * modalScreen = new CGUIModalScreen(this, parent, -1);
		modalScreen->drop();
		modalScreen->addChild(d);
	}

	return d;
}


//! adds a color select dialog. The returned pointer must not be dropped.
IGUIColorSelectDialog* CGUIEnvironment::addColorSelectDialog(const wchar_t* title,
				bool modal, IGUIElement* parent, s32 id)
{
	parent = parent ? parent : this;

	IGUIColorSelectDialog* d = new CGUIColorSelectDialog( title,
			this, parent, id);
	d->drop();

	if (modal)
	{
		// Careful, don't just set the modal as parent above. That will mess up the focus (and is hard to change because we have to be very
		// careful not to get virtual function call, like OnEvent, in the window.
		CGUIModalScreen * modalScreen = new CGUIModalScreen(this, parent, -1);
		modalScreen->drop();
		modalScreen->addChild(d);
	}

	return d;
}


//! adds a static text. The returned pointer must not be dropped.
IGUIStaticText* CGUIEnvironment::addStaticText(const wchar_t* text,
				const core::rect<s32>& rectangle,
				bool border, bool wordWrap,
				IGUIElement* parent, s32 id, bool background)
{
	IGUIStaticText* d = new CGUIStaticText(text, border, this,
			parent ? parent : this, id, rectangle, background);

	d->setWordWrap(wordWrap);
	d->drop();

	return d;
}


//! Adds an edit box. The returned pointer must not be dropped.
IGUIEditBox* CGUIEnvironment::addEditBox(const wchar_t* text,
			const core::rect<s32>& rectangle, bool border,
			IGUIElement* parent, s32 id)
{
	IGUIEditBox* d = new CGUIEditBox(text, border, this,
			parent ? parent : this, id, rectangle);

	d->drop();
	return d;
}


//! Adds a spin box to the environment
IGUISpinBox* CGUIEnvironment::addSpinBox(const wchar_t* text,
					 const core::rect<s32> &rectangle,
					 bool border,IGUIElement* parent, s32 id)
{
	IGUISpinBox* d = new CGUISpinBox(text, border,this,
		parent ? parent : this, id, rectangle);

	d->drop();
	return d;
}


//! Adds a tab control to the environment.
IGUITabControl* CGUIEnvironment::addTabControl(const core::rect<s32>& rectangle,
	IGUIElement* parent, bool fillbackground, bool border, s32 id)
{
	IGUITabControl* t = new CGUITabControl(this, parent ? parent : this,
		rectangle, fillbackground, border, id);
	t->drop();
	return t;
}


//! Adds tab to the environment.
IGUITab* CGUIEnvironment::addTab(const core::rect<s32>& rectangle,
	IGUIElement* parent, s32 id)
{
	IGUITab* t = new CGUITab(-1, this, parent ? parent : this,
		rectangle, id);
	t->drop();
	return t;
}


//! Adds a context menu to the environment.
IGUIContextMenu* CGUIEnvironment::addContextMenu(const core::rect<s32>& rectangle,
	IGUIElement* parent, s32 id)
{
	IGUIContextMenu* c = new CGUIContextMenu(this,
		parent ? parent : this, id, rectangle, true);
	c->drop();
	return c;
}


//! Adds a menu to the environment.
IGUIContextMenu* CGUIEnvironment::addMenu(IGUIElement* parent, s32 id)
{
	if (!parent)
		parent = this;

	IGUIContextMenu* c = new CGUIMenu(this,
		parent, id, core::rect<s32>(0,0,
				parent->getAbsolutePosition().getWidth(),
				parent->getAbsolutePosition().getHeight()));

	c->drop();
	return c;
}


//! Adds a toolbar to the environment. It is like a menu is always placed on top
//! in its parent, and contains buttons.
IGUIToolBar* CGUIEnvironment::addToolBar(IGUIElement* parent, s32 id)
{
	if (!parent)
		parent = this;

	IGUIToolBar* b = new CGUIToolBar(this, parent, id, core::rect<s32>(0,0,10,10));
	b->drop();
	return b;
}


//! Adds an element for fading in or out.
IGUIInOutFader* CGUIEnvironment::addInOutFader(const core::rect<s32>* rectangle, IGUIElement* parent, s32 id)
{
	core::rect<s32> rect;

	if (rectangle)
		rect = *rectangle;
	else if (Driver)
		rect = core::rect<s32>(core::dimension2di(Driver->getScreenSize()));

	if (!parent)
		parent = this;

	IGUIInOutFader* fader = new CGUIInOutFader(this, parent, id, rect);
	fader->drop();
	return fader;
}


//! Adds a combo box to the environment.
IGUIComboBox* CGUIEnvironment::addComboBox(const core::rect<s32>& rectangle,
	IGUIElement* parent, s32 id)
{
	IGUIComboBox* t = new CGUIComboBox(this, parent ? parent : this,
		id, rectangle);
	t->drop();
	return t;
}


//! returns the font
IGUIFont* CGUIEnvironment::getFont(const io::path& filename)
{
	// search existing font

	SFont f;
	f.NamedPath.setPath(filename);

	s32 index = Fonts.binary_search(f);
	if (index != -1)
		return Fonts[index].Font;

	// font doesn't exist, attempt to load it

	// does the file exist?

	if (!FileSystem->existFile(filename))
	{
		os::Printer::log("Could not load font because the file does not exist", f.NamedPath.getPath(), ELL_ERROR);
		return 0;
	}

	IGUIFont* ifont=0;
	io::IXMLReader *xml = FileSystem->createXMLReader(filename );
	if (xml)
	{
		// this is an XML font, but we need to know what type
		EGUI_FONT_TYPE t = EGFT_CUSTOM;

		bool found=false;
		while(!found && xml->read())
		{
			if (xml->getNodeType() == io::EXN_ELEMENT)
			{
				if (core::stringw(L"font") == xml->getNodeName())
				{
					if (core::stringw(L"vector") == xml->getAttributeValue(L"type"))
					{
						t = EGFT_VECTOR;
						found=true;
					}
					else if (core::stringw(L"bitmap") == xml->getAttributeValue(L"type"))
					{
						t = EGFT_BITMAP;
						found=true;
					}
					else found=true;
				}
			}
		}

		if (t==EGFT_BITMAP)
		{
			CGUIFont* font = new CGUIFont(this, filename);
			ifont = (IGUIFont*)font;

			// load the font
			io::path directory;
			core::splitFilename(filename, &directory);
			if (!font->load(xml, directory))
			{
				font->drop();
				font  = 0;
				ifont = 0;
			}
		}
		else if (t==EGFT_VECTOR)
		{
			// todo: vector fonts
			os::Printer::log("Unable to load font, XML vector fonts are not supported yet", f.NamedPath, ELL_ERROR);

			//CGUIFontVector* font = new CGUIFontVector(Driver);
			//ifont = (IGUIFont*)font;
			//if (!font->load(xml))
		}
		xml->drop();
	}


	if (!ifont)
	{

		CGUIFont* font = new CGUIFont(this, f.NamedPath.getPath() );
		ifont = (IGUIFont*)font;
		if (!font->load(f.NamedPath.getPath()))
		{
			font->drop();
			return 0;
		}
	}

	// add to fonts.

	f.Font = ifont;
	Fonts.push_back(f);

	return ifont;
}


//! add an externally loaded font
IGUIFont* CGUIEnvironment::addFont(const io::path& name, IGUIFont* font)
{
	if (font)
	{
		SFont f;
		f.NamedPath.setPath(name);
		s32 index = Fonts.binary_search(f);
		if (index != -1)
			return Fonts[index].Font;
		f.Font = font;
		Fonts.push_back(f);
		font->grab();
	}
	return font;
}

//! remove loaded font
void CGUIEnvironment::removeFont(IGUIFont* font)
{
	if ( !font )
		return;
	for ( u32 i=0; i<Fonts.size(); ++i )
	{
		if ( Fonts[i].Font == font )
		{
			Fonts[i].Font->drop();
			Fonts.erase(i);
			return;
		}
	}
}

//! returns default font
IGUIFont* CGUIEnvironment::getBuiltInFont() const
{
	if (Fonts.empty())
		return 0;

	return Fonts[0].Font;
}


IGUISpriteBank* CGUIEnvironment::getSpriteBank(const io::path& filename)
{
	// search for the file name

	SSpriteBank b;
	b.NamedPath.setPath(filename);

	s32 index = Banks.binary_search(b);
	if (index != -1)
		return Banks[index].Bank;

	// we don't have this sprite bank, we should load it
	if (!FileSystem->existFile(b.NamedPath.getPath()))
	{
		if ( filename != DefaultFontName )
		{
			os::Printer::log("Could not load sprite bank because the file does not exist", b.NamedPath.getPath(), ELL_DEBUG);
		}
		return 0;
	}

	// todo: load it!

	return 0;
}


IGUISpriteBank* CGUIEnvironment::addEmptySpriteBank(const io::path& name)
{
	// no duplicate names allowed

	SSpriteBank b;
	b.NamedPath.setPath(name);

	const s32 index = Banks.binary_search(b);
	if (index != -1)
		return 0;

	// create a new sprite bank

	b.Bank = new CGUISpriteBank(this);
	Banks.push_back(b);

	return b.Bank;
}


//! Creates the image list from the given texture.
IGUIImageList* CGUIEnvironment::createImageList(  video::ITexture* texture,
					core::dimension2d<s32>	imageSize, bool useAlphaChannel )
{
	CGUIImageList* imageList = new CGUIImageList( Driver );
	if( !imageList->createImageList( texture, imageSize, useAlphaChannel ) )
	{
		imageList->drop();
		return 0;
	}

	return imageList;
}

//! Returns the root gui element.
IGUIElement* CGUIEnvironment::getRootGUIElement()
{
	return this;
}


//! Returns the next element in the tab group starting at the focused element
IGUIElement* CGUIEnvironment::getNextElement(bool reverse, bool group)
{
	// start the search at the root of the current tab group
	IGUIElement *startPos = Focus ? Focus->getTabGroup() : 0;
	s32 startOrder = -1;

	// if we're searching for a group
	if (group && startPos)
	{
		startOrder = startPos->getTabOrder();
	}
	else
	if (!group && Focus && !Focus->isTabGroup())
	{
		startOrder = Focus->getTabOrder();
		if (startOrder == -1)
		{
			// this element is not part of the tab cycle,
			// but its parent might be...
			IGUIElement *el = Focus;
			while (el && el->getParent() && startOrder == -1)
			{
				el = el->getParent();
				startOrder = el->getTabOrder();
			}

		}
	}

	if (group || !startPos)
		startPos = this; // start at the root

	// find the element
	IGUIElement *closest = 0;
	IGUIElement *first = 0;
	startPos->getNextElement(startOrder, reverse, group, first, closest, false, (FocusFlags & EFF_CAN_FOCUS_DISABLED) != 0);

	if (closest)
		return closest; // we found an element
	else if (first)
		return first; // go to the end or the start
	else if (group)
		return this; // no group found? root group
	else
		return 0;
}

void CGUIEnvironment::setFocusBehavior(u32 flags)
{
	FocusFlags = flags;
}

u32 CGUIEnvironment::getFocusBehavior() const
{
	return FocusFlags;
}

//! creates an GUI Environment
IGUIEnvironment* createGUIEnvironment(io::IFileSystem* fs,
					video::IVideoDriver* Driver,
					IOSOperator* op)
{
	return new CGUIEnvironment(fs, Driver, op);
}


} // end namespace gui
} // end namespace irr

#endif // _IRR_COMPILE_WITH_GUI_

