/*  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 <wx/dynlib.h>
#include <wx/dir.h>

#include "App.h"
#include "AppSaveStates.h"
#include "Plugins.h"
#include "ConfigurationPanels.h"

#include "Dialogs/ModalPopups.h"

#include "Utilities/ThreadingDialogs.h"
#include "Utilities/SafeArray.inl"

// Allows us to force-disable threading for debugging/troubleshooting
static const bool DisableThreading =
#ifdef __LINUX__
	true;		// linux appears to have threading issues with loadlibrary.
#else
	false;
#endif

using namespace pxSizerFlags;
using namespace Threading;

BEGIN_DECLARE_EVENT_TYPES()
	DECLARE_EVENT_TYPE(pxEvt_EnumeratedNext, -1)
	DECLARE_EVENT_TYPE(pxEvt_EnumerationFinished, -1)
	DECLARE_EVENT_TYPE(pxEVT_ShowStatusBar, -1)
END_DECLARE_EVENT_TYPES()

DEFINE_EVENT_TYPE(pxEvt_EnumeratedNext);
DEFINE_EVENT_TYPE(pxEvt_EnumerationFinished);
DEFINE_EVENT_TYPE(pxEVT_ShowStatusBar);

typedef s32		(CALLBACK* TestFnptr)();
typedef void	(CALLBACK* ConfigureFnptr)();

static const wxString failed_separator( L"--------   Unsupported Plugins  --------" );

namespace Exception
{
	class NotEnumerablePlugin : public BadStream
	{
	public:
		DEFINE_STREAM_EXCEPTION( NotEnumerablePlugin, BadStream );

		wxString FormatDiagnosticMessage() const
		{
			FastFormatUnicode retval;
			retval.Write("File is not a PCSX2 plugin");
			_formatDiagMsg(retval);
			return retval;
		}
	};
}

// --------------------------------------------------------------------------------------
//  PluginEnumerator class
// --------------------------------------------------------------------------------------
class PluginEnumerator
{
protected:
	wxString			m_plugpath;
	wxDynamicLibrary	m_plugin;

	_PS2EgetLibType		m_GetLibType;
	_PS2EgetLibName		m_GetLibName;
	_PS2EgetLibVersion2	m_GetLibVersion2;

	u32 m_type;

public:

	// Constructor!
	//
	// Possible Exceptions:
	//   BadStream				- thrown if the provided file is simply not a loadable DLL.
	//   NotEnumerablePlugin	- thrown if the DLL is not a PCSX2 plugin, or if it's of an unsupported version.
	//
	PluginEnumerator( const wxString& plugpath )
		: m_plugpath( plugpath )
	{
		if( !m_plugin.Load( m_plugpath ) )
			throw Exception::BadStream( m_plugpath ).SetBothMsgs(L"File is not a valid dynamic library.");

		wxDoNotLogInThisScope please;
		m_GetLibType		= (_PS2EgetLibType)m_plugin.GetSymbol( L"PS2EgetLibType" );
		m_GetLibName		= (_PS2EgetLibName)m_plugin.GetSymbol( L"PS2EgetLibName" );
		m_GetLibVersion2	= (_PS2EgetLibVersion2)m_plugin.GetSymbol( L"PS2EgetLibVersion2" );

		if( m_GetLibType == NULL || m_GetLibName == NULL || m_GetLibVersion2 == NULL)
			throw Exception::NotEnumerablePlugin( m_plugpath );

		m_type = m_GetLibType();
	}

	bool CheckVersion( PluginsEnum_t pluginTypeIndex ) const
	{
		const PluginInfo& info( tbl_PluginInfo[pluginTypeIndex] );
		if( m_type & info.typemask )
		{
			int version = m_GetLibVersion2( info.typemask );
			if ( ((version >> 16)&0xff) == tbl_PluginInfo[pluginTypeIndex].version )
				return true;

			Console.Warning("%s Plugin %s:  Version %x != %x", info.shortname, m_plugpath.c_str(), 0xff&(version >> 16), info.version);
		}
		return false;
	}

	bool Test( int pluginTypeIndex ) const
	{
		// all test functions use the same parameterless API, so just pick one arbitrarily (I pick PAD!)
		TestFnptr testfunc = (TestFnptr)m_plugin.GetSymbol( fromUTF8( tbl_PluginInfo[pluginTypeIndex].shortname ) + L"test" );
		if( testfunc == NULL ) return false;
		return (testfunc() == 0);
	}

	wxString GetName() const
	{
		pxAssert( m_GetLibName != NULL );
		return fromUTF8(m_GetLibName());
	}

	void GetVersionString( wxString& dest, int pluginTypeIndex ) const
	{
		const PluginInfo& info( tbl_PluginInfo[pluginTypeIndex] );
		int version = m_GetLibVersion2( info.typemask );
		dest.Printf( L"%d.%d.%d", (version>>8)&0xff, version&0xff, (version>>24)&0xff );
	}
};

// --------------------------------------------------------------------------------------
//  ApplyPluginsDialog
// --------------------------------------------------------------------------------------
class ApplyPluginsDialog : public WaitForTaskDialog
{
	DECLARE_DYNAMIC_CLASS_NO_COPY(ApplyPluginsDialog)

	typedef wxDialogWithHelpers _parent;

protected:
	BaseApplicableConfigPanel*		m_panel;

public:
	ApplyPluginsDialog( BaseApplicableConfigPanel* panel=NULL );
	virtual ~ApplyPluginsDialog() throw() {}

	BaseApplicableConfigPanel* GetApplicableConfigPanel() const { return m_panel; }
};


// --------------------------------------------------------------------------------------
//  ApplyOverValidStateEvent
// --------------------------------------------------------------------------------------
class ApplyOverValidStateEvent : public pxActionEvent
{
	//DeclareNoncopyableObject( ApplyOverValidStateEvent );
	typedef pxActionEvent _parent;

protected:
	ApplyPluginsDialog*		m_owner;

public:
	ApplyOverValidStateEvent( ApplyPluginsDialog* owner=NULL )
	{
		m_owner = owner;
	}

	virtual ~ApplyOverValidStateEvent() throw() { }
	virtual ApplyOverValidStateEvent *Clone() const { return new ApplyOverValidStateEvent(*this); }

protected:
	void InvokeEvent();
};


// --------------------------------------------------------------------------------------
//  SysExecEvent_ApplyPlugins
// --------------------------------------------------------------------------------------
class SysExecEvent_ApplyPlugins : public SysExecEvent
{
	typedef SysExecEvent _parent;

protected:
	ApplyPluginsDialog*	m_dialog;
	
public:
	wxString GetEventName() const { return L"PluginSelectorPanel::ApplyPlugins"; }

	virtual ~SysExecEvent_ApplyPlugins() throw() {}
	SysExecEvent_ApplyPlugins* Clone() const { return new SysExecEvent_ApplyPlugins( *this ); }

	SysExecEvent_ApplyPlugins( ApplyPluginsDialog* parent, SynchronousActionState& sync )
		: SysExecEvent( &sync )
	{
		m_dialog = parent;
	}

protected:
	void InvokeEvent();
	void CleanupEvent();
	
	void PostFinishToDialog();
};

// --------------------------------------------------------------------------------------
//  ApplyPluginsDialog Implementations
// --------------------------------------------------------------------------------------
IMPLEMENT_DYNAMIC_CLASS(ApplyPluginsDialog, WaitForTaskDialog)

ApplyPluginsDialog::ApplyPluginsDialog( BaseApplicableConfigPanel* panel )
	: WaitForTaskDialog( _("Applying settings...") )
{
	GetSysExecutorThread().PostEvent( new SysExecEvent_ApplyPlugins( this, m_sync ) );
}

// --------------------------------------------------------------------------------------
//  ApplyOverValidStateEvent Implementations
// --------------------------------------------------------------------------------------
void ApplyOverValidStateEvent::InvokeEvent()
{
	wxDialogWithHelpers dialog( m_owner, _("Shutdown PS2 virtual machine?") );

	dialog += dialog.Heading( pxE( L"Warning!  Changing plugins requires a complete shutdown and reset of the PS2 virtual machine. PCSX2 will attempt to save and restore the state, but if the newly selected plugins are incompatible the recovery may fail, and current progress will be lost.\n\nAre you sure you want to apply settings now?"
	) );

	int result = pxIssueConfirmation( dialog, MsgButtons().OK().Cancel(), L"PluginSelector.ConfirmShutdown" );

	if( result == wxID_CANCEL )
		throw Exception::CannotApplySettings( m_owner->GetApplicableConfigPanel() ).Quiet()
			.SetDiagMsg(L"Cannot apply settings: canceled by user because plugins changed while the emulation state was active.");
}

// --------------------------------------------------------------------------------------
//  SysExecEvent_ApplyPlugins Implementations
// --------------------------------------------------------------------------------------
void SysExecEvent_ApplyPlugins::InvokeEvent()
{
	ScopedCoreThreadPause paused_core;

	ScopedPtr< VmStateBuffer > buffer;
	
	if( SysHasValidState() )
	{
		paused_core.AllowResume();
		ApplyOverValidStateEvent aEvt( m_dialog );
		wxGetApp().ProcessEvent( aEvt );
		paused_core.DisallowResume();

		// FIXME : We only actually have to save plugins here, except the recovery code
		// in SysCoreThread isn't quite set up yet to handle that (I think...) --air

		memSavingState saveme( *(buffer.Reassign(new VmStateBuffer(L"StateBuffer_ApplyNewPlugins"))) );
		
		saveme.FreezeAll();
	}

	ScopedCoreThreadClose closed_core;

	CorePlugins.Shutdown();
	CorePlugins.Unload();
	LoadPluginsImmediate();
	CorePlugins.Init();
	
	if( buffer ) CoreThread.UploadStateCopy( *buffer );

	PostFinishToDialog();

	closed_core.AllowResume();
	paused_core.AllowResume();
}

void SysExecEvent_ApplyPlugins::PostFinishToDialog()
{
	if( !m_dialog ) return;
	wxCommandEvent tevt( pxEvt_ThreadedTaskComplete );
	m_dialog->GetEventHandler()->AddPendingEvent( tevt );
	m_dialog = NULL;
}

void SysExecEvent_ApplyPlugins::CleanupEvent()
{
	PostFinishToDialog();	
	_parent::CleanupEvent();
}

// --------------------------------------------------------------------------------------
//  PluginSelectorPanel::StatusPanel  implementations
// --------------------------------------------------------------------------------------

Panels::PluginSelectorPanel::StatusPanel::StatusPanel( wxWindow* parent )
	: wxPanelWithHelpers( parent, wxVERTICAL )
	, m_gauge( *new wxGauge( this, wxID_ANY, 10 ) )
	, m_label( *new wxStaticText( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE | wxST_NO_AUTORESIZE ) )
{
	m_progress = 0;

	m_gauge.SetToolTip( _("I'm givin' her all she's got, Captain!") );

	*this	+= Heading(_( "Enumerating available plugins..." )).Bold()	| StdExpand();
	*this	+= m_gauge	| pxExpand.Border( wxLEFT | wxRIGHT, 32 );
	*this	+= m_label	| StdExpand();

	Fit();
}

void Panels::PluginSelectorPanel::StatusPanel::SetGaugeLength( int len )
{
	m_gauge.SetRange( len );
}

void Panels::PluginSelectorPanel::StatusPanel::AdvanceProgress( const wxString& msg )
{
	m_label.SetLabel( msg );
	m_gauge.SetValue( ++m_progress );
}

void Panels::PluginSelectorPanel::StatusPanel::Reset()
{
	m_gauge.SetValue( m_progress = 0 );
	m_label.SetLabel( wxEmptyString );
}

// Id for all Configure buttons (any non-negative arbitrary integer will do)
static const int ButtonId_Configure = 51;

// --------------------------------------------------------------------------------------
//  PluginSelectorPanel::ComboBoxPanel  implementations
// --------------------------------------------------------------------------------------
Panels::PluginSelectorPanel::ComboBoxPanel::ComboBoxPanel( PluginSelectorPanel* parent )
	: wxPanelWithHelpers( parent, wxVERTICAL )
	, m_FolderPicker(	*new DirPickerPanel( this, FolderId_Plugins,
		_("Plugins Search Path:"),
		_("Select a folder with PCSX2 plugins") )
	)
{
	wxFlexGridSizer& s_plugin( *new wxFlexGridSizer( PluginId_Count, 3, 16, 10 ) );
	s_plugin.SetFlexibleDirection( wxHORIZONTAL );
	s_plugin.AddGrowableCol( 1 );		// expands combo boxes to full width.

	const PluginInfo* pi = tbl_PluginInfo; do
	{
		const PluginsEnum_t pid = pi->id;

		m_combobox[pid] = new wxComboBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY );

		m_configbutton[pid] = new wxButton( this, ButtonId_Configure, _("Configure...") );
		m_configbutton[pid]->SetClientData( (void*)(int)pid );

		s_plugin	+= Label( pi->GetShortname() )	| pxBorder( wxTOP | wxLEFT, 2 );
		s_plugin	+= m_combobox[pid]				| pxExpand;
		s_plugin	+= m_configbutton[pid];
	} while( ++pi, pi->shortname != NULL );

//	if (InstallationMode != InstallMode_Portable)
		m_FolderPicker.SetStaticDesc( _("Click the Browse button to select a different folder for PCSX2 plugins.") );

	*this	+= s_plugin			| pxExpand;
	*this	+= 6;
	*this	+= m_FolderPicker	| StdExpand();
}

void Panels::PluginSelectorPanel::ComboBoxPanel::Reset()
{
	for( int i=0; i<PluginId_Count; ++i )
	{
		m_combobox[i]->Clear();
		m_combobox[i]->SetSelection( wxNOT_FOUND );
		m_combobox[i]->SetValue( wxEmptyString );

		m_configbutton[i]->Disable();
	}
}

void Panels::PluginSelectorPanel::DispatchEvent( const PluginEventType& evt )
{
	if( (evt != CorePlugins_Loaded) && (evt != CorePlugins_Unloaded) ) return;		// everything else we don't care about

	if( IsBeingDeleted() ) return;

	const PluginInfo* pi = tbl_PluginInfo; do
	{
		wxComboBox& box( m_ComponentBoxes->Get(pi->id) );
		int sel = box.GetSelection();
		if( sel == wxNOT_FOUND ) continue;

		m_ComponentBoxes->GetConfigButton(pi->id).Enable(
			(m_FileList==NULL || m_FileList->Count() == 0) ? false :
			g_Conf->FullpathMatchTest( pi->id,(*m_FileList)[((int)box.GetClientData(sel))] )
		);
	} while( ++pi, pi->shortname != NULL );

}

Panels::PluginSelectorPanel::PluginSelectorPanel( wxWindow* parent )
	: BaseSelectorPanel( parent )
{
	m_StatusPanel		= new StatusPanel( this );
	m_ComponentBoxes	= new ComboBoxPanel( this );

	// note: the status panel is a floating window, so that it can be positioned in the
	// center of the dialog after it's been fitted to the contents.

	*this	+= m_ComponentBoxes | StdExpand().ReserveSpaceEvenIfHidden();

	m_StatusPanel->Hide();
	m_ComponentBoxes->Hide();

	// refresh button used for diagnostics... (don't think there's a point to having one otherwise) --air
	//wxButton* refresh = new wxButton( this, wxID_ANY, L"Refresh" );
	//s_main.Add( refresh );
	//Connect( refresh->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PluginSelectorPanel::OnRefresh ) );

	Connect( pxEvt_EnumeratedNext,				wxCommandEventHandler( PluginSelectorPanel::OnProgress ) );
	Connect( pxEvt_EnumerationFinished,			wxCommandEventHandler( PluginSelectorPanel::OnEnumComplete ) );
	Connect( pxEVT_ShowStatusBar,				wxCommandEventHandler( PluginSelectorPanel::OnShowStatusBar ) );
	Connect( wxEVT_COMMAND_COMBOBOX_SELECTED,	wxCommandEventHandler( PluginSelectorPanel::OnPluginSelected ) );

	Connect( ButtonId_Configure, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PluginSelectorPanel::OnConfigure_Clicked ) );
}

Panels::PluginSelectorPanel::~PluginSelectorPanel() throw()
{
	CancelRefresh();		// in case the enumeration thread is currently refreshing...
}

void Panels::PluginSelectorPanel::AppStatusEvent_OnSettingsApplied()
{
	m_ComponentBoxes->GetDirPicker().Reset();
}

static wxString GetApplyFailedMsg()
{
	return pxsFmt( pxE( L"All plugins must have valid selections for %s to run.  If you are unable to make a valid selection due to missing plugins or an incomplete install of %s, then press Cancel to close the Configuration panel."
	), pxGetAppName().c_str(), pxGetAppName().c_str() );
}

void Panels::PluginSelectorPanel::Apply()
{
	// user never entered plugins panel?  Skip application since combo boxes are invalid/uninitialized.
	if( !m_FileList ) return;

	AppConfig curconf( *g_Conf );

	const PluginInfo* pi = tbl_PluginInfo; do
	{
		const PluginsEnum_t pid = pi->id;
		int sel = m_ComponentBoxes->Get(pid).GetSelection();
		if( sel == wxNOT_FOUND )
		{
			wxString plugname( pi->GetShortname() );

			throw Exception::CannotApplySettings( this )
				.SetDiagMsg(pxsFmt( L"PluginSelectorPanel: Invalid or missing selection for the %s plugin.", plugname.c_str()) )
				.SetUserMsg(pxsFmt( _("Please select a valid plugin for the %s."), plugname.c_str() ) + L"\n\n" + GetApplyFailedMsg() );
		}

		g_Conf->BaseFilenames.Plugins[pid] = GetFilename((int)m_ComponentBoxes->Get(pid).GetClientData(sel));
	} while( ++pi, pi->shortname != NULL );

	// ----------------------------------------------------------------------------
	// Make sure folders are up to date, and try to load/reload plugins if needed...

	g_Conf->Folders.ApplyDefaults();

	// Need to unload the current emulation state if the user changed plugins, because
	// the whole plugin system needs to be re-loaded.

	pi = tbl_PluginInfo; do {
		if( g_Conf->FullpathTo( pi->id ) != curconf.FullpathTo( pi->id ) )
			break;
	} while( ++pi, pi->shortname != NULL );

	if( pi->shortname == NULL ) return;		// no plugins changed? nothing left to do!

	// ----------------------------------------------------------------------------
	// Plugin names are not up-to-date -- RELOAD!

	try
	{
		if( wxID_CANCEL == ApplyPluginsDialog( this ).ShowModal() )
			throw Exception::CannotApplySettings( this ).Quiet().SetDiagMsg(L"User canceled plugin load process.");
	}
	catch( Exception::PluginError& ex )
	{
		// Rethrow PluginLoadErrors as a failure to Apply...

		wxString plugname( tbl_PluginInfo[ex.PluginId].GetShortname() );

		throw Exception::CannotApplySettings( this )
			.SetDiagMsg(ex.FormatDiagnosticMessage())
			.SetUserMsg(pxsFmt(
				_("The selected %s plugin failed to load.\n\nReason: %s\n\n"),
				plugname.c_str(), ex.FormatDisplayMessage().c_str()
			) + GetApplyFailedMsg());
	}
}

void Panels::PluginSelectorPanel::CancelRefresh()
{
}

// This method is a callback from the BaseSelectorPanel.  It is called when the page is shown
// and the page's enumerated selections are valid (meaning we should start our enumeration
// thread!)
void Panels::PluginSelectorPanel::DoRefresh()
{
	m_ComponentBoxes->Reset();
	if( !m_FileList )
	{
		wxCommandEvent evt;
		OnEnumComplete( evt );
		return;
	}

	// Disable all controls until enumeration is complete
	m_ComponentBoxes->Hide();

	// (including next button if it's a Wizard)
	wxWindow* forwardButton = GetGrandParent()->FindWindow( wxID_FORWARD );
	if( forwardButton != NULL )
		forwardButton->Disable();

	// Show status bar for plugin enumeration.  Use a pending event so that
	// the window's size can get initialized properly before trying to custom-
	// fit the status panel to it.
	wxCommandEvent evt( pxEVT_ShowStatusBar );
	GetEventHandler()->AddPendingEvent( evt );

	m_EnumeratorThread.Delete() = new EnumThread( *this );

	if( DisableThreading )
		m_EnumeratorThread->DoNextPlugin( 0 );
	else
		m_EnumeratorThread->Start();
}

bool Panels::PluginSelectorPanel::ValidateEnumerationStatus()
{
	if( m_EnumeratorThread ) return true;		// Cant reset file lists while we're busy enumerating...

	bool validated = true;

	// re-enumerate plugins, and if anything changed then we need to wipe
	// the contents of the combo boxes and re-enumerate everything.

	// Impl Note: ScopedPtr used so that resources get cleaned up if an exception
	// occurs during file enumeration.
	ScopedPtr<wxArrayString> pluginlist( new wxArrayString() );

	int pluggers = EnumeratePluginsInFolder( m_ComponentBoxes->GetPluginsPath(), pluginlist );

	if( !m_FileList || (*pluginlist != *m_FileList) )
		validated = false;

	if( pluggers == 0 )
	{
		m_FileList = NULL;
		return validated;
	}

	m_FileList.SwapPtr( pluginlist );

	// set the gague length a little shorter than the plugin count.  2 reasons:
	//  * some of the plugins might be duds.
	//  * on high end machines and Win7, the statusbar lags a lot and never gets to 100% before being hidden.
	
	m_StatusPanel->SetGaugeLength( std::max( 1, (pluggers-1) - (pluggers/8) ) );

	return validated;
}

void Panels::PluginSelectorPanel::OnPluginSelected( wxCommandEvent& evt )
{
	if( IsBeingDeleted() || m_ComponentBoxes->IsBeingDeleted() ) return;

	const PluginInfo* pi = tbl_PluginInfo; do
	{
		wxComboBox& box( m_ComponentBoxes->Get(pi->id) );
		if( box.GetId() == evt.GetId() )
		{
			// Button is enabled if:
			//   (a) plugins aren't even loaded yet.
			//   (b) current selection matches exactly the currently configured/loaded plugin.

			bool isSame = (!CorePlugins.AreLoaded()) || g_Conf->FullpathMatchTest( pi->id, (*m_FileList)[(int)box.GetClientData(box.GetSelection())] );
			m_ComponentBoxes->GetConfigButton( pi->id ).Enable( isSame );
			
			if( !isSame ) evt.Skip();		// enabled Apply button! :D
			return;
		}
	} while( ++pi, pi->shortname != NULL );
}

void Panels::PluginSelectorPanel::OnConfigure_Clicked( wxCommandEvent& evt )
{
	if( IsBeingDeleted() ) return;

	PluginsEnum_t pid = (PluginsEnum_t)(int)((wxEvtHandler*)evt.GetEventObject())->GetClientData();

	int sel = m_ComponentBoxes->Get(pid).GetSelection();
	if( sel == wxNOT_FOUND ) return;

	// Only allow configuration if the selected plugin matches exactly our currently loaded one.
	// Otherwise who knows what sort of funny business could happen configuring a plugin while
	// another instance/version is running. >_<

	const wxString filename( (*m_FileList)[(int)m_ComponentBoxes->Get(pid).GetClientData(sel)] );

	if( CorePlugins.AreLoaded() && !g_Conf->FullpathMatchTest( pid, filename ) )
	{
		Console.Warning( "(PluginSelector) Plugin name mismatch, configuration request ignored." );
		return;
	}

	wxDynamicLibrary dynlib( filename );

	if( ConfigureFnptr configfunc = (ConfigureFnptr)dynlib.GetSymbol( tbl_PluginInfo[pid].GetShortname() + L"configure" ) )
	{
		
		wxWindowDisabler disabler;
		wxDoNotLogInThisScope quiettime;
		ScopedCoreThreadPause paused_core( new SysExecEvent_SaveSinglePlugin(pid) );
		if (!CorePlugins.AreLoaded())
		{
			typedef void	(CALLBACK* SetDirFnptr)( const char* dir );

			if( SetDirFnptr func = (SetDirFnptr)dynlib.GetSymbol( tbl_PluginInfo[pid].GetShortname() + L"setSettingsDir" ) )
			{
				func( GetSettingsFolder().ToString().mb_str(wxConvFile) );
			}

			if( SetDirFnptr func = (SetDirFnptr)dynlib.GetSymbol( tbl_PluginInfo[pid].GetShortname() + L"setLogDir" ) )
			{
				func( GetLogFolder().ToString().mb_str(wxConvFile) );
			}
		}

		configfunc();
	}
}

void Panels::PluginSelectorPanel::OnShowStatusBar( wxCommandEvent& evt )
{
	m_StatusPanel->SetSize( m_ComponentBoxes->GetSize().GetWidth() - 8, wxDefaultCoord );
	m_StatusPanel->CentreOnParent();
	m_StatusPanel->Show();
}

void Panels::PluginSelectorPanel::OnEnumComplete( wxCommandEvent& evt )
{
	m_EnumeratorThread = NULL;

	// fixme: Default plugins should be picked based on the timestamp of the DLL or something?
	//  (for now we just force it to selection zero if nothing's selected)

	int emptyBoxes = 0;
	const PluginInfo* pi = tbl_PluginInfo; do
	{
		const PluginsEnum_t pid = pi->id;
		if( m_ComponentBoxes->Get(pid).GetCount() <= 0 )
			emptyBoxes++;

		else if( m_ComponentBoxes->Get(pid).GetSelection() == wxNOT_FOUND )
		{
			m_ComponentBoxes->Get(pid).SetSelection( 0 );
			m_ComponentBoxes->GetConfigButton(pid).Enable( !CorePlugins.AreLoaded() );
		}
	} while( ++pi, pi->shortname != NULL );

	m_ComponentBoxes->Show();
	m_StatusPanel->Hide();
	m_StatusPanel->Reset();

	wxWindow* forwardButton = GetGrandParent()->FindWindow( wxID_FORWARD );
	if( forwardButton != NULL )
		forwardButton->Enable();
}


void Panels::PluginSelectorPanel::OnProgress( wxCommandEvent& evt )
{
	if( !m_FileList ) return;

	// The thread can get canceled and replaced with a new thread, which means all
	// pending messages should be ignored.
	if( m_EnumeratorThread != (EnumThread*)evt.GetClientData() ) return;

	const size_t evtidx = evt.GetExtraLong();

	if( DisableThreading )
	{
		const int nextidx = evtidx+1;
		if( nextidx == m_FileList->Count() )
		{
			wxCommandEvent done( pxEvt_EnumerationFinished );
			GetEventHandler()->AddPendingEvent( done );
		}
		else
			m_EnumeratorThread->DoNextPlugin( nextidx );
	}

	m_StatusPanel->AdvanceProgress( (evtidx < m_FileList->Count()-1) ?
		(*m_FileList)[evtidx + 1] : wxString(_("Completing tasks..."))
	);

	EnumeratedPluginInfo& result( m_EnumeratorThread->Results[evtidx] );

	if( result.TypeMask == 0 )
	{
		Console.Error( L"Some kinda plugin failure: " + (*m_FileList)[evtidx] );
	}

	const PluginInfo* pi = tbl_PluginInfo; do
	{
		const PluginsEnum_t pid = pi->id;
		if( result.TypeMask & pi->typemask )
		{
			if( result.PassedTest & pi->typemask )
			{
				int sel = m_ComponentBoxes->Get(pid).Append( wxsFormat( L"%s %s [%s]",
					result.Name.c_str(), result.Version[pid].c_str(), Path::GetFilenameWithoutExt( (*m_FileList)[evtidx] ).c_str() ),
					(void*)evtidx
				);

				if( g_Conf->FullpathMatchTest( pid, (*m_FileList)[evtidx] ) )
				{
					m_ComponentBoxes->Get(pid).SetSelection( sel );
					m_ComponentBoxes->GetConfigButton(pid).Enable();
				}
			}
		}
	} while( ++pi, pi->shortname != NULL );
}


// --------------------------------------------------------------------------------------
//  EnumThread   method implementations
// --------------------------------------------------------------------------------------

Panels::PluginSelectorPanel::EnumThread::EnumThread( PluginSelectorPanel& master )
	: pxThread()
	, Results( master.FileCount(), L"PluginSelectorResults" )
	, m_master( master )
	, m_hourglass( Cursor_KindaBusy )
{
	Results.MatchLengthToAllocatedSize();
}

void Panels::PluginSelectorPanel::EnumThread::DoNextPlugin( int curidx )
{
	DbgCon.Indent().WriteLn( L"Plugin: " + m_master.GetFilename( curidx ) );

	try
	{
		EnumeratedPluginInfo& result( Results[curidx] );
		result.TypeMask = 0;

		PluginEnumerator penum( m_master.GetFilename( curidx ) );

		result.Name = penum.GetName();
		const PluginInfo* pi = tbl_PluginInfo; do
		{
			const PluginsEnum_t pid = pi->id;
			result.TypeMask |= pi->typemask;
			if( penum.CheckVersion( pid ) )
			{
				result.PassedTest |= tbl_PluginInfo[pid].typemask;
				penum.GetVersionString( result.Version[pid], pid );
			}
		} while( ++pi, pi->shortname != NULL );
	}
	catch( Exception::BadStream& ex )
	{
		Console.Warning( ex.FormatDiagnosticMessage() );
	}

	wxCommandEvent yay( pxEvt_EnumeratedNext );
	yay.SetClientData( this );
	yay.SetExtraLong( curidx );
	m_master.GetEventHandler()->AddPendingEvent( yay );
}

void Panels::PluginSelectorPanel::EnumThread::ExecuteTaskInThread()
{
	DevCon.WriteLn( "Plugin Enumeration Thread started..." );

	wxGetApp().Ping();
	Sleep( 2 );

	for( int curidx=0; curidx < m_master.FileCount(); ++curidx )
	{
		DoNextPlugin( curidx );

		// speed isn't critical here, but the pretty status bar sure is.  Sleep off
		// some brief cycles to give the status bar time to refresh.

		Sleep( 5 );
		//Sleep(150);		// uncomment this to slow down the selector, for debugging threading.
	}

	wxCommandEvent done( pxEvt_EnumerationFinished );
	done.SetClientData( this );
	m_master.GetEventHandler()->AddPendingEvent( done );

	DevCon.WriteLn( "Plugin Enumeration Thread complete!" );
}
