//
// Asura_Assert.cpp
//
// Created by KOR - 09 October 2006
//
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

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

#ifdef ASURA_ASSERT

// Platform
#include <revolution/kpad.h>
#include <revolution/gx.h>

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

// Engine platform-specific.
#include "Input/Asura_Wii_Input.h"
#include "Rendering/Asura_Wii_ROMFont.h"
#include "Rendering/Asura_GX.h"


// C O N S T A N T S /////////////////////////////////////////////////////////
static const u_int uASURA_WII_ASSERT_CHARS_PER_LINE = 60; // bit of a fudge - GC OS font is not monospaced.

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

enum
{
	ASURA_WII_ASSERT_INVALID    = -1,
	ASURA_WII_ASSERT_BREAK,
	ASURA_WII_ASSERT_IGNORE,
	ASURA_WII_ASSERT_IGNORE_ALL,
#ifdef ASURA_ALLOW_DISABLE_ASSERTS
	ASURA_WII_ASSERT_DISABLE_ASSERTS,
	ASURA_WII_ASSERT_LAST_OPTION = ASURA_WII_ASSERT_DISABLE_ASSERTS,
#else
	ASURA_WII_ASSERT_LAST_OPTION = ASURA_WII_ASSERT_IGNORE_ALL,
#endif
};

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

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 const int    ASURAASSERTF_OUTPUT_BUFFER_SIZE = 2048;
static char         g_szOutputBuffer[ASURAASSERTF_OUTPUT_BUFFER_SIZE];

static Asura_Mutex g_xAssertMutex;

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

// C L A S S E S/////////////////////////////////////////////////////////////
DEBUGVAR char dbg_cStickThreshold = 64;

class Asura_Wii_AssertInputSet
{
	friend class Asura_Wii_AssertInput;

	Asura_Wii_AssertInputSet()
	{
		Clear();
	}

	void Clear()
	{
		m_uButtonFlags = 0;
		m_cLx = 0;
	}

	bool IsLeftPressed() const
	{
		if (m_uButtonFlags & KPAD_BUTTON_LEFT)
		{
			return true;
		}
		if (m_cLx < -dbg_cStickThreshold)
		{
			return true;
		}
		return false;
	}

	bool IsRightPressed() const 
	{
		if (m_uButtonFlags & KPAD_BUTTON_RIGHT)
		{
			return true;
		}
		if (m_cLx > dbg_cStickThreshold)
		{
			return true;
		}
		return false;
	}

	bool IsConfirmPressed() const
	{
		return (m_uButtonFlags & (KPAD_BUTTON_A | KPAD_BUTTON_B));
	}

	unsigned int	m_uButtonFlags;
	char			m_cLx;
};

class Asura_Wii_AssertInput
{
public:

	void Update()
	{
		if(!KPADRead( 0, &m_axKPADStatus[0][0], KPAD_MAX_READ_BUFS ))
		{
			return;
		}

		m_xPrevInput = m_xCurInput;

		// TODO - handle multiple controllers? Not particularly important.
		m_xCurInput.m_uButtonFlags = m_axKPADStatus[0][0].hold;
		m_xCurInput.m_cLx = m_axKPADStatus[0][0].ex_status.fs.stick.x * 127;

	}

	bool IsAnyKeyPressed() const
	{
		return m_xCurInput.IsConfirmPressed() || 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:

	KPADStatus	m_axKPADStatus[ WPAD_MAX_CONTROLLERS ][ KPAD_MAX_READ_BUFS ] ;
	Asura_Wii_AssertInputSet m_xCurInput;
	Asura_Wii_AssertInputSet m_xPrevInput;
};

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

static u_int AssertConsoleCharsToLines(u_int uChars)
{
	return (uChars + uASURA_WII_ASSERT_CHARS_PER_LINE - 1)/uASURA_WII_ASSERT_CHARS_PER_LINE;
}

DEBUGVAR short dbg_sFontSize = 16;

static u_int OutputToScreen( u_int uCol, u_int uLine, u_int uColour, const char* szFormat, ... )
{
	Asura_Wii_ROMFont::SetSize(dbg_sFontSize, 0);
	// 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_WII_ASSERT_CHARS_PER_LINE+1];
	u_int uCharsRemaining = uNumChars;
	char* pcCurBuffer = acBuffer;
	while (uCharsRemaining)
	{
		u_int uCharsToCopy = Asura_Maths::Min(uASURA_WII_ASSERT_CHARS_PER_LINE - uCol, uCharsRemaining);
		Asura_Mem::Copy( acBuffer2, pcCurBuffer, uCharsToCopy );
		acBuffer2[uCharsToCopy] = 0;
		// KOR - OS font is irritatingly not monospaced, so width calculation is a fudge.
		Asura_Wii_ROMFont::RenderString(uCol * 8, uLine * dbg_sFontSize, 0, acBuffer2);
		uCharsRemaining -= uCharsToCopy;
		pcCurBuffer += uCharsToCopy;
		uCol = 0;
		++uLine;
	};
	return uNumChars;
}

DEBUGVAR u_int dbg_uBottomOffset = 25;

static void DrawAssertConsole( int iCurrentSelection, const char* szFile, int iLine, const char* szCondition, const char* szDescription )
{
	Asura_Render::BeginFrame();

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

	OutputToScreen( uXBase, uY++, 0xffffffff, "File:        %s", szFile );
	++uY;
	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 = dbg_uBottomOffset;
	OutputToScreen( uXBase, uY, 0xffffffff, "____________________________________________________________" );
	uY += 2;

	OutputToScreen( 3, uY, 0xffffffff, (ASURA_WII_ASSERT_BREAK == iCurrentSelection) ? "[Break]" : " Break ");
	OutputToScreen( 13, uY, 0xffffffff, (ASURA_WII_ASSERT_IGNORE == iCurrentSelection) ? "[Ignore once]" : " Ignore once ");
	OutputToScreen( 28, uY, 0xffffffff, (ASURA_WII_ASSERT_IGNORE_ALL == iCurrentSelection) ? "[Ignore all]" : " Ignore all ");
#ifdef ASURA_ALLOW_DISABLE_ASSERTS
	OutputToScreen( 43, uY, 0xffffffff, (ASURA_WII_ASSERT_DISABLE_ASSERTS == iCurrentSelection) ? "[Disable asserts]" : " Disable asserts ");
#endif
	++uY;

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

	Asura_Render::EndFrame();

}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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_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 ( AsuraAssert_GetIgnoreAll() )
	{
		*pucIgnore = g_ucAssertIgnoreCounter;
		return( false );
	}

	if(!Asura_Wii_Input::IsInitialised())
	{
		Asura_OutputToDebugger_Retail(( "[assertion ignored]\n" ));
		return false;
	}

	// If there's no debugger running then breaking is a really bad idea.
	// KOR - there doesn't appear to be a way of testing for this on GameCube, unfortunately.
	const bool bCanBreak = true;//( 0 != SNIsDebuggerRunning() );

	// Kill last frame if we're the thread that started the rendering.
	bool bWasRendering = false;

	if(Asura_Core::IsMainThread())
	{
		bWasRendering = Asura_Render::IsRendering();
	}
	else
	{
		OSReport("Assert not on main thread.\n");
		//return false;
	}
	if(bWasRendering) 
	{
		Asura_Render::EndFrame();
	}
	Asura_GX::LockMutex();

	Asura_GX::SetRenderStatesValid(false);

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

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

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

		do
		{
			xInput.Update();

			if(xInput.IsLeftSelected())
			{
				if( iCurrentSelection != ASURA_WII_ASSERT_BREAK )
				{
					iCurrentSelection--;
				}
			}

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

			bFinished = xInput.IsConfirmSelected();

			if (g_bIgnoreThisAssertOnce)
			{
				g_bIgnoreThisAssertOnce = false;
				iCurrentSelection = ASURA_WII_ASSERT_IGNORE;
				bFinished = true;
			}
			else if (g_bIgnoreThisAssertAll)
			{
				g_bIgnoreThisAssertAll = false;
				iCurrentSelection = ASURA_WII_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_WII_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());

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

		switch( iCurrentSelection )
		{
			case ASURA_WII_ASSERT_BREAK:
			{
				return bCanBreak;
			}

			default:
			case ASURA_WII_ASSERT_IGNORE:
			{
				Asura_OutputToDebugger_Retail(( "[assertion ignored]\n" ));
				return false;
			}

			case ASURA_WII_ASSERT_IGNORE_ALL:
			{
				Asura_OutputToDebugger_Retail(( "[assertion ignored and disabled]\n" ));
				*pucIgnore = g_ucAssertIgnoreCounter;
				return false;
			}
#ifdef ASURA_ALLOW_DISABLE_ASSERTS
			case ASURA_WII_ASSERT_DISABLE_ASSERTS:
			{
				Asura_OutputToDebugger_Retail(( "[assertions disabled]\n" ));
				AsuraAssert_SetIgnoreAll( true );
				return false;
			}
#endif
		}
	}
#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;
}


bool 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;
		return true;
	}
	return false;
}

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;
	}

	// KOR - Asserts from interrupts tend to go horribly wrong. 
	// Interrupts should return NULL for the current thread.
	if(OSGetCurrentThread() == NULL)
	{
		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()
{
	// Thread safety.
	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
		g_xAssertMutex.Unlock();
		return false;
	}

	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;
	g_xAssertMutex.Unlock();
}

const char* Asura_Platform_Assert_GetLogFileName()
{
	return ASSERTION_LOGFILE;
}

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

#endif // #ifdef ASURA_ASSERT
