/******************************************************************************

 @File         PVRShellOS.cpp

 @Title        Windows/PVRShellOS

 @Version      

 @Copyright    Copyright (c) Imagination Technologies Limited.

 @Platform     WinCE/Windows

 @Description  Makes programming for 3D APIs easier by wrapping window creation
               and other functions for use by a demo.

******************************************************************************/

/****************************************************************************
 ** INCLUDES                                                               **
 ****************************************************************************/
#include <windows.h>
#include <TCHAR.H>
#include <stdio.h>

#include "PVRShell.h"
#include "PVRShellAPI.h"
#include "PVRShellOS.h"
#include "PVRShellImpl.h"

// No Doxygen for CPP files, due to documentation duplication
/// @cond NO_DOXYGEN

#if !(WINVER >= 0x0500)
	#define COMPILE_MULTIMON_STUBS
	#include <multimon.h>
#endif

/****************************************************************************
	Defines
*****************************************************************************/
/*! The class name for the window */
#define WINDOW_CLASS _T("PVRShellClass")

/*! Maximum size to create string array for determining the read/write paths */
#define DIR_BUFFER_LEN	(10240)

/*! X dimension of the window that is created */
#define SHELL_DISPLAY_DIM_X	800
/*! Y dimension of the window that is created */
#define SHELL_DISPLAY_DIM_Y	600

/*****************************************************************************
	Declarations
*****************************************************************************/
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);

/*!***************************************************************************
	Class: PVRShellInit
*****************************************************************************/

/*!***********************************************************************
@Function		PVRShellOutputDebug
@Input			format			printf style format followed by arguments it requires
@Description	Writes the resultant string to the debug output (e.g. using
				printf(), OutputDebugString(), ...). Check the SDK release notes for
				details on how the string is output.
*************************************************************************/
void PVRShell::PVRShellOutputDebug(char const * const format, ...) const
{
	va_list arg;
	char	buf[1024];

	va_start(arg, format);
	vsnprintf(buf, 1024, format, arg);
	va_end(arg);

	// Passes the data to a platform dependant function
	m_pShellInit->OsDisplayDebugString(buf);
}

/*!***********************************************************************
 @Function		OsInit
 @description	Initialisation for OS-specific code.
*************************************************************************/
void PVRShellInit::OsInit()
{
	m_hAccelTable = 0;

	m_pShell->m_pShellData->nShellDimX = SHELL_DISPLAY_DIM_X;
	m_pShell->m_pShellData->nShellDimY = SHELL_DISPLAY_DIM_Y;

	m_hDC = 0;
	m_hWnd = 0;

	// Pixmap support: init variables to 0
	m_hBmPixmap = 0;
	m_hBmPixmapOld = 0;
	m_hDcPixmap = 0;

	/*
		Construct the binary path for GetReadPath() and GetWritePath()
	*/
	{
		/* Allocate memory for strings and return 0 if allocation failed */
		TCHAR* exeNameTCHAR = new TCHAR[DIR_BUFFER_LEN];
		char* exeName = new char[DIR_BUFFER_LEN];
		if(exeNameTCHAR && exeName)
		{
			DWORD retSize;

			/*
				Get the data path and a default application name
			*/

			// Get full path of executable
			retSize = GetModuleFileName(NULL, exeNameTCHAR, DIR_BUFFER_LEN);

			if (DIR_BUFFER_LEN > (int)retSize)
			{
				/* Get string length in char */
				retSize = (DWORD)_tcslen(exeNameTCHAR);

				/* Convert TChar to char */
				for (DWORD i = 0; i <= retSize; i++)
				{
					exeName[i] = (char)exeNameTCHAR[i];
				}

				SetAppName(exeName);
				SetReadPath(exeName);
				SetWritePath(exeName);
			}
		}

		delete [] exeName;
		delete [] exeNameTCHAR;
	}

	m_u32ButtonState = 0;	// clear mouse button state at startup
}

/*!***********************************************************************
 @Function		OsInitOS
 @description	Saves instance handle and creates main window
				In this function, we save the instance handle in a global variable and
				create and display the main program window.
*************************************************************************/
bool PVRShellInit::OsInitOS()
{
	MONITORINFO sMInfo;
	TCHAR		*appName;
	RECT		winRect;
	POINT		p;

	MyRegisterClass();

	/*
		Build the window title
	*/
	{
		const char		*pszName, *pszSeparator, *pszVersion;
		size_t			len;
		unsigned int	out, in;

		pszName			= (const char*)m_pShell->PVRShellGet(prefAppName);
		pszSeparator	= STR_WNDTITLE;
		pszVersion		= (const char*)m_pShell->PVRShellGet(prefVersion);

		len = strlen(pszName)+strlen(pszSeparator)+strlen(pszVersion)+1;
		appName = new TCHAR[len];

		for(out = 0; (appName[out] = pszName[out]) != 0; ++out);
		for(in = 0; (appName[out] = pszSeparator[in]) != 0; ++in, ++out);
		for(in = 0; (appName[out] = pszVersion[in]) != 0; ++in, ++out);
		_ASSERT(out == len-1);
	}

	/*
		Retrieve the monitor information.

		MonitorFromWindow() doesn't work, because the window hasn't been
		created yet.
	*/
	{
		HMONITOR	hMonitor;
		BOOL		bRet;

		p.x			= m_pShell->m_pShellData->nShellPosX;
		p.y			= m_pShell->m_pShellData->nShellPosY;
		hMonitor	= MonitorFromPoint(p, MONITOR_DEFAULTTOPRIMARY);
		sMInfo.cbSize = sizeof(sMInfo);
		bRet = GetMonitorInfo(hMonitor, &sMInfo);
		_ASSERT(bRet);
	}

	/*
		Reduce the window size until it fits on screen
	*/
	while(
		(m_pShell->m_pShellData->nShellDimX > (sMInfo.rcMonitor.right - sMInfo.rcMonitor.left)) ||
		(m_pShell->m_pShellData->nShellDimY > (sMInfo.rcMonitor.bottom - sMInfo.rcMonitor.top)))
	{
		m_pShell->m_pShellData->nShellDimX >>= 1;
		m_pShell->m_pShellData->nShellDimY >>= 1;
	}


	/*
		Create the window
	*/

	if(m_pShell->m_pShellData->bFullScreen)
	{
		m_hWnd = CreateWindow(WINDOW_CLASS, appName, WS_VISIBLE | WS_SYSMENU,CW_USEDEFAULT, CW_USEDEFAULT, m_pShell->m_pShellData->nShellDimX, m_pShell->m_pShellData->nShellDimY,
				NULL, NULL, m_hInstance, this);

		SetWindowLong(m_hWnd,GWL_STYLE,GetWindowLong(m_hWnd,GWL_STYLE) &~ WS_CAPTION);
		SetWindowPos(m_hWnd,HWND_NOTOPMOST,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
	}
	else
	{
		int x, y;

		SetRect(&winRect,
			m_pShell->m_pShellData->nShellPosX,
			m_pShell->m_pShellData->nShellPosY,
			m_pShell->m_pShellData->nShellPosX+m_pShell->m_pShellData->nShellDimX,
			m_pShell->m_pShellData->nShellPosY+m_pShell->m_pShellData->nShellDimY);
		AdjustWindowRectEx(&winRect, WS_CAPTION|WS_SYSMENU, false, 0);

		x = m_pShell->m_pShellData->nShellPosX - winRect.left;
		winRect.left += x;
		winRect.right += x;

		y = m_pShell->m_pShellData->nShellPosY - winRect.top;
		winRect.top += y;
		winRect.bottom += y;

		if(m_pShell->m_pShellData->bShellPosWasDefault)
		{
			x = CW_USEDEFAULT;
			y = CW_USEDEFAULT;
		}
		else
		{
			x = winRect.left;
			y = winRect.top;
		}

		m_hWnd = CreateWindow(WINDOW_CLASS, appName, WS_VISIBLE|WS_CAPTION|WS_SYSMENU,
			x, y, winRect.right-winRect.left, winRect.bottom-winRect.top, NULL, NULL, m_hInstance, this);

	}

	if(!m_hWnd)
		return false;

	if(m_pShell->m_pShellData->bFullScreen)
	{
		m_pShell->m_pShellData->nShellDimX = sMInfo.rcMonitor.right;
		m_pShell->m_pShellData->nShellDimY = sMInfo.rcMonitor.bottom;
		SetWindowPos(m_hWnd,HWND_TOPMOST,0,0,m_pShell->m_pShellData->nShellDimX,m_pShell->m_pShellData->nShellDimY,0);
	}

	m_hDC = GetDC(m_hWnd);
	ShowWindow(m_hWnd, m_nCmdShow);
	UpdateWindow(m_hWnd);

	delete [] appName;
	return true;
}

/*!***********************************************************************
 @Function		OsReleaseOS
 @description	Destroys main window
*************************************************************************/
void PVRShellInit::OsReleaseOS()
{
	ReleaseDC(m_hWnd, m_hDC);
	DestroyWindow(m_hWnd);
}

/*!***********************************************************************
 @Function		OsExit
 @description	Destroys main window
*************************************************************************/
void PVRShellInit::OsExit()
{
	const char	*szText;

	/*
		Show the exit message to the user
	*/
	szText		= (const char*)m_pShell->PVRShellGet(prefExitMessage);

	int			i, nT, nC;
	const char	*szCaption;
	TCHAR		*tzText, *tzCaption;

	szCaption	= (const char*)m_pShell->PVRShellGet(prefAppName);

	if(!szText || !szCaption)
		return;

	nT = (int)strlen(szText) + 1;
	nC = (int)strlen(szCaption) + 1;

	tzText = (TCHAR*)malloc(nT * sizeof(*tzText));
	tzCaption = (TCHAR*)malloc(nC * sizeof(*tzCaption));

	for(i = 0; (tzText[i] = szText[i]) != 0; ++i);
	for(i = 0; (tzCaption[i] = szCaption[i]) != 0; ++i);

	MessageBox(NULL, tzText, tzCaption, MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND);

	FREE(tzText);
	FREE(tzCaption);
}

/*!***********************************************************************
 @Function		OsDoInitAPI
 @Return		true on success
 @description	Perform API initialisation and bring up window / fullscreen
*************************************************************************/
bool PVRShellInit::OsDoInitAPI()
{

	// Pixmap support: create the pixmap
	if(m_pShell->m_pShellData->bNeedPixmap)
	{
		m_hDcPixmap = CreateCompatibleDC(m_hDC);
		m_hBmPixmap = CreateCompatibleBitmap(m_hDC, 640, 480);
	}

	if(!ApiInitAPI())
	{
		return false;
	}

	// Pixmap support: select the pixmap into a device context (DC) ready for blitting
	if(m_pShell->m_pShellData->bNeedPixmap)
	{
		m_hBmPixmapOld = (HBITMAP)SelectObject(m_hDcPixmap, m_hBmPixmap);
	}

	SetForegroundWindow(m_hWnd);

	/* No problem occured */
	return true;
}

/*!***********************************************************************
 @Function		OsDoReleaseAPI
 @description	Clean up after we're done
*************************************************************************/
void PVRShellInit::OsDoReleaseAPI()
{
	ApiReleaseAPI();

	if(m_pShell->m_pShellData->bNeedPixmap)
	{
		// Pixmap support: free the pixmap
		SelectObject(m_hDcPixmap, m_hBmPixmapOld);
		DeleteDC(m_hDcPixmap);
		DeleteObject(m_hBmPixmap);
	}
}

/*!***********************************************************************
 @Function		OsRenderComplete
 @Returns		false when the app should quit
 @description	Main message loop / render loop
*************************************************************************/
void PVRShellInit::OsRenderComplete()
{
	MSG		msg;

	/*
		Process the message queue
	*/
	while(PeekMessage(&msg, m_hWnd, 0, 0, PM_REMOVE))
	{
		if (!TranslateAccelerator(msg.hwnd, m_hAccelTable, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

}

/*!***********************************************************************
 @Function		OsPixmapCopy
 @Return		true if the copy succeeded
 @description	When using pixmaps, copy the render to the display
*************************************************************************/
bool PVRShellInit::OsPixmapCopy()
{
	return (BitBlt(m_hDC, 0, 0, 640, 480, m_hDcPixmap, 0, 0, SRCCOPY) == TRUE);
}

/*!***********************************************************************
 @Function		OsGetNativeDisplayType
 @Return		The 'NativeDisplayType' for EGL
 @description	Called from InitAPI() to get the NativeDisplayType
*************************************************************************/
void *PVRShellInit::OsGetNativeDisplayType()
{
	return m_hDC;
}

/*!***********************************************************************
 @Function		OsGetNativePixmapType
 @Return		The 'NativePixmapType' for EGL
 @description	Called from InitAPI() to get the NativePixmapType
*************************************************************************/
void *PVRShellInit::OsGetNativePixmapType()
{
	// Pixmap support: return the pixmap
	return m_hBmPixmap;
}

/*!***********************************************************************
 @Function		OsGetNativeWindowType
 @Return		The 'NativeWindowType' for EGL
 @description	Called from InitAPI() to get the NativeWindowType
*************************************************************************/
void *PVRShellInit::OsGetNativeWindowType()
{
	return m_hWnd;
}

/*!***********************************************************************
 @Function		OsGet
 @Input			prefName	Name of value to get
 @Modified		pn A pointer set to the value asked for
 @Returns		true on success
 @Description	Retrieves OS-specific data
*************************************************************************/
bool PVRShellInit::OsGet(const prefNameIntEnum prefName, int *pn)
{
	switch(prefName)
	{
	case prefButtonState:
		*pn = m_u32ButtonState;
		return true;
	};
	return false;
}

/*!***********************************************************************
 @Function		OsSet
 @Input			prefName				Name of preference to set to value
 @Input			value					Value
 @Return		true for success
 @Description	Sets OS-specific data
*************************************************************************/
bool PVRShellInit::OsSet(const prefNameBoolEnum prefName, const bool value)
{
	switch(prefName)
	{
	case prefShowCursor:
		ShowCursor(value ? TRUE : FALSE);
		return true;
	}

	return false;
}

/*!***********************************************************************
 @Function		OsSet
 @Input			prefName	Name of value to set
 @Input			i32Value 	The value to set our named value to
 @Returns		true on success
 @Description	Sets OS-specific data
*************************************************************************/
bool PVRShellInit::OsSet(const prefNameIntEnum prefName, const int i32Value)
{
	PVRSHELL_UNREFERENCED_PARAMETER(prefName);
	PVRSHELL_UNREFERENCED_PARAMETER(i32Value);
	return false;
}

/*!***********************************************************************
 @Function		OsGet
 @Input			prefName	Name of value to get
 @Modified		pp A pointer set to the value asked for
 @Returns		true on success
 @Description	Retrieves OS-specific data
*************************************************************************/
bool PVRShellInit::OsGet(const prefNamePtrEnum prefName, void **pp)
{
	switch(prefName)
	{
	case prefHINSTANCE:
		*pp = m_hInstance;
		return true;
	default:
		return false;
	}
}

/*!***********************************************************************
 @Function		OsDisplayDebugString
 @Input			str		string to output
 @Description	Prints a debug string
*************************************************************************/
void PVRShellInit::OsDisplayDebugString(char const * const str)
{
	if(str)
	{
#if defined(UNICODE)
		wchar_t	strc[1024];
		int		i;

		for(i = 0; (str[i] != '\0') && (i < (sizeof(strc) / sizeof(*strc))); ++i)
		{
			strc[i] = (wchar_t)str[i];
		}

		strc[i] = '\0';

		OutputDebugString(strc);
#else
		OutputDebugString(str);
#endif
	}
}

/*!***********************************************************************
 @Function		OsGetTime
 @Return		An incrementing time value measured in milliseconds
 @Description	Returns an incrementing time value measured in milliseconds
*************************************************************************/
unsigned long PVRShellInit::OsGetTime()
{
	return (unsigned long)GetTickCount();
}

/*****************************************************************************
 Class: PVRShellInitOS
*****************************************************************************/

/*!******************************************************************************************
@function		MyRegisterClass()
@description	Registers the window class.
				This function and its usage is only necessary if you want this code
				to be compatible with Win32 systems prior to the 'RegisterClassEx'
				function that was added to Windows 95. It is important to call this function
				so that the application will get 'well formed' small icons associated
				with it.
**********************************************************************************************/
ATOM PVRShellInitOS::MyRegisterClass()
{
	WNDCLASS wc;

    wc.style			= CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc		= (WNDPROC)WndProc;
    wc.cbClsExtra		= 0;
    wc.cbWndExtra		= 0;
    wc.hInstance		= m_hInstance;
    wc.hIcon			= LoadIcon(m_hInstance, _T("ICON"));
    wc.hCursor			= 0;
    wc.lpszMenuName		= 0;
	wc.hbrBackground	= (HBRUSH) GetStockObject(WHITE_BRUSH);
    wc.lpszClassName	= WINDOW_CLASS;

	return RegisterClass(&wc);
}

/*****************************************************************************
 Global code
*****************************************************************************/
void doButtonDown(HWND hWnd, PVRShellInit *pData, EPVRShellButtonState eButton, LPARAM lParam)
{
	RECT rcWinDimensions;
	GetClientRect(hWnd,&rcWinDimensions);
	float vec2TouchPosition[2] = { (float)(short)LOWORD(lParam)/(float)(rcWinDimensions.right), (float)(short)HIWORD(lParam)/(float)(rcWinDimensions.bottom) };
	pData->TouchBegan(vec2TouchPosition);
	SetCapture(hWnd);	// must be within window so capture
	pData->m_u32ButtonState |= eButton;
}

bool doButtonUp(HWND hWnd, PVRShellInit *pData, EPVRShellButtonState eButton, LPARAM lParam)
{
	RECT rcWinDimensions;
	GetClientRect(hWnd,&rcWinDimensions);
	float vec2TouchPosition[2] = { (float)(short)LOWORD(lParam)/(float)(rcWinDimensions.right), (float)(short)HIWORD(lParam)/(float)(rcWinDimensions.bottom) };
	pData->TouchEnded(vec2TouchPosition);
	pData->m_u32ButtonState &= (~eButton);

	if(vec2TouchPosition[0] < 0.f || vec2TouchPosition[0] > 1.f || vec2TouchPosition[1] < 0.f || vec2TouchPosition[1] > 1.f)
	{	// pointer has left window
		if(pData->m_u32ButtonState==0)
		{	// only release capture if mouse buttons have been released
			ReleaseCapture();
		}

		return false;
	}
	return true;
}

/*!***************************************************************************
@function		WndProc
@input			hWnd		Handle to the window
@input			message		Specifies the message
@input			wParam		Additional message information
@input			lParam		Additional message information
@returns		result code to OS
@description	Processes messages for the main window.
*****************************************************************************/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PVRShellInit *pData = (PVRShellInit*) GetWindowLongPtr(hWnd, GWLP_USERDATA);

	switch (message)
	{
	case WM_CREATE:
		{
			CREATESTRUCT	*pCreate = (CREATESTRUCT*)lParam;
			SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pCreate->lpCreateParams);
			break;
		}
	case WM_PAINT:
		break;
	case WM_DESTROY:
		return 0;
	case WM_CLOSE:
		pData->gShellDone = true;
		return 0;
	case WM_QUIT:
		return 0;
	case WM_MOVE:
		pData->m_pShell->PVRShellSet(prefPositionX, (int)LOWORD(lParam));
		pData->m_pShell->PVRShellSet(prefPositionY, (int)HIWORD(lParam)); 
		break;
	case WM_LBUTTONDOWN:
		{
			doButtonDown(hWnd,pData,ePVRShellButtonLeft,lParam);
			break;
		}
	case WM_LBUTTONUP:
		{
			if(!doButtonUp(hWnd,pData,ePVRShellButtonLeft,lParam))
				return false;
		break;
		}
	case WM_RBUTTONDOWN:
		{
			doButtonDown(hWnd,pData,ePVRShellButtonRight,lParam);
			break;
		}
	case WM_RBUTTONUP:
		{
			if(!doButtonUp(hWnd,pData,ePVRShellButtonRight,lParam))
				return false;
			break;
		}
	case WM_MBUTTONDOWN:
		{
			doButtonDown(hWnd,pData,ePVRShellButtonMiddle,lParam);
			break;
		}
	case WM_MBUTTONUP:
		{
			if(!doButtonUp(hWnd,pData,ePVRShellButtonMiddle,lParam))
				return false;
			break;
		}
	case WM_MOUSEMOVE:
		{
			RECT rcWinDimensions;
			GetClientRect(hWnd,&rcWinDimensions);
			float vec2TouchPosition[2] = { (float)(short)LOWORD(lParam)/(float)(rcWinDimensions.right), (float)(short)HIWORD(lParam)/(float)(rcWinDimensions.bottom) };
			
			if(vec2TouchPosition[0] < 0.f || vec2TouchPosition[0] > 1.f || vec2TouchPosition[1] < 0.f || vec2TouchPosition[1] > 1.f)
			{	
				// pointer has left window
				if(pData->m_u32ButtonState==0)
				{	// only release capture if mouse buttons have been released
					ReleaseCapture();
				}

				pData->TouchEnded(vec2TouchPosition);
				return false;
			}
			else
			{	// pointer is inside window
				pData->TouchMoved(vec2TouchPosition);
			}
			break;
		}
	case WM_SETFOCUS:
		pData->m_bHaveFocus = true;
		return 0;
	case WM_KILLFOCUS:
		pData->m_bHaveFocus = false;
		return 0;
	case WM_KEYDOWN:
	{
		switch(wParam)
		{
		case VK_ESCAPE:
		case 0xC1:
			pData->KeyPressed(PVRShellKeyNameQUIT);
			break;
		case VK_UP:
		case 0x35:
			pData->KeyPressed(pData->m_eKeyMapUP);
			break;
		case VK_DOWN:
		case 0x30:
			pData->KeyPressed(pData->m_eKeyMapDOWN);
			break;
		case VK_LEFT:
		case 0x37:
			pData->KeyPressed(pData->m_eKeyMapLEFT);
			break;
		case VK_RIGHT:
		case 0x39:
			pData->KeyPressed(pData->m_eKeyMapRIGHT);
			break;
		case VK_SPACE:
		case 0x38:
			pData->KeyPressed(PVRShellKeyNameSELECT);
			break;
		case '1':
		case 0x34:
			pData->KeyPressed(PVRShellKeyNameACTION1);
			break;
		case '2':
		case 0x36:
			pData->KeyPressed(PVRShellKeyNameACTION2);
			break;
		case VK_F11:
		case 0xC2:
			pData->KeyPressed(PVRShellKeyNameScreenshot);
			break;
		}
	}
	default:
		break;
	}

	return DefWindowProc(hWnd, message, wParam, lParam);
}

/*!***************************************************************************
@function		WinMain
@input			hInstance		Application instance from OS
@input			hPrevInstance	Always NULL
@input			lpCmdLine		command line from OS
@input			nCmdShow		Specifies how the window is to be shown
@returns		result code to OS
@description	Main function of the program
*****************************************************************************/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, TCHAR *lpCmdLine, int nCmdShow)
{
	size_t			i;
	char			*pszCmdLine;
	PVRShellInit	init;

	PVRSHELL_UNREFERENCED_PARAMETER(hPrevInstance);

#if defined(_WIN32)
	// Enable memory-leak reports
	_CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG));
#endif

	// Get a char-array command line as the input may be UNICODE
	i = _tcslen(lpCmdLine) + 1;
	pszCmdLine = new char[i];

	while(i)
	{
		--i;
		pszCmdLine[i] = (char)lpCmdLine[i];
	}

	//	Init the demo, process the command line, create the OS initialiser.
	if(!init.Init())
	{
		delete[] pszCmdLine;
		return EXIT_ERR_CODE;
	}

	init.CommandLine(pszCmdLine);
	init.m_hInstance = hInstance;
	init.m_nCmdShow = nCmdShow;

	//	Initialise/run/shutdown
	while(init.Run());

	delete[] pszCmdLine;

	return EXIT_NOERR_CODE;
}

/// @endcond

/*****************************************************************************
 End of file (PVRShellOS.cpp)
*****************************************************************************/

