/*  PCSX2 - PS2 Emulator for PCs
 *  Copyright (C) 2002-2010  PCSX2 Dev Team
 *
 *  PCSX2 is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU Lesser General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with PCSX2.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#include "PrecompiledHeader.h"
#include "App.h"
#include <memory>

using namespace pxSizerFlags;

// --------------------------------------------------------------------------------------
//  ConsoleLogSource_Event  (implementations)
// --------------------------------------------------------------------------------------

bool ConsoleLogSource_Event::Write( const pxEvtQueue* evtHandler, const SysExecEvent* evt, const wxChar* msg ) {
	return _parent::Write( pxsFmt(L"(%s:%s) ", WX_STR(evtHandler->GetEventHandlerName()), WX_STR(evt->GetEventName())) + msg );
}
bool ConsoleLogSource_Event::Warn( const pxEvtQueue* evtHandler, const SysExecEvent* evt, const wxChar* msg )	{
	return _parent::Write( pxsFmt(L"(%s:%s) ", WX_STR(evtHandler->GetEventHandlerName()), WX_STR(evt->GetEventName())) + msg );
}
bool ConsoleLogSource_Event::Error( const pxEvtQueue* evtHandler, const SysExecEvent* evt, const wxChar* msg ) {
	return _parent::Write( pxsFmt(L"(%s:%s) ", WX_STR(evtHandler->GetEventHandlerName()), WX_STR(evt->GetEventName())) + msg );
}

ConsoleLogSource_Event::ConsoleLogSource_Event()
{
	static const TraceLogDescriptor myDesc =
	{
		L"SysEvents",	L"S&ysVM Control Events",
		pxLt("Logs events as they are passed to the PS2 virtual machine."),
	};
	
	m_Descriptor = &myDesc;
}

ConsoleLogSource_Event pxConLog_Event;

// --------------------------------------------------------------------------------------
//  SysExecEvent  (implementations)
// --------------------------------------------------------------------------------------
wxString SysExecEvent::GetEventName() const
{
	pxFail( "Warning: Unnamed SysExecutor Event!  Please overload GetEventName() in all SysExecEvent derived classes." );
	return wxEmptyString;
}

wxString SysExecEvent::GetEventMessage() const
{
	return GetEventName();
}

// This is called from _DoInvokeEvent after various affinity and state checks have verified the
// message as executable.  Override this when possible.  Only override _DoInvokeEvent if you
// need some kind of additional low-level ability.
void SysExecEvent::InvokeEvent()
{
}

// This is called by _DoInvokeEvent *always* -- even when exceptions occur during InvokeEvent(),
// making this function a bit like a C# 'finally' block (try/catch/finally -- a nice feature lacking
// from C++ prior to the new C++0x standard).
//
// This function calls PostResult by default, and should be invoked by derived classes overriding
// CleanupEvent(), unless you want to change the PostResult behavior.
void SysExecEvent::CleanupEvent()
{
	PostResult();
}

// Transports the specified exception to the thread/context that invoked this event.
// Events are run from a try/catch block in the event handler that automatically transports
// exceptions as neeeded, so there shouldn't be much need to use this method directly.
void SysExecEvent::SetException( BaseException* ex )
{
	if( !ex ) return;

	ex->DiagMsg() += pxsFmt(L"(%s) ", WX_STR(GetEventName()));
	//ex->UserMsg() = prefix + ex->UserMsg();

	if( m_sync )
	{
		// Throws the exception inline with the message handler (this makes the exception
		// look like it was thrown quite naturally).
		m_sync->SetException( ex );
	}
	else
	{
		// transport the exception to the main thread, since the message is fully
		// asynchronous, or has already entered an asynchronous state.  Message is sent
		// as a non-blocking action since proper handling of user errors on async messages
		// is *usually* to log/ignore it (hah), or to suspend emulation and issue a dialog
		// box to the user.

		wxGetApp().PostEvent( pxExceptionEvent( ex ) );
	}
}

void SysExecEvent::SetException( const BaseException& ex )
{
	SetException( ex.Clone() );
}


// This method calls _DoInvoke after performing some setup, exception handling, and
// affinity checks.  For implementing behavior of your event, override _DoInvoke
// instead, which is the intended method of implementing derived class invocation.
void SysExecEvent::_DoInvokeEvent()
{
	AffinityAssert_AllowFrom_SysExecutor();

	try {
		InvokeEvent();
	}
	catch( BaseException& ex )
	{
		SetException( ex );
	}
	catch( std::runtime_error& ex )
	{
		SetException( new Exception::RuntimeError(ex) );
	}

	// Cleanup Execution -- performed regardless of exception or not above.
	try {
		CleanupEvent();
	}
	catch( BaseException& ex )
	{
		SetException( ex );
	}
	catch( std::runtime_error& ex )
	{
		SetException( new Exception::RuntimeError(ex) );
	}
}

// Posts an empty result to the invoking context/thread of this message, if one exists.
// If the invoking thread posted the event in non-blocking fashion then no action is
// taken.
void SysExecEvent::PostResult() const
{
	if( m_sync ) m_sync->PostResult();
}

// --------------------------------------------------------------------------------------
//  pxEvtQueue Implementations
// --------------------------------------------------------------------------------------
pxEvtQueue::pxEvtQueue() :
	m_OwnerThreadId(), m_Quitting(false), m_qpc_Start(0)
{
}

// Puts the event queue into Shutdown mode, which does *not* immediately stop nor cancel
// the queue's processing.  Instead it marks the queue inaccessible to all new events
// and continues procesing queued events for critical events that should not be ignored.
// (typically these are shutdown events critical to closing the app cleanly). Once
// all such events have been processed, the thread is stopped.
//
void pxEvtQueue::ShutdownQueue()
{
	if( m_Quitting ) return;
	m_Quitting = true;
	m_wakeup.Post();
}

struct ScopedThreadCancelDisable
{
	ScopedThreadCancelDisable()
	{
		int oldstate;
		pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &oldstate );
	}
	
	~ScopedThreadCancelDisable()
	{
		int oldstate;
		pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, &oldstate );
	}
};

// isIdle  - parameter is useful for logging only (currently)
void pxEvtQueue::ProcessEvents( pxEvtList& list, bool isIdle )
{
	ScopedLock synclock( m_mtx_pending );
    
    pxEvtList::iterator node;
    while( node = list.begin(), node != list.end() )
    {
        std::unique_ptr<SysExecEvent> deleteMe(*node);

		list.erase( node );
		if( !m_Quitting || deleteMe->IsCriticalEvent() )
		{
			// Some messages can be blocking, so we should release the mutex lock while
			// processing, to avoid having cases where the main thread deadlocks simply
			// trying to add a message to the queue due to the basic mutex acquire needed.

			m_qpc_Start = GetCPUTicks();

			synclock.Release();

			pxEvtLog.Write( this, deleteMe.get(), wxsFormat(L"Executing... [%s]%s",
				deleteMe->AllowCancelOnExit() ? L"Cancelable" : L"Noncancelable", isIdle ? L"(Idle)" : wxEmptyString).wc_str()
			);

			if( deleteMe->AllowCancelOnExit() )
				deleteMe->_DoInvokeEvent();
			else
			{
				ScopedThreadCancelDisable thr_cancel_scope;
				deleteMe->_DoInvokeEvent();
			}

			u64 qpc_end = GetCPUTicks();
			pxEvtLog.Write( this, deleteMe.get(), wxsFormat(L"Event completed in %ums",
				(u32)(((qpc_end-m_qpc_Start)*1000) / GetTickFrequency())).wc_str()
			);

			synclock.Acquire();
			m_qpc_Start = 0;		// lets the main thread know the message completed.
		}
		else
		{
			pxEvtLog.Write( this, deleteMe.get(), L"Skipping Event: %s" );
			deleteMe->PostResult();
		}
	}
}

void pxEvtQueue::ProcessIdleEvents()
{
	ProcessEvents( m_idleEvents, true );
}

void pxEvtQueue::ProcessPendingEvents()
{
	ProcessEvents( m_pendingEvents );
}

// This method is provided for wxWidgets API conformance.  I like to use PostEvent instead
// since it's reminiscent of PostMessage in Windows (and behaves rather similarly).
void pxEvtQueue::AddPendingEvent( SysExecEvent& evt )
{
	PostEvent( evt );
}

// Adds an event to the event queue in non-blocking fashion.  The thread executing this
// event queue will be woken up if it's idle/sleeping.
// IMPORTANT:  The pointer version of this function will *DELETE* the event object passed
// to it automatically when the event has been executed.  If you are using a scoped event
// you should use the Reference/Handle overload instead!
//
void pxEvtQueue::PostEvent( SysExecEvent* evt )
{
	std::unique_ptr<SysExecEvent> sevt(evt);
	if( !sevt ) return;

	if( m_Quitting )
	{
		sevt->PostResult();
		return;
	}

	ScopedLock synclock( m_mtx_pending );
	
	pxEvtLog.Write( this, evt, pxsFmt(L"Posting event! (pending=%d, idle=%d)", m_pendingEvents.size(), m_idleEvents.size()) );

	m_pendingEvents.push_back( sevt.release() );
	if( m_pendingEvents.size() == 1)
		m_wakeup.Post();
}

void pxEvtQueue::PostEvent( const SysExecEvent& evt )
{
	PostEvent( evt.Clone() );
}

void pxEvtQueue::PostIdleEvent( SysExecEvent* evt )
{
	if( !evt ) return;

	if( m_Quitting )
	{
		evt->PostResult();
		return;
	}

	ScopedLock synclock( m_mtx_pending );

	pxEvtLog.Write( this, evt, pxsFmt(L"Posting event! (pending=%d, idle=%d) [idle]", m_pendingEvents.size(), m_idleEvents.size()) );

	if( m_pendingEvents.empty() )
	{
		m_pendingEvents.push_back( evt );
		m_wakeup.Post();
	}
	else
		m_idleEvents.push_back( evt );
}

void pxEvtQueue::PostIdleEvent( const SysExecEvent& evt )
{
	PostIdleEvent( evt.Clone() );
}

void pxEvtQueue::ProcessEvent( SysExecEvent& evt )
{
	if( wxThread::GetCurrentId() != m_OwnerThreadId )
	{
		SynchronousActionState sync;
		evt.SetSyncState( sync );
		PostEvent( evt );
		sync.WaitForResult();
	}
	else
		evt._DoInvokeEvent();
}

void pxEvtQueue::ProcessEvent( SysExecEvent* evt )
{
	if( !evt ) return;

	if( wxThread::GetCurrentId() != m_OwnerThreadId )
	{
		SynchronousActionState sync;
		evt->SetSyncState( sync );
		PostEvent( evt );
		sync.WaitForResult();
	}
	else
	{
		std::unique_ptr<SysExecEvent> deleteMe(evt);
		deleteMe->_DoInvokeEvent();
	}
}

bool pxEvtQueue::Rpc_TryInvokeAsync( FnType_Void* method, const wxChar* traceName )
{
	if( wxThread::GetCurrentId() != m_OwnerThreadId )
	{
		PostEvent( new SysExecEvent_MethodVoid(method, traceName) );
		return true;
	}

	return false;
}

bool pxEvtQueue::Rpc_TryInvoke( FnType_Void* method, const wxChar* traceName )
{
	if( wxThread::GetCurrentId() != m_OwnerThreadId )
	{
		SynchronousActionState sync;
		SysExecEvent_MethodVoid evt(method);
		evt.Critical();
		evt.SetSyncState( sync );
		PostEvent( evt );
		sync.WaitForResult();

		return true;
	}
	
	return false;
}

// This method invokes the derived class Idle implementations (if any) and then enters
// the sleep state until such time that new messages are received.
//
// Extending: Derived classes should override _DoIdle instead, unless it is necessary
// to implement post-wakeup behavior.
//
void pxEvtQueue::Idle()
{
	ProcessIdleEvents();
	_DoIdle();
	m_wakeup.WaitWithoutYield();
}

void pxEvtQueue::SetActiveThread()
{
	m_OwnerThreadId = wxThread::GetCurrentId();
}

// --------------------------------------------------------------------------------------
//  ExecutorThread Implementations
// --------------------------------------------------------------------------------------
ExecutorThread::ExecutorThread(pxEvtQueue* evthandler)
	: m_EvtHandler(evthandler)
{
}

bool ExecutorThread::IsRunning() const
{
	if( !m_EvtHandler ) return false;
	return( !m_EvtHandler->IsShuttingDown() );
}

// Exposes the internal pxEvtQueue::ShutdownQueue API.  See pxEvtQueue for details.
void ExecutorThread::ShutdownQueue()
{
	if( !m_EvtHandler ) return;
	
	if( !m_EvtHandler->IsShuttingDown() )
		m_EvtHandler->ShutdownQueue();

	Block();
}

// Exposes the internal pxEvtQueue::PostEvent API.  See pxEvtQueue for details.
void ExecutorThread::PostEvent( SysExecEvent* evt )
{
	if( !pxAssert( m_EvtHandler ) ) { delete evt; return; }
	m_EvtHandler->PostEvent( evt );
}

void ExecutorThread::PostEvent( const SysExecEvent& evt )
{
	if( !pxAssert( m_EvtHandler ) ) return;
	m_EvtHandler->PostEvent( evt );
}

// Exposes the internal pxEvtQueue::PostIdleEvent API.  See pxEvtQueue for details.
void ExecutorThread::PostIdleEvent( SysExecEvent* evt )
{
	if( !pxAssert( m_EvtHandler ) ) return;
	m_EvtHandler->PostIdleEvent( evt );
}

void ExecutorThread::PostIdleEvent( const SysExecEvent& evt )
{
	if( !pxAssert( m_EvtHandler ) ) return;
	m_EvtHandler->PostIdleEvent( evt );
}

// Exposes the internal pxEvtQueue::ProcessEvent API.  See pxEvtQueue for details.
void ExecutorThread::ProcessEvent( SysExecEvent* evt )
{
	if( m_EvtHandler )
		m_EvtHandler->ProcessEvent( evt );
	else
	{
		std::unique_ptr<SysExecEvent> deleteMe(evt);
		deleteMe->_DoInvokeEvent();
	}
}

void ExecutorThread::ProcessEvent( SysExecEvent& evt )
{
	if( m_EvtHandler )
		m_EvtHandler->ProcessEvent( evt );
	else
		evt._DoInvokeEvent();
}

void ExecutorThread::OnStart()
{
	//if( !m_ExecutorTimer )
	//	m_ExecutorTimer = new wxTimer( wxTheApp, pxEvt_ThreadTaskTimeout_SysExec );

	m_name		= L"SysExecutor";
	_parent::OnStart();
}

void ExecutorThread::ExecuteTaskInThread()
{
	if( !pxAssertDev( m_EvtHandler, "Gimme a damn Event Handler first, object whore." ) ) return;

	m_EvtHandler->SetActiveThread();

	while( true )
	{
		if( !pxAssertDev( m_EvtHandler, "Event handler has been deallocated during SysExecutor thread execution." ) ) return;

		m_EvtHandler->Idle();
		m_EvtHandler->ProcessPendingEvents();
		if( m_EvtHandler->IsShuttingDown() ) break;
	}
}

void ExecutorThread::OnCleanupInThread()
{
	//wxGetApp().PostCommand( m_task, pxEvt_ThreadTaskComplete );
	_parent::OnCleanupInThread();
}

// --------------------------------------------------------------------------------------
//  Threaded Event Handlers (Pcsx2App)
// --------------------------------------------------------------------------------------

// This event is called when the SysExecutorThread's timer triggers, which means the
// VM/system task has taken an oddly long period of time to complete. The task is able
// to invoke a modal dialog from here that will grant the user some options for handling
// the unresponsive task.
void Pcsx2App::OnSysExecutorTaskTimeout( wxTimerEvent& evt )
{
	if( !SysExecutorThread.IsRunning() ) return;

	//BaseThreadedInvocation* task = SysExecutorThread.GetTask();
	//if( !task ) return;

	//task->ShowModalStatus();
}
