
///////////////////////////////////////////////////////////////////////////////
//
// File   : Asura_Platform_Assert.cpp
// Author : Kevin Floyer-Lea
// Date   : 27-Jun-2005
//
// Copyright (C) 2005 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Include files...
////////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"
#include "Core/Asura_Core.h"

#if defined(ASURA_ASSERT)

// This file's header...
#include "Debugging/Asura_Assert.h"

// Asura header-files...
#include "Core/Asura_UserInput.h"
#include "Core/Asura_String.h"
#include "Core/Asura_Multithread.h"
#include "Debugging/Asura_CPUProfile_Renderer.h"
#include "Debugging/Asura_Logfile.h"
#include "Rendering/Asura_Render.h"

#include "Rendering/Asura_Xbox360_Graphics.h"

#ifdef ASURA_XBOX360_DEBUG_TEXT_ASSERTS
#include "Core/Memory/Asura_Mem.h"
#ifdef ASURA_USE_DEBUGOVERLAY
#include "Debugging/Asura_DebugOverlay.h"
#endif
#include "Debugging/Asura_DebugText.h"
#ifdef ASURA_USE_PROFILING_SYSTEM_MULTITHREADED
#include "Debugging/Asura_CPUProfile_MultiThreaded_Render.h"
#endif
#include "Input/Asura_PlatformInput.h"
#include "Rendering/Asura_Viewport.h"
#endif

////////////////////////////////////////////////////////////////////////////////
// Constant declarations...
////////////////////////////////////////////////////////////////////////////////

#define IDIGNOREONCE            0
#define IDIGNOREALL             1
#define IDRETRY					2

#if defined ASURA_ALLOW_DISABLE_ASSERTS
	#define IDDISABLEASSERTS    3
#endif

#ifdef ASURA_XBOX360_DEBUG_TEXT_ASSERTS
#define INVALID_ASSERT_BUTTON	(999)
#define MAX_ASSERT_ERROR_LINES	(16)
#define MAX_ERROR_LINE_LENGTH	(128)
#endif

////////////////////////////////////////////////////////////////////////////////
// Static variable declarations...
////////////////////////////////////////////////////////////////////////////////

static bool		g_bIgnoreAllAsserts				= false;

static bool		g_bFirstTimeAssertFired			= true;

#if defined(ASURA_LOGFILES_ENABLED)
static const char*	ASSERTION_LOGFILE = "logfiles\\Assert!.txt";
#endif	// defined(ASURA_LOGFILES_ENABLED)

// (MH - 01.11.2002) paramaterised assert
static const char*  g_szAsuraAssertfFile;
static int          g_iAsuraAssertfLine;
static const char*  g_szAsuraAssertfCondition;
static u_char*      g_pucAsuraAssertfIgnore;
static const int    ASURAASSERTF_OUTPUT_BUFFER_SIZE = 2048;
static char         g_szOutputBuffer[ASURAASSERTF_OUTPUT_BUFFER_SIZE];
static Asura_Mutex	g_xAssertMutex;

////////////////////////////////////////////////////////////////////////////////
// Private functions...
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// Protected functions...
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// Public functions...
////////////////////////////////////////////////////////////////////////////////

#ifdef ASURA_XBOX360_DEBUG_TEXT_ASSERTS

static int ShowMessageBoxDebugText( const Asura_Char* szTitleString, const Asura_Char* szErrorString )
{
	if( Asura_Render::IsInProtectedRenderCode() )
	{
		//Asserting from within BeginFrame/Endframe raises potential issues, so we just break instead
		Asura_OutputToDebugger_Retail(("\n!!! Asserted from BeginFrame or EndFrame !!!\n"));
		#ifdef _DEBUG
		Asura_OutputToDebugger_Retail(("Cannot show assert dialog; breaking to debugger\n"));
		return IDRETRY;
		#else
		Asura_OutputToDebugger_Retail(("Cannot show assert dialog; ignoring assert\n"));
		return IDIGNOREONCE;
		#endif
	}

	bool bWasSuspended = false;
	if( Asura_Xbox360_Graphics::IsDeviceSuspended() )
	{
		bWasSuspended = true;
		Asura_Xbox360_Graphics::ResumeDevice();
	}

	//First Prevent anything from rendering
	Asura_Xbox360_Graphics::ExclusiveThreadOwnership();

	#ifdef ASURA_USE_PROFILING_SYSTEM
	Asura_CPUProfile_Renderer::SetPaused(true);
	#endif

	#ifdef ASURA_USE_DEBUGOVERLAY
	bool bDebugOverlayWasActive = Asura_DebugOverlay::IsActive();
	if(bDebugOverlayWasActive)
	{
		Asura_DebugOverlay::SetActive(false);
	}
	#endif

	//Remember if this thread was between BeginFrame/Endframe (if it was called during we are a bit screwed)
	bool bWasRendering = Asura_Xbox360_Graphics::HasThreadOwnership();
	bool bWasTiling = Asura_Xbox360_Graphics::IsTiling();
	if(bWasRendering)
	{
		Asura_Render::EndFrame();
	}

	Asura_Xbox360_Graphics::AcquireThreadOwnership();

	#ifdef ASURA_XBOX360_ALLOW_STATS
	bool bStatsEnabled = Asura_Xbox360_Graphics::AreStatsEnabled();
	Asura_Xbox360_Graphics::EnableStats(false);
	#endif

	//Some magic numbers here but its a bit unavoidable
	const u_int uTextOffsetX = 10;
	const u_int uTextOffsetY = 5;

	const u_int uTitlePosX = 0;
	const u_int uTitlePosY = 0;

	const u_int uErrorPosX = 0;
	const u_int uErrorPosY = 4;

	const u_int uMaxCharsWide = static_cast<u_int>(Asura_Viewport::GetWidth() / ASURA_DEBUGTEXT_OUTPUT_FONT_WIDTH);
	const u_int uMaxErrorWidth = Asura_Maths::Min(static_cast<u_int>(uMaxCharsWide - 48),static_cast<u_int>(MAX_ERROR_LINE_LENGTH));

	const u_int uButtonPosX = uMaxErrorWidth + uTextOffsetX + 4;
	const u_int uButtonPosY = 6;

	char aszErrorText[MAX_ASSERT_ERROR_LINES][MAX_ERROR_LINE_LENGTH];
	Asura_Mem::Set(aszErrorText,0,sizeof(char) * MAX_ASSERT_ERROR_LINES * MAX_ERROR_LINE_LENGTH);
	//Do some manual text wrapping
	u_int uCharPos = 0;
	u_int uLine = 0;
	u_int uLinePos = 0;
	
	while((uLine < MAX_ASSERT_ERROR_LINES) && szErrorString[uCharPos])
	{
		uLinePos = 0;

		while( (uLinePos < uMaxErrorWidth) && szErrorString[uCharPos] )
		{
			if( (szErrorString[uCharPos] == '\n') )
			{
				if(uLinePos == 0)
				{
					//Dont allow a totally blank line or we will think the error text is finished when renderign
					aszErrorText[uLine][uLinePos] = ' ';
				}
				//Manual cr/lf
				++ uCharPos;
				break;
			}
			else
			{
				aszErrorText[uLine][uLinePos] = szErrorString[uCharPos];
				++ uLinePos;
				++ uCharPos;
			}
		}
		++ uLine;
	}
	
	int iFlags = ASURA_DEBUGTEXT_FORMAT_LEFTJUSTIFIED;

	u_int uSelectedButton = IDIGNOREALL;
	u_int uPressedButton = INVALID_ASSERT_BUTTON;

	while( ( uPressedButton == INVALID_ASSERT_BUTTON ) || Asura_UserInput::HasAnyKeyBeenPressed() )
	{
		Asura_Render::BeginFrame();

		Asura_UserInput::Update();

		if(Asura_UserInput::DebouncedKeyPressed(ASURA_XBOX360_KEYID_PAD1_DOWN))
		{
			#ifdef ASURA_ALLOW_DISABLE_ASSERTS
			uSelectedButton	= Asura_Maths::Min(static_cast<u_int>(uSelectedButton + 1),static_cast<u_int>(IDDISABLEASSERTS));
			#else
			uSelectedButton	= Asura_Maths::Min(static_cast<u_int>(uSelectedButton + 1),static_cast<u_int>(IDRETRY));
			#endif
		}
		if(Asura_UserInput::DebouncedKeyPressed(ASURA_XBOX360_KEYID_PAD1_UP) && (uSelectedButton > IDIGNOREONCE))
		{
			-- uSelectedButton;
		}
		if(Asura_UserInput::DebouncedKeyPressed(ASURA_XBOX360_KEYID_PAD1_A))
		{
			uPressedButton = uSelectedButton;
		}


		Asura_DebugText::AddTextBlock(uTitlePosX + uTextOffsetX, uTitlePosY + uTextOffsetY, 0xFFFFFFFF, iFlags, szTitleString);
		Asura_DebugText::AddTextBlock(uTitlePosX + uTextOffsetX, uTitlePosY + uTextOffsetY + 1, 0xFFFFFFFF, iFlags, "__________________________");
		
		for(u_int u = 0; u < MAX_ASSERT_ERROR_LINES; ++u)
		{
			if(!aszErrorText[u][0])
			{
				//no more error text
				break;
			}
			Asura_DebugText::AddTextBlock(uErrorPosX + uTextOffsetX, uErrorPosY + uTextOffsetY + u, 0xFFDDDDDD, iFlags, aszErrorText[u]);
		}

		if(uSelectedButton == IDIGNOREONCE)
		{
			Asura_DebugText::AddTextBlock(uButtonPosX, uButtonPosY, 0xFFFFFFFF, iFlags, ">>  Ignore (once)  <<");
		}
		else
		{
			Asura_DebugText::AddTextBlock(uButtonPosX, uButtonPosY, 0xFFAAAAAA, iFlags, "    Ignore (once)");
		}

		if(uSelectedButton == IDIGNOREALL)
		{
			Asura_DebugText::AddTextBlock(uButtonPosX, uButtonPosY + 2, 0xFFFFFFFF, iFlags, ">> Ignore (always) <<");
		}
		else
		{
			Asura_DebugText::AddTextBlock(uButtonPosX, uButtonPosY + 2, 0xFFAAAAAA, iFlags, "   Ignore (always)");
		}

		if(uSelectedButton == IDRETRY)
		{
			Asura_DebugText::AddTextBlock(uButtonPosX, uButtonPosY + 4, 0xFFFFFFFF, iFlags, ">>      Break      <<");
		}
		else
		{
			Asura_DebugText::AddTextBlock(uButtonPosX, uButtonPosY + 4, 0xFFAAAAAA, iFlags, "        Break");
		}

		#ifdef ASURA_ALLOW_DISABLE_ASSERTS
		if(uSelectedButton == IDDISABLEASSERTS)
		{
			Asura_DebugText::AddTextBlock(uButtonPosX, uButtonPosY + 8, 0xFFFFFFFF, iFlags, ">> Disable Asserts <<");
		}
		else
		{
			Asura_DebugText::AddTextBlock(uButtonPosX, uButtonPosY + 8, 0xFFAAAAAA, iFlags, "   Disable Asserts");
		}
		#endif

		Asura_Render::EndFrame();
	}

	//Do an extra frame of rendering to clear the screen
	Asura_Render::BeginFrame();
	Asura_Render::EndFrame();

	#ifdef ASURA_XBOX360_ALLOW_STATS
	if(bStatsEnabled)
	{
		Asura_Xbox360_Graphics::EnableStats(true);
	}
	#endif

	#ifdef ASURA_USE_DEBUGOVERLAY
	if(bDebugOverlayWasActive)
	{
		Asura_DebugOverlay::SetActive(true);
	}
	#endif

	#ifdef ASURA_USE_PROFILING_SYSTEM
	Asura_CPUProfile_Renderer::SetPaused(false);
	#endif
	
	if(bWasRendering)
	{
		Asura_Render::BeginFrame();

		if(!bWasTiling)
		{
			Asura_Xbox360_Graphics::EndTiling();

			Direct3D_Device* const pxDevice = Asura_Xbox360_Graphics::GetDevice();

			pxDevice->SetRenderTarget( 0, Asura_Xbox360_Graphics::GetFinalRT() );
			pxDevice->SetRenderTarget( 1, NULL );
			pxDevice->SetRenderTarget( 2, NULL );
			pxDevice->SetRenderTarget( 3, NULL );

			pxDevice->SetDepthStencilSurface( Asura_Xbox360_Graphics::GetFinalDepth() );
		}
	}

	Asura_Xbox360_Graphics::ReleaseExclusiveThreadOwnership();

	if( bWasSuspended )
	{
		Asura_Xbox360_Graphics::AcquireThreadOwnership();
		Asura_Xbox360_Graphics::SuspendDevice();
	}

	return uPressedButton;
}

#else

static int ShowMessageBox( const Asura_UnicodeChar* wszTitleString, const Asura_UnicodeChar* wszErrorString )
{
    DWORD dwRet;
	XOVERLAPPED m_Overlapped;
	MESSAGEBOX_RESULT   m_Result;

	LPCWSTR g_pwstrButtons[] =
	{
		L"Ignore(once)",
		L"Ignore(all)",
		L"Break",
	};

    ZeroMemory( &m_Overlapped, sizeof( XOVERLAPPED ) ); 

    dwRet = XShowMessageBoxUI( NULL,
                wszTitleString,                   // Message box title
                wszErrorString,  // Message string
                ARRAYSIZE( g_pwstrButtons ),// Number of buttons
                g_pwstrButtons,             // Button captions
                1,                          // Button that gets focus
                XMB_ERRORICON,              // Icon to display
                &m_Result,                  // Button pressed result
                &m_Overlapped );


	bool bWasTiling = Asura_Xbox360_Graphics::IsTiling();
	bool bWasTiling2 = Asura_Xbox360_Graphics::IsTiling2();

	if ( bWasTiling )
	{
		Asura_Xbox360_Graphics::EndTiling();
	}
	if ( bWasTiling2 )
	{
		Asura_Xbox360_Graphics::EndTiling2();
	}

	while( !XHasOverlappedIoCompleted( &m_Overlapped ) )
    {
		Sleep(10);
		Asura_Xbox360_Graphics::PresentFrame();
	}
	{
        DWORD dwResult = XGetOverlappedResult( &m_Overlapped, NULL, TRUE );
     
		for (int i=0; i<50; ++i)
		{
			Asura_Xbox360_Graphics::PresentFrame();
			Sleep(10);
		}

		if (  bWasTiling )
		{
			Asura_Xbox360_Graphics::BeginTiling();
		}
		if (  bWasTiling2 )
		{
			Asura_Xbox360_Graphics::BeginTiling2();
		}

		if( dwResult == ERROR_SUCCESS )
        {
            return m_Result.dwButtonPressed;
        }
        else
        {
#if defined ASURA_ALLOW_DISABLE_ASSERTS
			LPCWSTR g_pwstrButtons2[] =
			{
				L"No",
				L"Yes",
			};

			ZeroMemory( &m_Overlapped, sizeof( XOVERLAPPED ) ); 

			dwRet = XShowMessageBoxUI( NULL,
				wszTitleString,                   // Message box title
				L"Do you wish to disable ALL Asserts?",  // Message string
				ARRAYSIZE( g_pwstrButtons2 ),// Number of buttons
				g_pwstrButtons2,             // Button captions
				0,                          // Button that gets focus
				XMB_ERRORICON,              // Icon to display
				&m_Result,                  // Button pressed result
				&m_Overlapped );

			if ( Asura_Xbox360_Graphics::IsTiling() )
			{
				Asura_Xbox360_Graphics::EndTiling();
			}

			while( !XHasOverlappedIoCompleted( &m_Overlapped ) )
			{
				Sleep(10);
				Asura_Xbox360_Graphics::PresentFrame();
			}
			{
				DWORD dwResult = XGetOverlappedResult( &m_Overlapped, NULL, TRUE );

				for (int i=0; i<50; ++i)
				{
					Asura_Xbox360_Graphics::PresentFrame();
					Sleep(10);
				}

				if (  bWasTiling )
				{
					Asura_Xbox360_Graphics::BeginTiling();
				}
				if( dwResult == ERROR_SUCCESS )
				{
					return m_Result.dwButtonPressed == 1 ? IDDISABLEASSERTS : IDIGNOREONCE;
				}
				else
				{
					return IDIGNOREONCE;
				}
			}
#else
			return IDIGNOREONCE;
#endif
		}
    }
}

#endif

bool Asura_AssertFired(const char* file, int line, const char* condition, const char* desc, u_char* pucIgnore)
{
	static bool s_bAssertFired = false;

	if (s_bAssertFired)
	{		
		// DMR 05/02/07 - Handle recursive asserts - if displaying this assert causes another,
		// we could get a nasty stack overflow crash - so ignore the new assert.
		Asura_OutputToDebugger_Retail(("\n--------------------------------------------------------------------------------\n"));
		Asura_OutputToDebugger_Retail(("\n(WARNING - Recursive assert!... assertion failed within assert system...)\n"));
		Asura_OutputToDebugger_Retail(("%s(%d) : Assertion\n(%s) failed: %s\n", file, line, condition, desc));
		Asura_OutputToDebugger_Retail(("--------------------------------------------------------------------------------\n\n"));

		return false;
	}

	//Lock our mutex so that if we are asserting from another thread it will wait for the current assert to complete
	g_xAssertMutex.Lock();

	s_bAssertFired = true;

	AsuraAssert_LogClearLog();
    Asura_SimpleLogfileOutput((ASSERTION_LOGFILE, "Assertion (%s) failed: %s.\nFile %s, Line %d\n\n", condition, desc, file, line));

	Asura_OutputToDebugger_Retail(("\n--------------------------------------------------------------------------------\n"));
	Asura_OutputToDebugger_Retail(("%s(%d) : Assertion\n(%s) failed: %s\n", file, line, condition, desc));
	Asura_OutputToDebugger_Retail(("--------------------------------------------------------------------------------\n\n"));
	
	int iValue;
	if( g_bIgnoreAllAsserts )
	{
		AsuraAssert_RecordAssertHitWhileIgnoringAll();
		iValue = IDIGNOREALL;
	}
	else if (!Asura_Xbox360_Graphics::GetDevice())
	{
		iValue = IDIGNOREONCE;
	}
	else
	{
		#ifdef ASURA_XBOX360_DEBUG_TEXT_ASSERTS
		Asura_Char szTitleBuffer[256];
		Asura_Char szErrorBuffer[1024];

		if(Asura_Core::IsMainThread())
		{
			Asura_PrintfToBfr( &szTitleBuffer[0], "Assert - Main thread", 255, true);
		}
		else
		{
			const DWORD uThreadID = GetCurrentThreadId();
			#ifdef ASURA_USE_PROFILING_SYSTEM_MULTITHREADED
			//Get the thread name if possible
			const Asura_CPUProfile_Renderer_ThreadData* pxThreadData = Asura_CPUProfile_Renderer_ThreadData::Platform_GetThreadData();
			if(pxThreadData)
			{
				Asura_PrintfToBfr( &szTitleBuffer[0], "Assert - Thread '%s' ID: %X", 255, true, pxThreadData->m_szThreadName,uThreadID );
			}
			#else
			Asura_PrintfToBfr( &szTitleBuffer[0], "Assert - Thread ID %X", 255, true, uThreadID );
			#endif
		}
		
		Asura_PrintfToBfr( &szErrorBuffer[0], "Assert( %s )\n%s\n\nLine: %d, File: %s", 1023, true, condition, desc, line, file );
		
		iValue = ShowMessageBoxDebugText( szTitleBuffer, szErrorBuffer );
		#else
		Asura_UnicodeChar wszTitleBuffer[256];
		Asura_UnicodeChar wszErrorBuffer[1024];
		
		Asura_PrintfToBfr( &wszTitleBuffer[0], L"Assert - %S", 255, true, Asura_Core::IsMainThread() ? "Main thread" : "Not Main Thread" );
		Asura_PrintfToBfr( &wszErrorBuffer[0], L"Assert( %S )\n%S\n\nLine: %d, File: %S", 1023, true, condition, desc, line, file );
	
		bool bWasSuspended = false;
		if( Asura_Xbox360_Graphics::IsDeviceSuspended() )
		{
			bWasSuspended = true;
			Asura_Xbox360_Graphics::ResumeDevice();
		}

		SCOPED_DEVICE_OWNERSHIP();
		ASURA_CPUPROFILE_SCOPED_PAUSE
		iValue = ShowMessageBox( wszTitleBuffer, wszErrorBuffer );

		if( bWasSuspended )
		{
			Asura_Xbox360_Graphics::AcquireThreadOwnership();
			Asura_Xbox360_Graphics::SuspendDevice();
		}
		#endif
	}
		
	s_bAssertFired = false;

	g_xAssertMutex.Unlock();

	switch( iValue )
	{
		default:

#if defined ASURA_ALLOW_DISABLE_ASSERTS
		case IDDISABLEASSERTS:
		{
			g_bIgnoreAllAsserts = true;
			// fall-thru
		}
#endif // defined ASURA_ALLOW_DISABLE_ASSERTS

		case IDIGNOREALL:
		{
			*pucIgnore = g_ucAssertIgnoreCounter;
			return false;
		}
		case IDIGNOREONCE:
		{
			return false;
		}
		case IDRETRY:
		{
			return true;
		}
	}
}

bool Asura_AssertfSetup( const char* szFile, int iLine, const char* szCondition, u_char* pucIgnore)
{
    g_szAsuraAssertfFile = szFile;
    g_iAsuraAssertfLine = iLine;
    g_szAsuraAssertfCondition = szCondition;
    g_pucAsuraAssertfIgnore = pucIgnore;
	return true;
}

bool Asura_AssertfFired( const Asura_Char* szFormat, ... )
{
	va_list xArgs;
	va_start( xArgs, szFormat );
    Asura_PrintfToBfr( g_szOutputBuffer, szFormat, xArgs, ASURAASSERTF_OUTPUT_BUFFER_SIZE, true );
	va_end( xArgs );

    bool bRetVal = Asura_AssertFired(g_szAsuraAssertfFile, g_iAsuraAssertfLine, 
        g_szAsuraAssertfCondition, g_szOutputBuffer, g_pucAsuraAssertfIgnore);

    g_szAsuraAssertfFile = 0;
    g_iAsuraAssertfLine = 0;
    g_szAsuraAssertfCondition = 0;
    g_pucAsuraAssertfIgnore = 0;

    return bRetVal;
}

const char* Asura_Platform_Assert_GetLogFileName()
{
	return ASSERTION_LOGFILE;
}

#endif	// defined(ASURA_ASSERT)


/// End of file. ///
