// 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"

#include "IVideoDriver.h"

#include "CGUISkin.h"
#include "CGUIButton.h"
#include "CGUIScrollBar.h"
#include "CGUIFont.h"
#include "CGUISpriteBank.h"
#include "CGUIImage.h"
#include "CGUICheckBox.h"
#include "CGUIListBox.h"
#include "CGUIImageList.h"
#include "CGUIFileOpenDialog.h"
#include "CGUIStaticText.h"
#include "CGUIEditBox.h"
#include "CGUITabControl.h"
#include "CGUIComboBox.h"

#include "IWriteFile.h"
#ifdef IRR_ENABLE_BUILTIN_FONT
#include "BuiltInFont.h"
#endif
#include "os.h"

namespace irr
{
namespace gui
{

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

	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()
{
	clearDeletionQueue();

	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();

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

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

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


void CGUIEnvironment::loadBuiltInFont()
{
#ifdef IRR_ENABLE_BUILTIN_FONT
	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();
#endif
}


//! draws all gui elements
void CGUIEnvironment::drawAll(bool useScreenSize)
{
	if (useScreenSize && Driver)
	{
		core::dimension2d<s32> dim(Driver->getScreenSize());
		if (AbsoluteRect.LowerRightCorner.X != dim.Width ||
			AbsoluteRect.UpperLeftCorner.X != 0 ||
			AbsoluteRect.LowerRightCorner.Y != dim.Height ||
			AbsoluteRect.UpperLeftCorner.Y != 0
			)
		{
			setRelativePosition(core::recti(0,0,dim.Width, dim.Height));
		}
	}

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

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

	clearDeletionQueue();
}


//! 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;
	}

	getRootGUIElement()->removeAllChildren();
}


//! 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::addToDeletionQueue(IGUIElement* element)
{
	if (!element)
		return;

	element->grab();
	DeletionQueue.push_back(element);
}

void CGUIEnvironment::clearDeletionQueue()
{
	if (DeletionQueue.empty())
		return;

	for (u32 i=0; i<DeletionQueue.size(); ++i)
	{
		DeletionQueue[i]->remove();
		DeletionQueue[i]->drop();
	}

	DeletionQueue.clear();
}

//
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
			// CAREFUL when changing - there's an identical check in CGUIModalScreen::OnEvent
			if (FocusFlags & EFF_SET_ON_TAB &&
				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;
	case EET_STRING_INPUT_EVENT:
		if (Focus && Focus->OnEvent(event))
			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;
}


//! 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 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 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 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 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;

	if (modal)
		return nullptr;

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

	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 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(this, parent ? parent : this,
		rectangle, id);
	t->drop();
	return t;
}


//! 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;
#if 0
		{
			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;
			}
		}
#endif


	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