/** SGX

The   sss    gggg x   x   
     s      g      x x    Engine
      sss   g  gg   x
         s  g   g  x x          Copyright 1995-2009.
     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: sgx/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_pCurrentSurface = CSGXDrawSurfaceManager::get()->getDisplaySurface();
	m_pCurrentSkin = m_pDefaultSkin = new CSGXGUISkin();
	for(tUINT32 i=0;i<8;++i) {
		m_isKeyDown[i] = m_wasKeyDown[i] = 0;
	}
}

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


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

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

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

CSGXDrawSurface *		
CSGXGUI::setCurrentSurface(CSGXDrawSurface *pSurface) {
	CSGXDrawSurface *prev = m_pCurrentSurface;
	m_pCurrentSurface = pSurface;
	return 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, const tBOOL registerWithGUI/*=TRUE*/) {
	sgxWidget *ptr = new sgxGUIFrame(x,y,params);
	registerWithGUI && registerWidget(ptr);
	return ptr;
}

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

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

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

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

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

sgxWidget *CSGXGUI::createStaticText(const tINT32 x, const tINT32 y, const CGUIDesign::tJustify jx, const CGUIDesign::tJustify jy, const sgxGUIStaticTextParams &params, const tBOOL registerWithGUI/*=TRUE*/) {
	sgxWidget *ptr = new sgxGUIStaticText(x,y, jx, jy, params);
	registerWithGUI && registerWidget(ptr);
	return ptr;
}

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

sgxWidget *CSGXGUI::createDynamicImage(const tINT32 x, const tINT32 y, const sgxGUIDynamicImageParams &params, const tBOOL registerWithGUI/*=TRUE*/) {
	sgxWidget *ptr = new sgxGUIDynamicImage(x,y,params);
	registerWithGUI && registerWidget(ptr);
	return ptr;
}

sgxWidget *CSGXGUI::createGUIButton(const tINT32 x, const tINT32 y, const sgxGUIButtonParams &params, const tBOOL registerWithGUI/*=TRUE*/) {
	sgxWidget *ptr = new sgxGUIButton(x,y,params);
	registerWithGUI && registerWidget(ptr);
	return ptr;
}

sgxWidget *CSGXGUI::createGUIImageButton(const tINT32 x, const tINT32 y, const sgxGUIImageButtonParams &params, const tBOOL registerWithGUI/*=TRUE*/) {
	sgxWidget *ptr = new sgxGUIImageButton(x,y,params);
	registerWithGUI && registerWidget(ptr);
	return ptr;
}


//
// Group creation
//
sgxWidgetGroup	*CSGXGUI::createGroup(const tUINT32 userData=0, const tBOOL registerWithGUI/*=TRUE*/) {
	sgxWidgetGroup *ptr = new sgxWidgetGroup(userData);
	registerWithGUI && registerScenarioObject(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::applyKeyboardUp(const tUINT32 key) {
	m_isKeyDown[key/32] &= ~(1<<(key&31));
}

void			
CSGXGUI::applyKeyboardDown(const tUINT32 key) {
	m_isKeyDown[key/32] |= (1<<(key&31));
}

void			
CSGXGUI::applyKeyboardState(const tUINT32 key, const tBOOL state) {
	if (state) {
		applyKeyboardDown(key);
	} else {
		applyKeyboardUp(key);
	}
}

tUINT32			
CSGXGUI::applyKeyboardState(const tBYTE *bitStates) {
	for(tUINT32 i=0;i<8;++i) {
		m_isKeyDown[i] |= bitStates[i];
	}
	return 1;
}


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) {
#if 0
		sgxPoint2i pt(m_vPosition);
		sgxMatrix43 inverse;

		m_pCurrentSurface->m_Xform.inv(inverse);
		sgxVector2 v2 = inverse.mul(pt.x,pt.y);
		sgxVector2 v2b = m_pCurrentSurface->m_Xform.m.mul(pt.x,pt.y);
		pt.x = v2.x;
		pt.y = v2.y;
#endif
		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 - iterate all keys with changed states, and process the first.]
	for(tUINT32 i=0;i<256;++i) {
		tBOOL wasDown = (m_wasKeyDown[i/32] & (1<<(i&31)));
		tBOOL isDown = (m_isKeyDown[i/32] & (1<<(i&31)));

		if (!wasDown && isDown) {
			// pressed
			sgxWidget *pWidget = getWidgetWithKeyShortcut(i);
			if (pWidget && pWidget->isEnabled()) {
				pWidget->onKeyDown(i);
				pWidget->onPressed(m_vPosition);
			}
		} else if (wasDown && !isDown) {
			// released
			sgxWidget *pWidget = getWidgetWithKeyShortcut(i);
			if (pWidget && pWidget->isEnabled()) {
				pWidget->onRelease(m_vPosition);
				pWidget->onFocusLose();
				pWidget->onKeyUp(i);
				
			}
		}
	}
#if 0
	// if down, send
			if (pWidget) {
			if (pWidget->isEnabled()) {
				pWidget->onPressed(m_vPosition);
				setFocus(pWidget);
				setActive(pWidget);
			}
			}
	// if up, send clear to 
			clearActive();
#endif
	/*
		m_wasKeyDown[key/32] |= (1<<(key&31));

	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;

	for(tUINT32 i=0;i<8;++i) {
		m_wasKeyDown[i] = m_isKeyDown[i];
	}
}

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

sgxWidget *CSGXGUI::getWidgetOfUserData(const tCHAR c, const tUINT32 idx) {
	sgxString str;
	sgxStringFormat(str, "%c%d", c, idx);
	return getWidgetOfUserData(str);
}

sgxWidget *CSGXGUI::getWidgetOfUserData(const sgxString &str) {
	tUINT32 userdata = sgxGetCRC32(str);
	return getWidgetOfUserData(userdata);
}

sgxWidget *CSGXGUI::getWidgetOfUserData(const tUINT32 userdata) {
	if (m_pRootWidget) {
		return m_pRootWidget->getWidgetOfUserData(userdata);
	}
	return NULL;
}

sgxWidget *CSGXGUI::getWidgetWithKeyShortcut(const tUINT32 key) {
	if (m_pRootWidget) {
		return m_pRootWidget->getWidgetWithKeyShortcut(key);
	}
	return NULL;
}

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

	if (m_pRootWidget != pWidget) {
		clearFocus();
		clearActive();
	}
	m_pRootWidget = pWidget;
	return prev;
}

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

void CSGXGUI::draw(const sgxWidget *pFrame) const {
	drawScreens(pFrame);
	drawCursor();
}

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

void CSGXGUI::drawScreens(const sgxWidget *pFrame) const {
	const CSGXFont *pPopFont = NULL;
	if (!m_pCurrentSurface) {
		return;
	}

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

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

	//
	if (m_pDefaultFont) {
		m_pCurrentSurface->setCurrentFont(pPopFont);
	}
}

void CSGXGUI::drawCursor(CSGXDrawSurface *pSurface) const {
	CSGXDrawSurface *pUseSurface = pSurface;

	if (pSurface == NULL) {
		pUseSurface = CSGXDrawSurfaceManager::get()->getDisplaySurface();
	} else {
		pUseSurface = m_pCurrentSurface;
	}
	//
	if (m_bPointerVisible && m_pCurrentSurface) {
		CSGXGraphicsEngine::get()->set2DMode();	

//		pUseSurface->resetRenderTransform();
		pUseSurface->setFillTexture(m_pPointerTexture);
		pUseSurface->setFillTextureRegion(m_PointerIsDown?m_iPointerRegionIn:m_iPointerRegionOut);
		pUseSurface->setFillColor(sgxColorRGB::White);
		pUseSurface->fillBox(m_vPosition.x, m_vPosition.y, m_vPosition.x+m_iPointerImageSize, m_vPosition.y+m_iPointerImageSize);
	}
}

void			
CSGXGUI::applyActiveCycleNext() {

	if (m_pFocus) {
		sgxWidget *pNext = m_pFocus->getNextSibling(TRUE);
		clearActive();
		setFocus(pNext);
	} else if (m_pRootWidget) {
		setFocus(m_pRootWidget->getChildWidget(0));
	}
}

void			
CSGXGUI::applyActiveCyclePrevious() {

	if (m_pFocus) {
		sgxWidget *pNext = m_pFocus->getPreviousSibling(TRUE);
		clearActive();
		setFocus(pNext);
	} else if (m_pRootWidget) {
		setFocus(m_pRootWidget->getChildWidget(0));
	}
}


void			
CSGXGUI::applyActiveCycleSelect() {
	if (m_pFocus) {
		m_pFocus->onActiveSelect();
	}
}


