//------------------------------------------------------------------------------
// File : SkyContext.cpp
//------------------------------------------------------------------------------
// SkyWorks : Copyright 2002 Mark J. Harris and
//						The University of North Carolina at Chapel Hill
//------------------------------------------------------------------------------
// Permission to use, copy, modify, distribute and sell this software and its 
// documentation for any purpose is hereby granted without fee, provided that 
// the above copyright notice appear in all copies and that both that copyright 
// notice and this permission notice appear in supporting documentation. 
// Binaries may be compiled with this software without any royalties or 
// restrictions. 
//
// The author(s) and The University of North Carolina at Chapel Hill make no 
// representations about the suitability of this software for any purpose. 
// It is provided "as is" without express or implied warranty.
/**
 * @file SkyContext.cpp
 * 
 * Graphics Context Interface.  Initializes GL extensions, etc.
 */

#ifdef HAVE_CONFIG_H
#  include <simgear_config.h>
#endif

#include <GL/glut.h>
#ifndef WIN32
#include <GL/glx.h>
#endif

//#include "extgl.h"

#include "SkyContext.hpp"
#include "SkyUtil.hpp"
#include "SkyMaterial.hpp"
#include "SkyTextureState.hpp"

//------------------------------------------------------------------------------
// Function     	  : SkyContext::SkyContext
// Description	    : 
//------------------------------------------------------------------------------
/**
 * @fn SkyContext::SkyContext()
 * @brief Constructor.
 *
 */ 
SkyContext::SkyContext()
{
  _iWidth  = glutGet(GLUT_WINDOW_WIDTH);
  _iHeight = glutGet(GLUT_WINDOW_HEIGHT);
  
  // materials and structure classes
  AddCurrentGLContext();
  // Initialize all the extensions and load the functions - JW (file is extgl.c)
  #ifdef WIN32
  glInitialize();
  InitializeExtension("GL_ARB_multitexture");
  #endif
}


//------------------------------------------------------------------------------
// Function     	  : SkyContext::~SkyContext
// Description	    : 
//------------------------------------------------------------------------------
/**
 * @fn SkyContext::~SkyContext()
 * @brief Destructor.
 */ 
SkyContext::~SkyContext()
{
  // delete map of materials
  for (ContextMaterialIterator cmi = _currentMaterials.begin(); cmi != _currentMaterials.end(); ++cmi)
  {
    SAFE_DELETE(cmi->second);
  }
  _currentMaterials.clear();
}


//------------------------------------------------------------------------------
// Function     	  : SkyContext::ProcessReshapeEvent
// Description	    : 
//------------------------------------------------------------------------------
/**
 * @fn SkyContext::ProcessReshapeEvent(int iWidth, int iHeight)
 * @brief Handles window resize events, and notifies all context listeners of the event.
 */ 
SKYRESULT SkyContext::ProcessReshapeEvent(int iWidth, int iHeight)
{ 
  _iWidth = iWidth; 
  _iHeight = iHeight; 
  return _SendMessage(SKYCONTEXT_MESSAGE_RESHAPE);
}


//------------------------------------------------------------------------------
// Function     	  : SkyContext::InitializeExtensions
// Description	    : 
//------------------------------------------------------------------------------
/**
* @fn SkyContext::InitializeExtensions(const char *pExtensionNames)
* @brief Initializes GL extension specified by @a pExtensionNames.      
*/ 
SKYRESULT SkyContext::InitializeExtension(const char *pExtensionName)
{ /***
  if (!QueryExtension(pExtensionName)) // see query search function defined in extgl.c
  {
    SkyTrace(
      "ERROR: SkyContext::InitializeExtenstions: The following extensions are unsupported: %s\n", 
      pExtensionName);
    return SKYRESULT_FAIL;
  }  **/
  //set this false to catch all the extensions until we come up with a linux version
  return SKYRESULT_FAIL;
}


//------------------------------------------------------------------------------
// Function     	  : SkyContext::GetCurrentMaterial
// Description	    : 
//------------------------------------------------------------------------------
/**
* @fn SkyContext::GetCurrentMaterial()
* @brief Returns the current cached material state that is active in this OpenGL context.
* 
* @todo <WRITE EXTENDED SkyContext::GetCurrentMaterial FUNCTION DOCUMENTATION>
*/ 
SkyMaterial* SkyContext::GetCurrentMaterial()
{
#ifdef WIN32
    ContextMaterialIterator cmi = _currentMaterials.find(wglGetCurrentContext());
#else
    ContextMaterialIterator cmi = _currentMaterials.find(glXGetCurrentContext());
#endif
  if (_currentMaterials.end() != cmi)
    return cmi->second;
  else
  {
    SkyTrace("SkyContext::GetCurrentMaterial(): Invalid context.");
    return NULL;
  }
  
}


//------------------------------------------------------------------------------
// Function     	  : SkyContext::GetCurrentTextureState
// Description	    : 
//------------------------------------------------------------------------------
/**
 * @fn SkyContext::GetCurrentTextureState()
 * @brief Returns the current cached texture state that is active in this OpenGL context.
 * 
 * @todo <WRITE EXTENDED SkyContext::GetCurrentTextureState FUNCTION DOCUMENTATION>
 */ 
SkyTextureState* SkyContext::GetCurrentTextureState()
{
#ifdef WIN32
    ContextTextureStateIterator ctsi = _currentTextureState.find(wglGetCurrentContext());
#else
    ContextTextureStateIterator ctsi = _currentTextureState.find(glXGetCurrentContext());
#endif
  if (_currentTextureState.end() != ctsi)
    return ctsi->second;
  else
  {
    SkyTrace("SkyContext::GetCurrentTextureState(): Invalid context.");
    return NULL;
  }
}


//------------------------------------------------------------------------------
// Function     	  : SkyContext::AddCurrentGLContext
// Description	    : 
//------------------------------------------------------------------------------
/**
 * @fn SkyContext::AddCurrentGLContext()
 * @brief @todo <WRITE BRIEF SkyContext::AddCurrentGLContext DOCUMENTATION>
 * 
 * @todo <WRITE EXTENDED SkyContext::AddCurrentGLContext FUNCTION DOCUMENTATION>
 */ 
SKYRESULT SkyContext::AddCurrentGLContext()
{
    SkyMaterial *pCurrentMaterial = new SkyMaterial;
#ifdef WIN32
    _currentMaterials.insert(std::make_pair(wglGetCurrentContext(), pCurrentMaterial));
#else
    _currentMaterials.insert(std::make_pair(glXGetCurrentContext(), pCurrentMaterial));
#endif

    SkyTextureState *pCurrentTS = new SkyTextureState;
#ifdef WIN32
    _currentTextureState.insert(std::make_pair(wglGetCurrentContext() , pCurrentTS));
#else
    _currentTextureState.insert(std::make_pair(glXGetCurrentContext() , pCurrentTS));
#endif
    return SKYRESULT_OK;
}

//------------------------------------------------------------------------------
// Function     	  : SkyContext::Register
// Description	    : 
//------------------------------------------------------------------------------
/**
 * @fn SkyContext::Register(Listener* pListener, int priority)
 * @brief Register with the messaging system to handle notification of mode changes
 */ 
SKYRESULT SkyContext::Register(Listener* pListener, int priority)
{
	std::list<ListenerPair>::iterator iter = 
    std::find_if(_listeners.begin(), _listeners.end(), _ListenerPred(pListener));
	if (iter == _listeners.end())
	{
		// insert the listener, sorted by priority
		for (iter=_listeners.begin(); iter != _listeners.end(); ++iter)
		{
			if (priority <= iter->first)
			{
				_listeners.insert(iter, ListenerPair(priority, pListener));
				break;
			}
		}
		if (iter == _listeners.end())
		{
				_listeners.push_back(ListenerPair(priority, pListener));
		}
		// Send a message to the pListener if we are already active so it
		// can intialize itself
		//FAIL_RETURN(pListener->GraphicsReshapeEvent());
	} 
  else
	{
		FAIL_RETURN_MSG(SKYRESULT_FAIL, "SkyContext: Listener is already registered");
	}
	return SKYRESULT_OK;
}


//------------------------------------------------------------------------------
// Function     	  : SkyContext::UnRegister
// Description	    : 
//------------------------------------------------------------------------------
/**
 * @fn SkyContext::UnRegister(Listener *pListener)
 * @brief UnRegister with the messaging system.
 */ 
SKYRESULT SkyContext::UnRegister(Listener *pListener)
{
	std::list<ListenerPair>::iterator iter = 
    std::find_if(_listeners.begin(), _listeners.end(), _ListenerPred(pListener));
	if (iter == _listeners.end())
	{
		FAIL_RETURN_MSG(SKYRESULT_FAIL, "SkyContext: Listener is not registered");
	} 
  else
	{
		_listeners.erase(iter);
	}
	return SKYRESULT_OK;
}


/**
 * @fn SkyContext::_SendMessage(SkyMessageType msg)
 * @brief Messaging system to handle notification of mode changes
 */ 
SKYRESULT SkyContext::_SendMessage(SkyMessageType msg)
{
	if (_listeners.size() == 0) return SKYRESULT_OK;

	bool failure = false;
	SKYRESULT res, failureCode = SKYRESULT_OK;
	std::list<ListenerPair>::iterator iter;
	SKYRESULT (Listener::*fnPtr)() = NULL;

	// Make a pointer to the appropriate method
	switch (msg)
	{
	case SKYCONTEXT_MESSAGE_RESHAPE: fnPtr = &Listener::GraphicsReshapeEvent; break;
	}

	// Notify all listeners of the messag. catch failures, but still call everyone else.
	// !!! WRH HORRIBLE HACK must cache the current "end" because these functions could register new listeners
	std::list<ListenerPair>::iterator endIter = _listeners.end();
	endIter--;

	iter = _listeners.begin();
	do
	{
		if ( SKYFAILED( res = (iter->second->*fnPtr)() ) )
		{
			failureCode = res;
			SkyTrace("SkyContext: SendMessage failed");
		}
		if (iter == endIter) break;
		iter++;
	} while (true);

	FAIL_RETURN(failureCode);

	return SKYRESULT_OK;
}
