//////////////////////////////////////////////////////////////////////////////
//
// Asura_Assert.cpp
//
// By: Richard M Smith, Morgan Parry, Mike Healey.
//
// PSP version based on PS2 equivalent
//
// Copyright (c) 2001-2005 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////



//////////////
// INCLUDES //
//////////////

#include "Core/Asura.h"

#ifdef ASURA_ASSERT

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


// Stdlib independent.
#include <stdarg.h>

// Stdlib platform-specific.
#include <libsn.h>
#include <ctrlsvc.h> //needed for controller functions
#include <displaysvc.h>	//needed for VSync
#include <devkit_version.h>

// Engine independent
#include "Core/Asura_String.h"
#include "Core/Memory/Asura_Mem.h"
#include "Debugging/Asura_Logfile.h"
#ifdef ASURA_MAIN
#include "Core/Asura_Core.h"	// for IsMainThread()
#include "Core/Asura_Multithread.h"
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
#include "Network/Asura_Network.h"
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
#include "Rendering/Asura_Render.h"
#endif // #ifdef ASURA_MAIN 

// Engine platform-specific.
#include "Core/Asura_PlatformBasics.h"
#include "Rendering/Asura_PSP_Graphics.h"

#ifndef ASURA_LOGFILES_ENABLED
#error you should have logfiles enabled if asserts are enabled.
#endif

// C O N S T A N T S /////////////////////////////////////////////////////////
static const u_int uASURA_PSP_ASSERT_CHARS_PER_LINE = 60;	// (480/8)

// T Y P E S /////////////////////////////////////////////////////////////////

enum
{
    ASURA_PSP_ASSERT_INVALID    = -1,
    ASURA_PSP_ASSERT_BREAK,
    ASURA_PSP_ASSERT_IGNORE,
    ASURA_PSP_ASSERT_IGNORE_ALL
};

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

#ifdef ASURA_PSP_SONY_SUBMISSION
#warning "do you really want asserts on in submission builds?"
static bool g_bIgnoreAllAsserts = true;
#else
static bool g_bIgnoreAllAsserts = false;
#endif
u_char		  g_ucAssertIgnoreCounter = 1;
volatile bool g_bIgnoreThisAssertOnce = false;
volatile bool g_bIgnoreThisAssertAll = false;

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 bool		   g_bCurrentVsyncOn = false;

static const int    ASURAASSERTF_OUTPUT_BUFFER_SIZE = 2048;
static char         g_szOutputBuffer[ASURAASSERTF_OUTPUT_BUFFER_SIZE];

#ifdef ASURA_MAIN
static Asura_Mutex g_xAssertMutex;
static bool g_bAssertMutexValid = true;
#endif

// G L O B A L   F U N C T I O N S ///////////////////////////////////////////
void Asura_PSP_AssertShutdown( void )
{
#ifdef ASURA_MAIN
	g_bAssertMutexValid = false;
#endif
}

void AsuraAssert_SetIgnoreAll( bool bIgnore )
{
    g_bIgnoreAllAsserts = bIgnore;
}

bool AsuraAssert_GetIgnoreAll()
{
	return g_bIgnoreAllAsserts;
}

// C L A S S E S /////////////////////////////////////////////////////////////
class Asura_PSP_AssertInputSet
{
	friend class Asura_PSP_AssertInput;

	Asura_PSP_AssertInputSet()
	{
		Clear();
	}

	void Clear()
	{
		m_uButtonFlags = 0;
		m_ucLx = 0x80;	// centre
		m_ucLy = 0x80;
	}

	bool IsLeftPressed() const
	{
		if (m_uButtonFlags & SCE_CTRL_LEFT)
		{
			return true;
		}
		if (m_ucLx < 0x60)
		{
			return true;
		}
		return false;
	}

	bool IsRightPressed() const 
	{
		if (m_uButtonFlags & SCE_CTRL_RIGHT)
		{
			return true;
		}
		if (m_ucLx > 0xa0)
		{
			return true;
		}
		return false;
	}

	bool IsConfirmPressed() const
	{
#if (SCE_DEVKIT_VERSION >= 0x02080000)
		return (m_uButtonFlags & (SCE_CTRL_CROSS | SCE_CTRL_CIRCLE | SCE_CTRL_R));
#else
		return (m_uButtonFlags & (SCE_CTRL_CROSS | SCE_CTRL_CIRCLE | SCE_CTRL_R1));
#endif
	}

	unsigned int	m_uButtonFlags;
	u_char			m_ucLx, m_ucLy;
};

class Asura_PSP_AssertInput
{
public:

	void Update()
	{
		// Read the pad
		SceCtrlData		axBuffer[8];
#ifdef ASURA_MAIN
		int				iRet	= (Asura_Core::IsMainThread())
								? sceCtrlReadBufferPositive(axBuffer, 8)	// Not-multithread safe.
								: sceCtrlPeekBufferPositive(axBuffer, 8);	// Multithread safe.
#else
		int iRet = sceCtrlReadBufferPositive(axBuffer, 8); // Not multithread safe, but we should only have one.
#endif

		m_xPrevInput = m_xCurInput;

		if (iRet) 
		{
			m_xCurInput.m_uButtonFlags = axBuffer[0].Buttons;
			m_xCurInput.m_ucLx = axBuffer[0].Lx;        // X axis:0x00-0xff
			m_xCurInput.m_ucLy = axBuffer[0].Ly;        // Y axis:0x00-0xff
		}
		else 
		{
			m_xCurInput.Clear();
		}
	}

	bool IsAnyKeyPressed() const
	{
		return m_xCurInput.m_uButtonFlags || m_xCurInput.IsLeftPressed() || m_xCurInput.IsRightPressed();
	}

	bool IsLeftSelected() const
	{
		return m_xCurInput.IsLeftPressed() && !m_xPrevInput.IsLeftPressed();
	}

	bool IsRightSelected() const
	{
		return m_xCurInput.IsRightPressed() && !m_xPrevInput.IsRightPressed();
	}

	bool IsConfirmSelected() const
	{
		return m_xCurInput.IsConfirmPressed() && !m_xPrevInput.IsConfirmPressed();
	}

private:

	Asura_PSP_AssertInputSet m_xCurInput;
	Asura_PSP_AssertInputSet m_xPrevInput;
};

// F U N C T I O N S /////////////////////////////////////////////////////////
static bool AssertInternal( void );
static bool PreAssert();
static void PostAssert();

#ifdef ASURA_MAIN
static u_int AssertConsoleCharsToLines(u_int uChars)
{
    return (uChars + uASURA_PSP_ASSERT_CHARS_PER_LINE - 1)/uASURA_PSP_ASSERT_CHARS_PER_LINE;
}

static u_int OutputToScreen( u_int uCol, u_int uLine, u_int uColour, const char* szFormat, ... )
{
	// Print string to buffer
	const u_int uBufferSize = 512;
	char acBuffer[uBufferSize];

	va_list xArgs;
	va_start( xArgs, szFormat );
    const u_int uNumChars = Asura_PrintfToBfr( acBuffer, szFormat, xArgs, uBufferSize, true );
	va_end( xArgs );

	// split buffer into multiple lines
	char acBuffer2[uASURA_PSP_ASSERT_CHARS_PER_LINE+1];
	u_int uCharsRemaining = uNumChars;
	char* pcCurBuffer = acBuffer;
	while (uCharsRemaining)
	{
		u_int uCharsToCopy = Asura_Maths::Min(uASURA_PSP_ASSERT_CHARS_PER_LINE - uCol, uCharsRemaining);
		Asura_Mem::Copy( acBuffer2, pcCurBuffer, uCharsToCopy );
		acBuffer2[uCharsToCopy] = 0;
		GU_DebugPrint( uCol * 8, uLine * 8, uColour, acBuffer2 );
		uCharsRemaining -= uCharsToCopy;
		pcCurBuffer += uCharsToCopy;
		uCol = 0;
		++uLine;
	};
	return uNumChars;
}

static void DrawAssertConsole( int iCurrentSelection, const char* szFile, int iLine, const char* szCondition, const char* szDescription )
{
//	static bool ls_bFirstTime = true;
//	if (ls_bFirstTime)
//	{
//		ls_bFirstTime = false;
//		GU_Init();// ok to call more than once?
//		GU_Display(SCEGU_DISPLAY_ON);
//	}

	Asura_PSP_Graphics::BeginFrame_ForAssert();

	//GU_Clear(SCEGU_CLEAR_ALL);

	const u_int uXBase = 0;
	u_int uY = 12;
    OutputToScreen( uXBase, uY++, 0xffffffff, "____________________________________________________________" );
	uY++;
	OutputToScreen( uXBase, uY++, 0xffffffff, "                    ASSERTION FAILURE" );
    OutputToScreen( uXBase, uY++, 0xffffffff, "____________________________________________________________" );
	uY++;

	OutputToScreen( uXBase, uY++, 0xffffffff, "File:        %s", szFile );
	OutputToScreen( uXBase, uY++, 0xffffffff, "Line:        %i", iLine );

    if( szCondition )
    {
	    u_int uCharsWritten = OutputToScreen( uXBase, uY, 0xffffffff, "Condition:   %s", szCondition );
		uY += AssertConsoleCharsToLines( uCharsWritten );
    }

    if( szDescription )
    {
	    u_int uCharsWritten = OutputToScreen( uXBase, uY, 0xffffffff, "Description: %s", szDescription );
		uY += AssertConsoleCharsToLines( uCharsWritten );
    }

	uY += 2;
    OutputToScreen( uXBase, uY, 0xffffffff, "____________________________________________________________" );
	uY += 2;

    u_int uColour;
	u_int uX = uXBase;
	const u_int uSelectedColour = 0xffff00ff;
	const u_int uUnselectedColour = 0xff555555;
	uColour = (ASURA_PSP_ASSERT_BREAK == iCurrentSelection) ? uSelectedColour : uUnselectedColour;
	uX += OutputToScreen( uX, uY, uColour, "       BREAK        " );

	uColour = (ASURA_PSP_ASSERT_IGNORE == iCurrentSelection) ? uSelectedColour : uUnselectedColour;
	uX += OutputToScreen( uX, uY, uColour, "    IGNORE ONCE     " );

	uColour = (ASURA_PSP_ASSERT_IGNORE_ALL == iCurrentSelection) ? uSelectedColour : uUnselectedColour;
	uX += OutputToScreen( uX, uY, uColour, "     IGNORE ALL" );
	++uY;

    OutputToScreen( uXBase, uY++, 0xffffffff, "____________________________________________________________" );
	
	Asura_PSP_Graphics::EndFrame_ForAssert();

}
#endif // #ifdef ASURA_MAIN


///////////////////////////////////////////////////////////////////////////////////////////////////////////
// FUNCTION     : AssertInternal()
// RETURNS      : TRUE if the program should break; FALSE otherwise
// DESCRIPTION  : On screen assert message providing the above information with the option to
//                break into the program, ignore this one assert or ignore this assert for the
//                remainder of this program
///////////////////////////////////////////////////////////////////////////////////////////////////////////
static bool AssertInternal()
{
	const char* szFile = g_szFile;
	const int iLine = g_iLineNumber;
	const char* const szCondition = g_szCondition;
	const char* const szDescription = g_szDescription;
	u_char* const pucIgnore = g_pucIgnore;

    const char* ASSERTION_LOGFILE = "logfiles\\asserts.txt";

    const char* ASSERTION_OUTPUT_FORMAT_STRING_MAIN = "ASSERTION FAILURE:\n   File:        %s (line %i)\n";
    const char* ASSERTION_OUTPUT_FORMAT_STRING_COND = "   Condition:   %s\n";
    const char* ASSERTION_OUTPUT_FORMAT_STRING_DESC = "   Description: %s\n";

    // Output to debugger.
    {
        Asura_OutputToDebugger_Retail(( ASSERTION_OUTPUT_FORMAT_STRING_MAIN, szFile, iLine ));
        
        if( szCondition ) { Asura_OutputToDebugger_Retail(( ASSERTION_OUTPUT_FORMAT_STRING_COND, szCondition )); }

        if( szDescription ) { Asura_OutputToDebugger_Retail(( ASSERTION_OUTPUT_FORMAT_STRING_DESC, szDescription )); }
    }
    
    // Log as well.
    {
        static bool bFirstTimeAssertFired = true;
        if (bFirstTimeAssertFired)
        {
            bFirstTimeAssertFired = false;
            Asura_SimpleLogfileClear(ASSERTION_LOGFILE);
        }

        Asura_SimpleLogfileOutput(( ASSERTION_LOGFILE, ASSERTION_OUTPUT_FORMAT_STRING_MAIN, szFile, iLine ));
        
        if( szCondition ) { Asura_SimpleLogfileOutput(( ASSERTION_LOGFILE, ASSERTION_OUTPUT_FORMAT_STRING_COND, szCondition )); }

        if( szDescription ) { Asura_SimpleLogfileOutput(( ASSERTION_LOGFILE, ASSERTION_OUTPUT_FORMAT_STRING_DESC, szDescription )); }
    }

#ifndef ASURA_MAIN
    {
		*pucIgnore = g_ucAssertIgnoreCounter;
        return( true );
    }
#else

	if ( !g_bAssertMutexValid )
	{
		*pucIgnore = g_ucAssertIgnoreCounter;
		return true;
	}

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

    // If there's no debugger running then breaking is a really bad idea.
	// Currently SNIsDebuggerRunning is broken so do it based on what kind
    // of build this is.
#if defined(ASURA_PSP_ARTIST_BUILD) || defined(ASURA_PSP_SONY_SUBMISSION)
	const bool bCanBreak = false;
#else
	const bool bCanBreak = true;//( 0 != SNIsDebuggerRunning() );
#endif

	// Kill last frame if we're the thread that started the rendering.
	bool bWasRendering = false;
	
	if(Asura_Core::IsMainThread())
	{
		bWasRendering = Asura_Render::IsRendering();
	}
	if(bWasRendering) 
	{
		Asura_PSP_Graphics::EndFrame();
	}

	Asura_PSP_Graphics::LockMutex();

	// this block of code renders the assert dialog and gets the user action.
    {
		Asura_PSP_AssertInput xInput;

        u_int	uNumFrames			= 0;
		bool    bFinished           = false;
        int     iCurrentSelection   = ASURA_PSP_ASSERT_IGNORE;

		do
		{
			xInput.Update();
			DrawAssertConsole( iCurrentSelection, szFile, iLine, szCondition, szDescription );
		}
		while(xInput.IsAnyKeyPressed());

        do
        {
			xInput.Update();
            
			if(xInput.IsLeftSelected())
			{
                if( iCurrentSelection != ASURA_PSP_ASSERT_BREAK )
                {
                    iCurrentSelection--;
                }
			}

			if(xInput.IsRightSelected())		   
			{
                if( iCurrentSelection != ASURA_PSP_ASSERT_IGNORE_ALL)
                {
                    iCurrentSelection++;
                }
			}

			bFinished = xInput.IsConfirmSelected();

            if (g_bIgnoreThisAssertOnce)
            {
                g_bIgnoreThisAssertOnce = false;
                iCurrentSelection = ASURA_PSP_ASSERT_IGNORE;
                bFinished = true;
            }
            else if (g_bIgnoreThisAssertAll)
            {
                g_bIgnoreThisAssertAll = false;
                iCurrentSelection = ASURA_PSP_ASSERT_IGNORE_ALL;
                bFinished = true;
            }
#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
			else if (uNumFrames > 200 && Asura_Network::IsExternalNetworkActive() && 
				((!Asura_Network::IsServer()) || Asura_Network::GetNumberOfMachines() > 1))
			{
				// only display for a couple of seconds if external network AND 
				// if we're the server, if people are connected, or if we're just a 
				// client then always.
                iCurrentSelection = ASURA_PSP_ASSERT_IGNORE;
                bFinished = true;
			}
#endif // #ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

            DrawAssertConsole( iCurrentSelection, szFile, iLine, szCondition, szDescription );

			uNumFrames++;

        } while( !bFinished );

    // Wait for the X button to be released
		do
		{
			xInput.Update();
			DrawAssertConsole( iCurrentSelection, szFile, iLine, szCondition, szDescription );
		}
		while (xInput.IsAnyKeyPressed());

        //GU_Clear(SCEGU_CLEAR_ALL);

		// restart rendering if we were mid-render before entering assert
		Asura_PSP_Graphics::UnlockMutex();
		if (bWasRendering)
		{
			Asura_PSP_Graphics::BeginFrame();
		}

        switch( iCurrentSelection )
        {
            case ASURA_PSP_ASSERT_BREAK:
                return bCanBreak;

            default:
            case ASURA_PSP_ASSERT_IGNORE:
                Asura_OutputToDebugger_Retail(( "[assertion ignored]\n" ));
                return false;
        
            case ASURA_PSP_ASSERT_IGNORE_ALL:
                Asura_OutputToDebugger_Retail(( "[assertion ignored and disabled]\n" ));
				*pucIgnore = g_ucAssertIgnoreCounter;
                return false;
        }
    }
#endif // else not #ifndef ASURA_MAIN

} // End Function 'Asura_AssertFired'

bool Asura_AssertFired( const char* szFile, int iLineNumber,
                        const char* szCondition, const char* szDescription,
                        u_char* pucIgnore )
{
	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;
}


void Asura_AssertfSetup( const char* szFile, int iLine, const char* szCondition, u_char* pucIgnore)
{
	if (PreAssert())
	{
		g_szFile       = szFile;
		g_iLineNumber  = iLine;
		g_szCondition  = szCondition;
		g_pucIgnore    = pucIgnore;
	}
}

bool Asura_AssertfFired( const Asura_Char* szFormat, ... )
{
    // 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_szOutputBuffer, szFormat, xArgs, ASURAASSERTF_OUTPUT_BUFFER_SIZE, true );
	va_end( xArgs );
    g_szDescription = g_szOutputBuffer;

    bool bShouldBreak = AssertInternal();

	PostAssert();

	return bShouldBreak;
}



static bool PreAssert()
{
#ifdef ASURA_MAIN
	// Thread safety.
    if (g_bAssertMutexValid)
	{
		g_xAssertMutex.Lock();
	}
	if (g_bInAssert)
	{
		// don't break if we're entering an assert from inside another assert (e.g. assert called by begin/end frame).
		// unlock the recursive lock
		if (g_bAssertMutexValid)
		{
			g_xAssertMutex.Unlock();
		}
		return false;
	}
#endif
	
	// JCT - turn VSync on, so we can actually read the assert on screen
	g_bCurrentVsyncOn = Asura_PSP_Graphics::GetUseVsync();
	Asura_PSP_Graphics::SetUseVsync(true);

	g_bInAssert = true;
	return true;
}

static void PostAssert()
{
	g_szFile        = NULL;
	g_iLineNumber   = 0;
	g_szCondition   = NULL;
	g_szDescription = NULL;
	g_pucIgnore		= NULL;

	g_bInAssert = false;

	// JCT - set Vsync back to how it was before the assert fired
	Asura_PSP_Graphics::SetUseVsync(g_bCurrentVsyncOn);

#ifdef ASURA_MAIN
	if (g_bAssertMutexValid)
	{
		g_xAssertMutex.Unlock();
	}
#endif
}

void AsuraAssert_ReEnableAll()
{
	do 
	{
		++g_ucAssertIgnoreCounter;
	} while ( g_ucAssertIgnoreCounter == 0 ); // If we wrap the u_char, don't want to accidentally ignore all asserts
}

bool AsuraAssert_ConsoleCmd_EnableAsserts( bool bHelp )
{
	if ( bHelp )
	{
		return true;
	}
	AsuraAssert_SetIgnoreAll(false);
	AsuraAssert_ReEnableAll();
	return true;
}

//// (MH - 17.02.2004) implementation of library assert which should be used in
//// preference to the library version, which seems to just call exit() on PS2.
//// ignore all is not implemented however.
//extern "C"
//{
//	void __assert(const char* szFile, int iLine, const char* szCondition)
//	{
//		bool bIgnoreAllDummy;
//		if (Asura_AssertFired(szFile, iLine, szCondition, "", &bIgnoreAllDummy))
//		{
//			ASURA_PS2_DEBUG_BREAK();
//		}
//		// hopefully you can get to the calling point by stepping over.
//	}
//}
		

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

#endif //#ifndef ASURA_PSP_SONY_SUBMISSION
