//////////////////////////////////////////////////////////////////////////////
//
// Asura_Platform_Assert.cpp
//
// Copyright (c) 2006 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

#ifdef ASURA_ASSERT

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

// Stdlib platform-specific.
#include <libsn.h>
#include <sys/synchronization.h>
#include <sysutil/sysutil_msgdialog.h>
#include <sys/timer.h>

// Engine independent.
#include "Core/Asura_Maths.h"
#include "Core/Asura_Core.h"
#include "Core/Asura_String.h"
#include "Core/Asura_UserInput.h"
#include "Core/Asura_Multithread.h"
#include "Debugging/Asura_Logfile.h"

// Engine platform-specific.
#include "Input/Asura_PlatformInput.h"
#include "Rendering/Asura_PS3_Graphics.h"
#include "Rendering/Asura_PS3_FrameBuffer.h"
#include "Rendering/Asura_PS3_RenderingStates.h"
#include "Sound/Asura_PS3_Multistream_Audio.h"

// C L A S S E S /////////////////////////////////////////////////////////////

class MutexWrapper
{

public:

    MutexWrapper( void )
    {
	    sys_lwmutex_attribute_t xMutexAttributes;
	    sys_lwmutex_attribute_initialize( xMutexAttributes );
	    sys_lwmutex_create( &m_xMutex, &xMutexAttributes );
    }

    ~MutexWrapper( void )
    {
        sys_lwmutex_destroy( &m_xMutex );
    }

    bool Lock( void ) { return ( CELL_OK == sys_lwmutex_lock( &m_xMutex, 1000000 ) ); }
    void Unlock( void ) { sys_lwmutex_unlock( &m_xMutex ); }

private:

    sys_lwmutex_t m_xMutex;

};

// C O N S T A N T S /////////////////////////////////////////////////////////

const char* szASSERT_LOGFILE     = "logfiles\\asserts.txt";
const char* szASSERT_FORMAT_MAIN = "ASSERTION FAILURE:\n   File:        %s @ %i\n";
const char* szASSERT_FORMAT_COND = "   Condition:   %s\n";
const char* szASSERT_FORMAT_DESC = "   Description: %s\n";

// G L O B A L S /////////////////////////////////////////////////////////////

static bool		g_bIgnoreAllAsserts				= false;
static bool		g_bFirstTimeAssertFired			= true;

static const char* g_szFile;
static int         g_iLineNumber;
static const char* g_szCondition;
static const char* g_szDescription;
static u_char*     g_pucIgnore;

static bool		   g_bInAssert = false;

static char g_szAssertfBuffer[512];

static MutexWrapper g_xMutexWrapper;

// P R O T O T Y P E S   A N D   I N L I N E   F U N C T I O N S /////////////

static bool AssertInternal( void );
static void MsgDialogCallback( int iButtonType, void* pvUserData );
static bool PreAssert();
static void PostAssert();

// F U N C T I O N   I M P L E M E N T A T I O N S ///////////////////////////

bool Asura_AssertFired( const char* szFile, int iLineNumber,
                        const char* szCondition, const char* szDescription,
                        u_char* pucIgnore )
{
	if( Asura_PS3_Graphics::IsInShutdown() ) return false;

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

    g_szFile        = szFile;
    g_iLineNumber   = iLineNumber;
    g_szCondition   = szCondition;
    g_szDescription = szDescription;
    g_pucIgnore		= pucIgnore;
    
    bool bShouldBreak = AssertInternal();

	PostAssert();

	return bShouldBreak;
}

bool Asura_AssertfSetup( const char* szFile, int iLine, const char* szCondition, u_char* pucIgnore)
{
	if( Asura_PS3_Graphics::IsInShutdown() ) return false;
	if( !PreAssert() ) return false;

	g_szFile       = szFile;
	g_iLineNumber  = iLine;
	g_szCondition  = szCondition;
	g_pucIgnore		= pucIgnore;
	return true;
}

bool Asura_AssertfFired( const Asura_Char* szFormat, ... )
{
	if( Asura_PS3_Graphics::IsInShutdown() ) return false;
    // NB: It's assumed that Asura_AssertfSetup() has been called immediately
    // before this.
	if (!g_bInAssert)
	{
		return false;
	}

    va_list xArgs;
    va_start( xArgs, szFormat );
    Asura_PrintfToBfr( g_szAssertfBuffer, szFormat, xArgs, sizeof(g_szAssertfBuffer), true );
    va_end( xArgs );

    g_szDescription = g_szAssertfBuffer;

    bool bShouldBreak = AssertInternal();

	PostAssert();

	return bShouldBreak;
}

const char* Asura_Platform_Assert_GetLogFileName()
{
	return szASSERT_LOGFILE;
}

static bool AssertInternal( void )
{
	if( Asura_PS3_Graphics::IsInShutdown() ) return false;
    
	#ifdef ASURA_USE_AUDIO_SYSTEM
	Asura_MultiStream_Audio::PauseAllStreams();
	#endif
	
	// Output to debugger.
    {
        Asura_OutputToDebugger_Retail(( szASSERT_FORMAT_MAIN, g_szFile, g_iLineNumber ));
        
        if( g_szCondition )
		{
			Asura_OutputToDebugger_Retail(( szASSERT_FORMAT_COND, g_szCondition ));
		}

        if( g_szDescription )
		{
			Asura_OutputToDebugger_Retail(( szASSERT_FORMAT_DESC, g_szDescription ));
		}
    }

    // And to the logfile.
    {
        // Clear logfile first time through.
		AsuraAssert_LogClearLog();
        Asura_SimpleLogfileOutput(( szASSERT_LOGFILE, szASSERT_FORMAT_MAIN, g_szFile, g_iLineNumber ));
        
        if( g_szCondition )
		{
			Asura_SimpleLogfileOutput(( szASSERT_LOGFILE, szASSERT_FORMAT_COND, g_szCondition ));
		}

        if( g_szDescription )
		{
			Asura_SimpleLogfileOutput(( szASSERT_LOGFILE, szASSERT_FORMAT_DESC, g_szDescription ));
		}
    }

    if( g_bIgnoreAllAsserts )
    {
		AsuraAssert_RecordAssertHitWhileIgnoringAll();
		*g_pucIgnore = g_ucAssertIgnoreCounter;
        return false;
    }

    // Only attempt to break if there's actually a debugger attached.
    const bool bCanBreak = snIsDebuggerRunning();

	bool bRender = true;

	if ( !Asura_PS3_Graphics::IsInitialised() || !Asura_PS3_RenderingStates::GetSurface() )
	{
		bRender = false;
		Asura_OutputToDebugger_Retail(( "*** cannot display assert dialog, graphics not initialised ***\n" ));
	}

    char szMsg[CELL_MSGDIALOG_STRING_SIZE];
    Asura_PrintfToBfr( szMsg,
                       "ASSERT FAILED (%s @ %i)\nCONDITION: %s\nDESCRIPTION: %s\n\nIGNORE? (X button - ignore/break, O button - ignore all)",
                       sizeof(szMsg), true,
                       g_szFile, g_iLineNumber, g_szCondition, g_szDescription );

	if ( bRender )
	{
		bRender = Asura_PS3_Graphics::LockMutexTimeOut(2.0f);
		if ( !bRender ) Asura_OutputToDebugger_Retail(( "*** cannot display assert dialog, graphics mutex lock failed ***\n" ));
	}

    int iMsgDialogButtonPress = CELL_MSGDIALOG_BUTTON_INVALID;
    int iDialogError = CELL_OK;
    if ( bRender )
    {
		iDialogError =
			cellMsgDialogOpen2( CELL_MSGDIALOG_DIALOG_TYPE_ERROR | CELL_MSGDIALOG_BUTTON_TYPE_YESNO | CELL_MSGDIALOG_DEFAULT_CURSOR_YES,
							   szMsg,
							   MsgDialogCallback,
							   &iMsgDialogButtonPress,
							   NULL );

		if ( iDialogError != CELL_OK )
		{
			bRender = false;
			Asura_PS3_Graphics::UnlockMutex();
		}
	}

    bool bReturnValue;
    if( bRender )
    {
        // Wait for our callback.

//		Asura_PS3_Graphics::EndFrame();
		CellGcmSurface* pSurface = Asura_PS3_RenderingStates::GetSurface();

		while( CELL_MSGDIALOG_BUTTON_INVALID == iMsgDialogButtonPress )
		{
			sys_timer_usleep(5000);
			if( Asura_PS3_Graphics::IsInShutdown() ) { Asura_PS3_Graphics::UnlockMutex(); return false; }
			Asura_PS3_DeferredRendering::AssertFlip();
			cellSysutilCheckCallback();
		}
	
		//BW restore things, since we were drawing in a different command buffer Asura_PS3_RenderingStates internal state can get out of sync with what's actually happening
		Asura_PS3_RenderingStates::SetSurface(pSurface);

		Asura_PS3_Graphics::UnlockMutex();

        switch( iMsgDialogButtonPress )
        {
            default:
                // Hmm. Asserting here isn't really going to work. Just fall
                // through to the ignore case. This case is only here to
                // keep the compiler happy anyway.

            case CELL_MSGDIALOG_BUTTON_YES:    // Ignore.
                bReturnValue = false;
				Asura_OutputToDebugger_Retail(( "[assertion ignored]\n" ));
                break;

            case CELL_MSGDIALOG_BUTTON_NO:     // Break.
                bReturnValue = bCanBreak;
				Asura_OutputToDebugger_Retail(( "[assertion break]\n" ));
                break;

            case CELL_MSGDIALOG_BUTTON_ESCAPE: // Ignore all.
				*g_pucIgnore = g_ucAssertIgnoreCounter;
                bReturnValue = false;
				Asura_OutputToDebugger_Retail(( "[assertion ignore all]\n" ));
                break;
        }
    }
    else
    {
		bReturnValue = bCanBreak;
		
		if ( iDialogError != CELL_OK )
		{
			// If creating the dialog fails (maybe because we haven't even
			// initialised that stuff yet) then just break, if we even can.
			const char* szReason = (static_cast<u_int>(iDialogError) == CELL_SYSUTIL_ERROR_BUSY) ? "sysutil busy" : "unknown";
			Asura_OutputToDebugger_Retail(("***  cannot display assert dialog, error (%x) = %s ***\n", iDialogError, szReason));
		}

		
		if (bReturnValue)
		{
			Asura_OutputToDebugger_Retail(( "[assertion forced break]\n" ));
		}
		else
		{
			Asura_OutputToDebugger_Retail(( "[assertion forced ignored]\n" ));
		}
    }

    g_szFile        = NULL;
    g_szCondition   = NULL;
    g_szDescription = NULL;
	g_pucIgnore		= NULL;

	#ifdef ASURA_USE_AUDIO_SYSTEM
	Asura_MultiStream_Audio::UnpauseAllStreams();
	#endif

    return bReturnValue;
}

static void MsgDialogCallback( int iButtonType, void* pvUserData )
{
    *(static_cast<int*>(pvUserData)) = iButtonType;
}

static bool PreAssert()
{
	// Thread safety.
    if ( !g_xMutexWrapper.Lock() ) return false;
    
	if (g_bInAssert)
	{
		// don't break if we're entering an assert from inside another assert (e.g. assert called by begin/end frame).
		g_xMutexWrapper.Unlock();
		return false;
	}
	g_bInAssert = true;
	return true;
}

static void PostAssert()
{
	g_bInAssert = false;
	g_xMutexWrapper.Unlock();
}

// E N D   F I L E   W R A P P E R ///////////////////////////////////////////

#endif // #ifdef ASURA_ASSERT
