/*  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 "AppSaveStates.h"
#include "GSFrame.h"

#include <wx/dir.h>
#include <wx/file.h>

#include "Plugins.h"
#include "GS.h"
#include "AppConfig.h"

using namespace Threading;

// The GS plugin needs to be opened to save/load the state during plugin configuration, but
// the window shouldn't. This blocks it. :)
static bool s_DisableGsWindow = false;

__aligned16 AppCorePlugins CorePlugins;

SysCorePlugins& GetCorePlugins()
{
	return CorePlugins;
}


// --------------------------------------------------------------------------------------
//  CorePluginsEvent
// --------------------------------------------------------------------------------------
class CorePluginsEvent : public pxActionEvent
{
	typedef pxActionEvent _parent;

protected:
	PluginEventType		m_evt;

public:
	virtual ~CorePluginsEvent() = default;
	CorePluginsEvent* Clone() const { return new CorePluginsEvent( *this ); }

	explicit CorePluginsEvent( PluginEventType evt, SynchronousActionState* sema=NULL )
		: pxActionEvent( sema )
	{
		m_evt = evt;
	}

	explicit CorePluginsEvent( PluginEventType evt, SynchronousActionState& sema )
		: pxActionEvent( sema )
	{
		m_evt = evt;
	}

	CorePluginsEvent( const CorePluginsEvent& src )
		: pxActionEvent( src )
	{
		m_evt = src.m_evt;
	}
	
	void SetEventType( PluginEventType evt ) { m_evt = evt; }
	PluginEventType GetEventType() { return m_evt; }

protected:
	void InvokeEvent()
	{
		sApp.DispatchEvent( m_evt );
	}
};

static void PostPluginStatus( PluginEventType pevt )
{
	sApp.PostAction( CorePluginsEvent( pevt ) );
}

static void ConvertPluginFilenames( wxString (&passins)[PluginId_Count] )
{
	const PluginInfo* pi = tbl_PluginInfo; do
	{
		passins[pi->id] = wxGetApp().Overrides.Filenames[pi->id].GetFullPath();

		if( passins[pi->id].IsEmpty() || !wxFileExists( passins[pi->id] ) )
			passins[pi->id] = g_Conf->FullpathTo( pi->id );
	} while( ++pi, pi->shortname != NULL );
}

typedef void (AppCorePlugins::*FnPtr_AppPluginManager)();
typedef void (AppCorePlugins::*FnPtr_AppPluginPid)( PluginsEnum_t pid );

// --------------------------------------------------------------------------------------
//  SysExecEvent_AppPluginManager
// --------------------------------------------------------------------------------------
class SysExecEvent_AppPluginManager : public SysExecEvent
{
protected:
	FnPtr_AppPluginManager	m_method;

public:
	wxString GetEventName() const { return L"CorePluginsMethod"; }
	virtual ~SysExecEvent_AppPluginManager() = default;
	SysExecEvent_AppPluginManager* Clone() const { return new SysExecEvent_AppPluginManager( *this ); }

	SysExecEvent_AppPluginManager( FnPtr_AppPluginManager method )
	{
		m_method = method;
	}

protected:
	void InvokeEvent()
	{
		if( m_method ) (CorePlugins.*m_method)();
	}
};

// --------------------------------------------------------------------------------------
//  LoadSinglePluginEvent
// --------------------------------------------------------------------------------------
class LoadSinglePluginEvent : public pxActionEvent
{
	typedef pxActionEvent _parent;
	wxDECLARE_DYNAMIC_CLASS_NO_ASSIGN(LoadSinglePluginEvent);

protected:
	wxString		m_filename;
	PluginsEnum_t	m_pid;

public:
	LoadSinglePluginEvent(const LoadSinglePluginEvent&) = default;
	virtual ~LoadSinglePluginEvent() = default;
	virtual LoadSinglePluginEvent *Clone() const { return new LoadSinglePluginEvent(*this); }

	LoadSinglePluginEvent( PluginsEnum_t pid = PluginId_GS, const wxString& filename=wxEmptyString )
		: m_filename( filename )
	{
		m_pid = pid;
	}

protected:
	void InvokeEvent()
	{
		GetCorePlugins().Load( m_pid, m_filename );
	}
};

// --------------------------------------------------------------------------------------
//  SinglePluginMethodEvent
// --------------------------------------------------------------------------------------
class SinglePluginMethodEvent : public pxActionEvent
{
	typedef pxActionEvent _parent;
	wxDECLARE_DYNAMIC_CLASS_NO_ASSIGN(SinglePluginMethodEvent);

protected:
	PluginsEnum_t			m_pid;
	FnPtr_AppPluginPid		m_method;

public:
	SinglePluginMethodEvent(const SinglePluginMethodEvent&) = default;
	virtual ~SinglePluginMethodEvent() = default;
	virtual SinglePluginMethodEvent *Clone() const { return new SinglePluginMethodEvent(*this); }

	SinglePluginMethodEvent( FnPtr_AppPluginPid method=NULL,  PluginsEnum_t pid = PluginId_GS )
	{
		m_pid		= pid;
		m_method	= method;
	}

protected:
	void InvokeEvent()
	{
		//GetCorePlugins().Unload( m_pid );
		if( m_method ) (CorePlugins.*m_method)( m_pid );
	}
};

wxIMPLEMENT_DYNAMIC_CLASS( LoadSinglePluginEvent,	 pxActionEvent );
wxIMPLEMENT_DYNAMIC_CLASS( SinglePluginMethodEvent, pxActionEvent );

// --------------------------------------------------------------------------------------
//  AppCorePlugins
// --------------------------------------------------------------------------------------
//
// Thread Affinity Notes:
//  It's important to ensure that Load/Unload/Init/Shutdown are all called from the
//  MAIN/UI Thread only.  Those APIs are allowed to issue modal popups, and as such
//  are only safe when invoked form the UI thread.  Under windows the popups themselves
//  will typically work from any thread, but some common control activities will fail
//  (such as opening the browser windows).  On Linux it's probably just highly unsafe, period.
//
//  My implementation is to execute the main Load/Init/Shutdown/Unload procedure on the
//  SysExecutor, and then dispatch each individual plugin to the main thread.  This keeps
//  the main thread from being completely busy while plugins are loaded and initialized.
//  (responsiveness is bliss!!) -- air
//

static void _SetSettingsFolder()
{
	if (wxGetApp().Rpc_TryInvoke( _SetSettingsFolder )) return;
	CorePlugins.SetSettingsFolder( GetSettingsFolder().ToString() );
}

static void _SetLogFolder()
{
	if (wxGetApp().Rpc_TryInvoke( _SetLogFolder )) return;
	CorePlugins.SetLogFolder( GetLogFolder().ToString() );
}

void AppCorePlugins::Load( PluginsEnum_t pid, const wxString& srcfile )
{ 
	if( !wxThread::IsMain() )
	{
		Sleep( 5 );
		LoadSinglePluginEvent evt( pid, srcfile );
		wxGetApp().ProcessAction( evt);
		return;
	}
	
	_parent::Load( pid, srcfile );
}

void AppCorePlugins::Unload( PluginsEnum_t pid )
{
	if( !wxThread::IsMain() )
	{
		Sleep( 5 );
		SinglePluginMethodEvent evt( &AppCorePlugins::Unload, pid );
		wxGetApp().ProcessAction( evt );
		return;
	}

	_parent::Unload( pid );
}

void AppCorePlugins::Load( const wxString (&folders)[PluginId_Count] )
{
	if( !pxAssert(!AreLoaded()) ) return;

	_SetLogFolder();
	SendLogFolder();

	_SetSettingsFolder();
	SendSettingsFolder();

	_parent::Load( folders );
	PostPluginStatus( CorePlugins_Loaded );
}

void AppCorePlugins::Unload()
{
	_parent::Unload();
	PostPluginStatus( CorePlugins_Unloaded );
}

void AppCorePlugins::Init( PluginsEnum_t pid )
{
	if( !wxTheApp ) return;

	if( !wxThread::IsMain() )
	{
		SinglePluginMethodEvent evt(&AppCorePlugins::Init, pid);
		wxGetApp().ProcessAction( evt );
		Sleep( 5 );
		return;
	}

	_parent::Init( pid );
}

void AppCorePlugins::Shutdown( PluginsEnum_t pid )
{
	if( !wxThread::IsMain() && wxTheApp )
	{
		SinglePluginMethodEvent evt( &AppCorePlugins::Shutdown, pid );
		wxGetApp().ProcessAction( evt );
		Sleep( 5 );
		return;
	}

	_parent::Shutdown( pid );
}

bool AppCorePlugins::Init()
{
	if( !NeedsInit() ) return false;

    _SetLogFolder();
	SendLogFolder();

	_SetSettingsFolder();
	SendSettingsFolder();

	if (_parent::Init())
	{
		PostPluginStatus( CorePlugins_Init );
		return true;
	}
	return false;
}

bool AppCorePlugins::Shutdown()
{
	if (_parent::Shutdown())
	{
		PostPluginStatus( CorePlugins_Shutdown );
		return true;
	}
	return false;
}

void AppCorePlugins::Close()
{
	AffinityAssert_AllowFrom_CoreThread();

	if( !NeedsClose() ) return;

	PostPluginStatus( CorePlugins_Closing );
	_parent::Close();
	PostPluginStatus( CorePlugins_Closed );
}

void AppCorePlugins::Open()
{
	AffinityAssert_AllowFrom_CoreThread();

	/*if( !GetSysExecutorThread().IsSelf() )
	{
		GetSysExecutorThread().ProcessEvent( new SysExecEvent_AppPluginManager( &AppCorePlugins::Open ) );
		return;
	}*/

    SetLogFolder( GetLogFolder().ToString() );
	SetSettingsFolder( GetSettingsFolder().ToString() );

	if( !NeedsOpen() ) return;

	PostPluginStatus( CorePlugins_Opening );
	_parent::Open();
	PostPluginStatus( CorePlugins_Opened );
}

// Yay, this plugin is guaranteed to always be opened first and closed last.
bool AppCorePlugins::OpenPlugin_GS()
{
	if( GSopen2 && !s_DisableGsWindow )
	{
		sApp.OpenGsPanel();
	}

	bool retval = _parent::OpenPlugin_GS();

	GSsetVsync(EmuConfig.GS.GetVsync());

	return retval;
}

// Yay, this plugin is guaranteed to always be opened first and closed last.
void AppCorePlugins::ClosePlugin_GS()
{
	_parent::ClosePlugin_GS();
	if( CloseViewportWithPlugins && GetMTGS().IsSelf() && GSopen2 ) sApp.CloseGsPanel();
}


// --------------------------------------------------------------------------------------
//  LoadCorePluginsEvent
// --------------------------------------------------------------------------------------
class LoadCorePluginsEvent : public SysExecEvent
{
protected:
	wxString			m_folders[PluginId_Count];

public:
	LoadCorePluginsEvent()
	{
		ConvertPluginFilenames( m_folders );
	}
	
	wxString GetEventName() const
	{
		return L"LoadCorePlugins";
	}

	wxString GetEventMessage() const
	{
		return _("Loading PS2 system plugins...");
	}

protected:
	void InvokeEvent()
	{
		CorePlugins.Load( m_folders );
	}
	~LoadCorePluginsEvent() = default;
};

// --------------------------------------------------------------------------------------
//  Public API / Interface
// --------------------------------------------------------------------------------------

int EnumeratePluginsInFolder(const wxDirName& searchpath, wxArrayString* dest)
{
	if (!searchpath.Exists()) return 0;

	std::unique_ptr<wxArrayString> placebo;
	wxArrayString* realdest = dest;
	if (realdest == NULL)
	{
		placebo = std::make_unique<wxArrayString>();
		realdest = placebo.get();
	}

#ifdef __WXMSW__
	// Windows pretty well has a strict "must end in .dll" rule.
	wxString pattern( L"*%s" );
#else
	// Other platforms seem to like to version their libs after the .so extension:
	//    blah.so.3.1.fail?
	wxString pattern( L"*%s*" );
#endif

	wxDir::GetAllFiles( searchpath.ToString(), realdest, pxsFmt( pattern, WX_STR(wxDynamicLibrary::GetDllExt())), wxDIR_FILES );

	// SECURITY ISSUE:  (applies primarily to Windows, but is a good idea on any platform)
	//   The search folder order for plugins can vary across operating systems, and in some poorly designed
	//   cases (old versions of windows), the search order is a security hazard because it does not
	//   search where you might really expect.  In our case wedo not want *any* searching.  The only
	//   plugins we want to load are the ones we found in the directly the user specified, so make
	//   sure all paths are FULLY QUALIFIED ABSOLUTE PATHS.
	//
	// (for details, read: http://msdn.microsoft.com/en-us/library/ff919712.aspx )

	for (uint i=0; i<realdest->GetCount(); ++i )
	{
		(*realdest)[i] = Path::MakeAbsolute((*realdest)[i]);
	}

	return realdest->GetCount();
}

// Posts a message to the App to reload plugins.  Plugins are loaded via a background thread
// which is started on a pending event, so don't expect them to be ready  "right now."
// If plugins are already loaded, onComplete is invoked, and the function returns with no
// other actions performed.
void LoadPluginsPassive()
{
	AffinityAssert_AllowFrom_MainUI();

	// Plugins already loaded?
	if( !CorePlugins.AreLoaded() )
	{
		wxGetApp().SysExecutorThread.PostEvent( new LoadCorePluginsEvent() );
	}
}

static void _LoadPluginsImmediate()
{
	if( CorePlugins.AreLoaded() ) return;

	wxString passins[PluginId_Count];
	ConvertPluginFilenames( passins );
	CorePlugins.Load( passins );
}

void LoadPluginsImmediate()
{
	AffinityAssert_AllowFrom_SysExecutor();
	_LoadPluginsImmediate();
}

// Performs a blocking load of plugins.  If the emulation thread is active, it is shut down
// automatically to prevent race conditions (it depends on plugins).
//
// Exceptions regarding plugin failures will propagate out of this function, so be prepared
// to handle them.
//
// Note that this is not recommended for most situations, but coding improper passive loads
// is probably worse, so if in doubt use this and air will fix it up for you later. :)
//
void ScopedCoreThreadClose::LoadPlugins()
{
	DbgCon.WriteLn("(ScopedCoreThreadClose) Loading plugins!");
	_LoadPluginsImmediate();
}


class SysExecEvent_UnloadPlugins : public SysExecEvent
{
public:
	wxString GetEventName() const { return L"UnloadPlugins"; }

	virtual ~SysExecEvent_UnloadPlugins() = default;
	SysExecEvent_UnloadPlugins* Clone() const { return new SysExecEvent_UnloadPlugins(*this); }

	virtual bool AllowCancelOnExit() const { return false; }
	virtual bool IsCriticalEvent() const { return true; }

	void InvokeEvent()
	{
		CoreThread.Cancel();
		CorePlugins.Unload();
	}
};

class SysExecEvent_ShutdownPlugins : public SysExecEvent
{
public:
	wxString GetEventName() const { return L"ShutdownPlugins"; }

	virtual ~SysExecEvent_ShutdownPlugins() = default;
	SysExecEvent_ShutdownPlugins* Clone() const { return new SysExecEvent_ShutdownPlugins(*this); }

	virtual bool AllowCancelOnExit() const { return false; }
	virtual bool IsCriticalEvent() const { return true; }

	void InvokeEvent()
	{
		CoreThread.Cancel();
		CorePlugins.Shutdown();
	}
};

void UnloadPlugins()
{
	GetSysExecutorThread().PostEvent( new SysExecEvent_UnloadPlugins() );
}

void ShutdownPlugins()
{
	GetSysExecutorThread().PostEvent( new SysExecEvent_ShutdownPlugins() );
}

void SysExecEvent_SaveSinglePlugin::InvokeEvent()
{
	s_DisableGsWindow = true;		// keeps the GS window smooth by avoiding closing the window

	ScopedCoreThreadPause paused_core;
	//_LoadPluginsImmediate();

	if( CorePlugins.AreLoaded() )
	{
		std::unique_ptr<VmStateBuffer> plugstore;

		if( CoreThread.HasActiveMachine() )
		{
			Console.WriteLn( Color_Green, L"Suspending single plugin: " + tbl_PluginInfo[m_pid].GetShortname() );
			plugstore = std::make_unique<VmStateBuffer>(L"StateCopy_SinglePlugin");
			memSavingState save( plugstore.get() );
			GetCorePlugins().Freeze( m_pid, save );
		}
			
		GetCorePlugins().Close( m_pid );
		_post_and_wait( paused_core );

		if( plugstore )
		{
			Console.WriteLn( Color_Green, L"Recovering single plugin: " + tbl_PluginInfo[m_pid].GetShortname() );
			memLoadingState load( plugstore.get() );
			GetCorePlugins().Freeze( m_pid, load );
			// GS plugin suspend / resume hack. Removed in r4363, hopefully never to return :p
			//GetCorePlugins().Close( m_pid );		// hack for stupid GS plugins.
		}
	}

	s_DisableGsWindow = false;
	paused_core.AllowResume();
}

void SysExecEvent_SaveSinglePlugin::CleanupEvent()
{
	s_DisableGsWindow = false;
	_parent::CleanupEvent();
}
