/** SGX

The   sss    gggg x   x   
     s      g      x x    Engine
      sss   g  gg   x
         s  g   g  x x          Copyright 1995-2008.
     ssss    gggg x   x         Steven Goodwin. All Rights Reserved.
    
This version has been released under the GNU Public License v2.0.

Please see the license files for details.

File: src/sgx/gui/sgxguimanager.cpp

*/

#include "sgx/core/core.hpp"
#include "sgx/graphics/graphics.hpp"
#include "sgx/gui/gui.hpp"

//
// Basic class handlers
//
CSGXGUI *CSGXGUI::ms_pSingleton = NULL;

CSGXGUI *CSGXGUI::get() {
    if (ms_pSingleton == NULL) {
        ms_pSingleton = new CSGXGUI();
		ms_pSingleton->initialize();
    }

    return ms_pSingleton;
}

CSGXGUI::CSGXGUI() :
	m_vPosition(0,0),
	m_PointerIsDown(FALSE),
	m_vLastPosition(0,0),
	m_WasPointerDown(FALSE),
	
	m_iPointerImageSize(32),
	m_bPointerVisible(FALSE),
	m_pPointerTexture(NULL),
	m_iPointerRegionIn(0),
	m_iPointerRegionOut(0),
	m_vPointerHotspot(0,0),

	m_pRootWidget(NULL),
	m_pDefaultFont(NULL),
	
	m_pActive(NULL),
	m_pFocus(NULL)	
{
	m_pCurrentSkin = m_pDefaultSkin = new CSGXGUISkin();
}

CSGXGUI::~CSGXGUI()  {
	release();
	delete m_pDefaultSkin;
}


tBOOL
CSGXGUI::registerWidget(sgxWidget *pWidget)  {
	CSGXEngine::registerObject(pWidget);
	
	if (pWidget && pWidget->isPointInside(m_vPosition)) {
		pWidget->onHoverEnter(m_vPosition);
	}
	return TRUE;
}

void
CSGXGUI::unregisterObjects()  {
	clearFocus();
	clearActive();
	CSGXEngine::unregisterObjects();
}

/**
This returns the previous skin, or NULL if there was none.
The use of the 'default skin' is for internal convenience - it
saves us check for !=NULL each time. The end user doesn't need 
to know about it, therefore we never return it to them.

This is because the general usage is:

	delete setCurrentSkin(pNewSkin);
*/
CSGXGUISkin *
CSGXGUI::setCurrentSkin(CSGXGUISkin *pSkin)  {
	CSGXGUISkin *prev = m_pCurrentSkin;
	m_pCurrentSkin = pSkin;
	return prev == m_pDefaultSkin ? NULL : prev;
}

const CSGXFont *		
CSGXGUI::setDefaultFont(const CSGXFont *pFont) {
	const CSGXFont *prev = m_pDefaultFont;
	m_pDefaultFont = pFont;
	return pFont == m_pDefaultFont ? NULL : prev;
}

	/**
This always returns a valid skin, with which to render.
The const-const means that no one can corrupt out default skin,
should that be the only one available.

Contrast with @setCurrentSkin concerning the return case of NULL
*/
const CSGXGUISkin * const
CSGXGUI::getCurrentSkin() const {
	return m_pCurrentSkin == NULL ? m_pDefaultSkin : m_pCurrentSkin;
}

CSGXGUISkin * 
CSGXGUI::getCurrentSkin() {
	return m_pCurrentSkin == NULL ? m_pDefaultSkin : m_pCurrentSkin;
}

//
// Gadget creation
//
sgxWidget *CSGXGUI::createFrame(const tINT32 x, const tINT32 y, const sgxGUIFrameParams &params) {
	sgxWidget *ptr = new sgxGUIFrame(x,y,params);
	registerWidget(ptr);
	return ptr;
}

sgxWidget *CSGXGUI::createButton(const tINT32 x, const tINT32 y, const sgxGUIButtonParams &params, CSGXCallback *pCallback, const tUINT32 userdata) {
	sgxWidget *ptr = new sgxGUIButton(x,y,params);
	ptr->setUserData(userdata);
	ptr->setCallback(pCallback);
	registerWidget(ptr);
	return ptr;
}

sgxWidget *CSGXGUI::createImageButton(const tINT32 x, const tINT32 y, const sgxGUIImageButtonParams &params, CSGXCallback *pCallback, const tUINT32 userdata) {
	sgxWidget *ptr = new sgxGUIImageButton(x,y,params);
	ptr->setUserData(userdata);
	ptr->setCallback(pCallback);
	registerWidget(ptr);
	return ptr;
}

sgxWidget *CSGXGUI::createCheckBox(const tINT32 x, const tINT32 y, const sgxGUICheckBoxParams &params, CSGXCallback *pCallback, const tUINT32 userdata) {
	sgxWidget *ptr = new sgxGUICheckBox(x,y,params);
	ptr->setUserData(userdata);
	ptr->setCallback(pCallback);
	registerWidget(ptr);
	return ptr;
}

sgxWidget *CSGXGUI::createRadioButton(const tINT32 x, const tINT32 y, const sgxGUIRadioButtonParams &params, CSGXCallback *pCallback, const tUINT32 userdata) {
	sgxWidget *ptr = new sgxGUIRadioButton(x,y,params);
	ptr->setUserData(userdata);
	ptr->setCallback(pCallback);
	registerWidget(ptr);
	return ptr;
}

sgxWidget *CSGXGUI::createHotSpot(const tINT32 x, const tINT32 y, const sgxGUIHotSpotParams &params, CSGXCallback *pCallback, const tUINT32 userdata) {
	sgxWidget *ptr = new sgxGUIHotSpot(x,y,params);
	ptr->setUserData(userdata);
	ptr->setCallback(pCallback);
	registerWidget(ptr);
	return ptr;
}

sgxWidget *CSGXGUI::createStaticText(const tINT32 x, const tINT32 y, const sgxGUIStaticTextParams &params) {
	sgxWidget *ptr = new sgxGUIStaticText(x,y, CGUIDesign::fmtLeftJustify, CGUIDesign::fmtTopAlignment, params);
	registerWidget(ptr);
	return ptr;
}

sgxWidget *CSGXGUI::createStaticImage(const tINT32 x, const tINT32 y, const sgxGUIStaticImageParams &params) {
	sgxWidget *ptr = new sgxGUIStaticImage(x,y,params);
	registerWidget(ptr);
	return ptr;
}

//
// Group creation
//
sgxWidgetGroup	*CSGXGUI::createGroup(const tUINT32 userData=0) {
	sgxWidgetGroup *ptr = new sgxWidgetGroup(userData);
	registerObject(ptr);
	return ptr;
}


//
// Input channels. The game loop must push its interface requests
// through with these functions. This way it is the *game* that can 
// determine which mouse/pointer is used to control the interface.
//
void CSGXGUI::applyPointerPosition(const sgxPoint2i &pos) {
	m_vPosition = pos;
	m_vPosition -= m_vPointerHotspot;
}

void CSGXGUI::applyPointerUp() {
	m_PointerIsDown = FALSE;
}

void CSGXGUI::applyPointerDown() {
	m_PointerIsDown = TRUE;
}

void CSGXGUI::setPointerImage() {
	m_pPointerTexture = m_pCurrentSkin->getStandardPointerTexture();
	m_iPointerRegionIn = m_pCurrentSkin->getStandardPointerRegionIn();
	m_iPointerRegionOut = m_pCurrentSkin->getStandardPointerRegionOut();
	m_iPointerImageSize = m_pPointerTexture->getRegionWidth(m_iPointerRegionIn);
}

void CSGXGUI::setPointerImage(CSGXTexture *pTexture, tUINT32 regionIn, tUINT32 regionOut, tUINT32 size) {
	m_pPointerTexture = pTexture;
	m_iPointerRegionIn = regionIn;
	m_iPointerRegionOut = regionOut;
	m_iPointerImageSize = size;
}

void CSGXGUI::setPointerHotspot(const sgxPoint2i &vHotspot) {
	m_vPointerHotspot = vHotspot;
}


void CSGXGUI::showPointer(tBOOL visible) {
	m_bPointerVisible = visible;
}

void CSGXGUI::hidePointer() {
	m_bPointerVisible = FALSE;
}

//
// Standard handlers
//

void CSGXGUI::update(const tREAL32 telaps)  {
	update(m_pRootWidget, telaps);
}

void CSGXGUI::update(sgxWidget *pRoot, const tREAL32 telaps) {
	SGX_UNUSED(telaps);
	sgxWidget *pWidget = NULL;

	if (pRoot) {
		pWidget = pRoot->getWidgetAt(m_vPosition, sgxWidget::eFunctioningWidgets);
		pRoot->update(telaps);
	}

	// onClick
	if (m_PointerIsDown && !m_WasPointerDown) {
		if (pWidget) {
			if (pWidget->isEnabled()) {
				pWidget->onPressed(m_vPosition);
				setFocus(pWidget);
				setActive(pWidget);
			}
		} else {
			clearFocus();
		}
	}

	// on release
	if (m_WasPointerDown && !m_PointerIsDown) {
		if (m_pActive) {
			m_pActive->onRelease(m_vPosition);
		}
		clearActive();
	}

	// on normal moving
	if (m_WasPointerDown == m_PointerIsDown && m_vPosition != m_vLastPosition) {
		sgxWidget *pLastPositionWidget = getWidgetAt(m_vLastPosition, sgxWidget::eFunctioningWidgets);
		sgxWidget *pCurrentPositionWidget = getWidgetAt(m_vPosition, sgxWidget::eFunctioningWidgets);

		// TODO: episilon difference
		if (m_pActive) {
			// handle the moving onto, and off from, the current widget
			if (pCurrentPositionWidget == m_pActive && pLastPositionWidget != m_pActive) {
				m_pActive->onReenterArea(m_vPosition);
			}
			if (pLastPositionWidget == m_pActive && pCurrentPositionWidget != m_pActive) {
				m_pActive->onLeaveArea(m_vPosition);
			}

		} else {	// nothing active, just do hovers
			if (pLastPositionWidget != pCurrentPositionWidget) {
				if (pLastPositionWidget) {
					pLastPositionWidget->onHoverLeave(m_vLastPosition);
				}
				if (pCurrentPositionWidget) {
					pCurrentPositionWidget->onHoverEnter(m_vPosition);
				}
			}
		}
	}

	// onKeyDown
	/*
	key = getLastKeyPressed();
	altkey = getAltKeysDown();
	NOTE: key handling can be trivial to prove PC had it. Wii won't
if key is ESCAPE { if (m_pActive) leavearea(); clearActive(); 
elif key is space/return) {
	if (m_pFocus) {
		m_pFocus->onSelect();//??? or onPress
	}
	elif (key == TAB) {
	// next gadget
	::iterator it = parent->getFirstChild();
	find self,
	++it
	if (it == child.end() {
		it = child.begin();
	}
	return *it;
	}
	elif pWidget = getWidgetFromKeypress(key,altkey) {
	}
	else if (m_pActive) {
		m_pActive->onKeyDown
	}

	*/

	// Now update the lasts...
	m_WasPointerDown = m_PointerIsDown;
	m_vLastPosition = m_vPosition;
}

//
// Internal processing
//
void CSGXGUI::setFocus(sgxWidget *pWidget) {
	m_pFocus = pWidget;
	if (m_pFocus) {
		m_pFocus->onFocusGain();
	}
}

void CSGXGUI::setActive(sgxWidget *pWidget) {
	m_pActive = pWidget;
	if (m_pActive) {
		m_pActive->onActiveGain();
	}
}


sgxWidget * CSGXGUI::getActiveWidget() const {
	return m_pActive;
}
sgxWidget * CSGXGUI::getFocusWidget() const {
	return m_pFocus;
}

void CSGXGUI::clearFocus() {
	if (m_pFocus) {
		m_pFocus->onFocusLose();
	}
	m_pFocus = NULL;
}

void CSGXGUI::clearActive() {
	if (m_pActive) {
		m_pActive->onActiveLose();
	}
	m_pActive = NULL;
}

sgxWidget *CSGXGUI::getWidgetAt(const sgxWidget::tSelectionSet fromSet) {
	return getWidgetAt(m_vPosition, fromSet);
}

sgxWidget *CSGXGUI::getWidgetAt(const sgxPoint2i &position, const sgxWidget::tSelectionSet fromSet) {
	if (m_pRootWidget) {
		return m_pRootWidget->getWidgetAt(position, fromSet);
	}
	return NULL;
}

void CSGXGUI::setRootWidget(sgxWidget *pWidget) {
	m_pRootWidget = pWidget;
}

void CSGXGUI::draw() const {
	draw(m_pRootWidget);
}

void CSGXGUI::draw(const sgxWidget *pFrame) const {
	const CSGXFont *pPopFont = NULL;

	if (m_pDefaultFont) {
		pPopFont = CSGXFontManager::get()->getCurrentFont();
		CSGXFontManager::get()->setCurrentFont(m_pDefaultFont);
	}

	if (pFrame) {
		pFrame->draw(0,0);
	}

	if (m_bPointerVisible) {
		CSGXDrawSurface *pSurface = CSGXDrawSurfaceManager::get()->getDisplaySurface();
		CSGXGraphicsEngine::get()->set2DMode();	

		pSurface->resetRenderTransform();
		pSurface->setFillTexture(m_pPointerTexture);
		pSurface->setFillTextureRegion(m_PointerIsDown?m_iPointerRegionIn:m_iPointerRegionOut);
		pSurface->setFillColor(sgxColorRGB::White);
		pSurface->fillBox(m_vPosition.x, m_vPosition.y, m_vPosition.x+m_iPointerImageSize, m_vPosition.y+m_iPointerImageSize);
	}
	//
	if (m_pDefaultFont) {
		CSGXFontManager::get()->setCurrentFont(pPopFont);
	}
}

CGUIDesign *
CSGXGUI::loadDesign(const sgxString &filename) {
	CSGXFile file(filename+".dat");

	return loadDesign(file);
}

CGUIDesign::CGUIDesign(tUINT32 numAssets) {
	m_pAssetList = new CGUIDesignAsset *[numAssets];
	m_iNumTextures = numAssets;
}

CGUIDesign::~CGUIDesign() {
	for(tUINT32 ia=0;ia<m_iNumTextures;++ia) {
		//delete m_pAssetList[ia];
	}
	for(tUINT32 is=0;is<m_iNumScreens;++is) {
		delete m_pScreenList[is];
	}
}

//
//
//
CGUIDesignScreen::CGUIDesignScreen() :
m_pFrame(NULL) {
}

CGUIDesignScreen::~CGUIDesignScreen() {

	sgxVector<const sgxWidget *>::const_iterator cit = m_WidgetList.begin();

	for(;cit!=m_WidgetList.end();++cit) {
		delete *cit;
	}
	m_WidgetList.clear();
}

sgxWidget * 
CGUIDesignScreen::addWidget(sgxWidget *pWidget) {
	m_WidgetList.push_back(pWidget);
	return pWidget;
}



/**
This adds GUI elements into the current screen, based around
the descriptions present in the data file.
*/
sgxWidget *
CGUIDesignScreen::applyScreen() {
	if (!m_pFrame) {
		sgxVector<const sgxWidget *>::const_iterator cit = m_WidgetList.begin();

		m_pFrame = CSGXGUI::get()->createFrame();
		for(;cit!=m_WidgetList.end();++cit) {
			m_pFrame->addChildWidget(SGX_CONST_CAST(sgxWidget *, *cit));
		}
	}

	return m_pFrame;
}

void
CGUIDesignScreen::releaseScreenWidgets() {
	if (m_pFrame) {
		//m_pFrame->releaseChildWidgets();	// this isn't called, because we own the widgets in DesignScreen
		delete m_pFrame;
		m_pFrame = NULL;
	}
}

void 
CGUIDesign::adjustPosition(tINT32 &x, tINT32 &y, const tUINT32 w, const tUINT32 h, const tJustify fx, const tJustify fy) {
//	SGX_ASSERT(w && h && "
	switch(fx) {
		case fmtLeftJustify:
			break;
		case fmtCentreJustify:
			x -= w/2;
			break;
		case fmtRightJustify:
			x -= w;
			break;
	}
	//
	switch(fy) {
		case fmtTopAlignment:
			break;
		case fmtMiddleAlignment:
			y -= h/2;
			break;
		case fmtBottomAlignment:
			y -= h;
			break;
	}
}

CGUIDesign *
CSGXGUI::loadDesign(CSGXFile &file) {
	if (!file.open()) {
		return NULL;
	}

	tUINT32 numAssets;

	file >> numAssets;
	CGUIDesign *pDesign = new CGUIDesign(numAssets);
	for(tUINT32 i=0;i<numAssets;++i) {
		tBYTE assetType;
		sgxString str;
		file >> assetType >> str;
		switch(assetType) {
			case CGUIDesign::assetAnimation:
				pDesign->m_pAssetList[i] = SGX_STATIC_CAST(CGUIDesignAnimationAsset *, CSGXTextureAnimationManager::loadData(str));
				break;
			case CGUIDesign::assetTexture:
				pDesign->m_pAssetList[i] = SGX_STATIC_CAST(CGUIDesignTextureAsset *, CSGXTextureManager::get()->loadTexture(str));
				break;
			default:
				sgxError("No such asset type.");
		}
	}
	// Screens
	file >> pDesign->m_iNumScreens;
	pDesign->m_pScreenList = new CGUIDesignScreen *[pDesign->m_iNumScreens];
	for(tUINT32 i=0;i<pDesign->m_iNumScreens;++i) {
		CGUIDesignScreen *pCurrScreen = pDesign->m_pScreenList[i] = new CGUIDesignScreen();
		tUINT32 items;
		file >> items;
		for(tUINT32 i=0;i<items;++i) {
			tBYTE type;
			file >> type;
			//
			tINT32 x,y,visible, enabled, fx, fy, userdata;
			sgxWidget *pWidget = NULL;
			file >> x >> y >> visible >> enabled >> fx >> fy >> userdata;
			switch(type) {
				case CGUIDesignScreen::widgetHotspot:
					{
						tUINT32  w, h;
						file >> w >> h;

						CGUIDesign::adjustPosition(x,y,w,h,(CGUIDesign::tJustify)fx,(CGUIDesign::tJustify)fy);
						pWidget = pCurrScreen->addWidget(new sgxGUIHotSpot(x, y, sgxGUIHotSpotParams(w, h)));
					}
					break;
				case CGUIDesignScreen::widgetImage:
					{
						tUINT32 textureIndex, region, w, h, style, intColor;
						sgxColorRGBA col;
						file >> textureIndex >> region >> w >> h >> style >> intColor;

						col.fromInt(intColor);

						CGUIDesignTextureAsset *pTexture = SGX_STATIC_CAST(CGUIDesignTextureAsset *, pDesign->m_pAssetList[textureIndex]);

						if (w == 0) {
							w = pTexture->getRegionWidth(region);
						}
						if (h == 0) {
							h = pTexture->getRegionHeight(region);
						}
						// TODO: move this common code for vary offset. x-= xf{w}?
						CGUIDesign::adjustPosition(x,y,w,h,(CGUIDesign::tJustify)fx,(CGUIDesign::tJustify)fy);
						pWidget = pCurrScreen->addWidget(new sgxGUIStaticImage(x, y, sgxGUIStaticImageParams(pTexture, region, (sgxGUIStaticImageParams::tStyle)style, col, w, h)));
					}
					break;

				case CGUIDesignScreen::widgetAnimatedImage:
					{
						tUINT32 textureIndex, w, h, intColor;
						tUINT32	animationAsset, animationSequence;
						sgxColorRGBA col;
						file >> textureIndex >> w >> h >> intColor >> animationAsset >> animationSequence;

						col.fromInt(intColor);

						CGUIDesignTextureAsset *pTexture = SGX_STATIC_CAST(CGUIDesignTextureAsset *, pDesign->m_pAssetList[textureIndex]);
						CGUIDesignAnimationAsset *pAnimation = SGX_STATIC_CAST(CGUIDesignAnimationAsset *, pDesign->m_pAssetList[animationAsset]);

						pWidget = pCurrScreen->addWidget(new sgxGUIDynamicImage(x, y, sgxGUIDynamicImageParams(pTexture, pAnimation, animationSequence, w, h, (CGUIDesign::tJustify)fx, (CGUIDesign::tJustify)fy)));
					}
					break;

					
				case CGUIDesignScreen::widgetText:
					{
						sgxString str;
						tUINT32 color;
						sgxColorRGBA col;

						file >> str;
						file >> color;

						col.fromInt(color);

						pWidget = pCurrScreen->addWidget(new sgxGUIStaticText(x, y, (CGUIDesign::tJustify)fx,(CGUIDesign::tJustify)fy, sgxGUIStaticTextParams(str, col)));
						pWidget->setText(SGXText::getText(str.c_str()));
					}
					break;
				case CGUIDesignScreen::widgetButton:
					{
						tUINT32 w, h=0;
						sgxString txt;

						file >> w >> txt;
						
						if (w == 0) {
							w = getCurrentSkin()->getDefaultButtonWidth();
						}
						if (h == 0) {
							h = getCurrentSkin()->getDefaultButtonHeight();
						}

						CGUIDesign::adjustPosition(x,y,w,h,(CGUIDesign::tJustify)fx,(CGUIDesign::tJustify)fy);
						pWidget = pCurrScreen->addWidget(new sgxGUIButton(x, y, sgxGUIButtonParams(txt, w)));
					}
					break;
				case CGUIDesignScreen::widgetImageButton:
					{
						tUINT32 type;
						file >> type;

						tUINT32 w = getCurrentSkin()->getDefaultImageButtonWidth();
						tUINT32 h = getCurrentSkin()->getDefaultImageButtonHeight();

						CGUIDesign::adjustPosition(x,y,w,h,(CGUIDesign::tJustify)fx,(CGUIDesign::tJustify)fy);
						pWidget = pCurrScreen->addWidget(new sgxGUIImageButton(x, y, sgxGUIImageButtonParams((sgxGUISkinnedImageButton::tButtonType)type)));
					}
					break;

				default:
					sgxTrace("WARNING: Unknown widget type: %d", type);
			}
			//
			if (pWidget) {
				pWidget->setUserData(userdata);

				if (!visible) {
					pWidget->hide();
				}
				if (!enabled) {
					pWidget->disable();
				}
			}
		}
	}

	return pDesign;
}

