////////////////////////////////////////////////////////////////////////////////
// Plainamp, Open source Winamp core
// 
// Copyright © 2005  Sebastian Pipping <webmaster@hartwork.org>
// 
// -->  http://www.hartwork.org
// 
// This source code is released under the GNU General Public License (GPL).
// See GPL.txt for details. Any non-GPL usage is strictly forbidden.
////////////////////////////////////////////////////////////////////////////////


#include "PluginManager.h"
#include "Plugin.h"
#include "InputPlugin.h"
#include "OutputPlugin.h"
#include "VisPlugin.h"
#include "DspPlugin.h"
#include "GenPlugin.h"
#include "Console.h"
#include "Main.h"
#include "Util.h"
#include "Config.h"



#define MENU_INPUT_CONFIG   1
#define MENU_INPUT_ABOUT    2

#define MENU_OUTPUT_CONFIG  1
#define MENU_OUTPUT_ABOUT   2
#define MENU_OUTPUT_SEP     3
#define MENU_OUTPUT_ACTIVE  4
#define MENU_OUTPUT_LOAD    5

#define MENU_GEN_CONFIG     1
#define MENU_GEN_SEP        2
#define MENU_GEN_LOAD       3



HWND WindowManager = NULL; // extern
HWND WindowListView = NULL;


WNDPROC WndprocListViewBackup = NULL;
LRESULT CALLBACK WndprocListView( HWND hwnd, UINT message, WPARAM wp, LPARAM lp );

LRESULT CALLBACK WndprocManager( HWND hwnd, UINT message, WPARAM wp, LPARAM lp );


HMENU input_menu = NULL;
HMENU output_menu = NULL;
HMENU gen_menu = NULL;



bool bManagerGrid;
ConfBool cbManagerGrid( &bManagerGrid, "ManagerGrid", CONF_MODE_PUBLIC, true );

bool bManagerVisible;
WINDOWPLACEMENT WinPlaceManager;

void WinPlaceManagerCallback( ConfVar * var )
{
	if( !IsWindow( WindowManager ) ) return;

	GetWindowPlacement( WindowManager, &WinPlaceManager );

	// MSDN:  If the window identified by the hWnd parameter
	//        is maximized, the showCmd member is SW_SHOWMAXIMIZED.
	//        If the window is minimized, showCmd is SW_SHOWMINIMIZED.
	//        Otherwise, it is SW_SHOWNORMAL.
	if( !bManagerVisible )
	{
		WinPlaceManager.showCmd = SW_HIDE;
	}
}

RECT rManagerDefault = { 500, 400, 1000, 700 };

ConfWinPlaceCallback cwpcWinPlaceManager(
	&WinPlaceManager,
	TEXT( "WinPlaceManager" ),
	&rManagerDefault,
	WinPlaceManagerCallback
);



////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
#define CLASSNAME_PAPMC  TEXT( "PLAINAMP_PMC" )
#define PAPMC_TITLE      TEXT( "Plugin Manager" )

bool PluginManager::Build()
{
	LoadCommonControls();

	// Register class
	WNDCLASS wc = {
		0,                                     // UINT style
		WndprocManager,                           // WNDPROC lpfnWndProc
    	0,                                     // int cbClsExtra
    	0,                                     // int cbWndExtra
    	g_hInstance,                           // HINSTANCE hInstance
    	NULL,  // HICON hIcon
    	LoadCursor( NULL, IDC_ARROW ),         // HCURSOR hCursor
    	( HBRUSH )COLOR_WINDOW,                // HBRUSH hbrBackground
    	NULL,                                  // LPCTSTR lpszMenuName
    	CLASSNAME_PAPMC                        // LPCTSTR lpszClassName
	};
		
	if( !RegisterClass( &wc ) ) return false;

	WindowManager = CreateWindowEx(
		WS_EX_TOOLWINDOW,                              // DWORD dwExStyle
		CLASSNAME_PAPMC,                               // LPCTSTR lpClassName
		PAPMC_TITLE,                                   // LPCTSTR lpWindowName
		WS_OVERLAPPEDWINDOW |                          // DWORD dwStyle
			WS_CLIPCHILDREN,                            //
		rManagerDefault.left,                          // int x
		rManagerDefault.top,                           // int y
		rManagerDefault.right - rManagerDefault.left,  // int nWidth
		rManagerDefault.bottom - rManagerDefault.top,  // int nHeight
		NULL,                                          // HWND hWndParent
		NULL,                                          // HMENU hMenu
		g_hInstance,                                   // HINSTANCE hInstance
		NULL                                           // LPVOID lpParam
	);

	RECT r;
	GetClientRect( WindowManager, &r );
	
	WindowListView = CreateWindowEx(
		WS_EX_CLIENTEDGE,                              // DWORD dwExStyle
		WC_LISTVIEW,                                   // LPCTSTR lpClassName
		NULL,                                          // LPCTSTR lpWindowName
		WS_VSCROLL |                                   // DWORD dwStyle
			LVS_REPORT  |                              //
			LVS_SORTASCENDING |                        //
			LVS_SINGLESEL |                            //
			WS_CHILD |                                 //
			WS_VISIBLE,                                //
		0,                                             // int x
		0,                                             // int y
		r.right - r.left,                              // int nWidth
		r.bottom - r.top,                              // int nHeight
		WindowManager,                                 // HWND hWndParent
		NULL,                                          // HMENU hMenu
		g_hInstance,                                   // HINSTANCE hInstance
		NULL                                           // LPVOID lpParam
	);
	
	if( !WindowListView ) return false;

	ListView_SetExtendedListViewStyle(
		WindowListView,
	    LVS_EX_FULLROWSELECT  |
			( bManagerGrid ? LVS_EX_GRIDLINES : 0 ) |
			LVS_EX_HEADERDRAGDROP
	);

	// (0) File
	LVCOLUMN lvc = {
		LVCF_TEXT | LVCF_WIDTH | LVCF_FMT,  // UINT mask; 
		LVCFMT_LEFT,                        // int fmt; 
		100,                                // int cx; 
		TEXT( "File" ),                     // LPTSTR pszText; 
		5,                                  // int cchTextMax; 
		0,                                  // int iSubItem; 
		0,                                  // int iOrder;
		0                                   // int iImage;
	};
	ListView_InsertColumn( WindowListView, 0, &lvc );
	
	// (1) Status
	lvc.pszText     = TEXT( "Status" );
	lvc.cchTextMax  = 7;
	ListView_InsertColumn( WindowListView, 1, &lvc );

	// (2) Name
	lvc.cx          = 220;
	lvc.pszText     = TEXT( "Name" );
	lvc.cchTextMax  = 5;
	ListView_InsertColumn( WindowListView, 2, &lvc );
		
	
	// (3) Type
	lvc.pszText     = TEXT( "Type" );
	lvc.cchTextMax  = 5;
	ListView_InsertColumn( WindowListView, 3, &lvc );


	bManagerVisible = ( WinPlaceManager.showCmd != SW_HIDE );
	SetWindowPlacement( WindowManager, &WinPlaceManager );


	// Exchange window procedure
	WndprocListViewBackup = ( WNDPROC )GetWindowLong( WindowListView, GWL_WNDPROC );
	if( WndprocListViewBackup != NULL )
	{
		SetWindowLong( WindowListView, GWL_WNDPROC, ( LONG )WndprocListView );
	}


	// Build context menu
	input_menu = CreatePopupMenu();
	AppendMenu( input_menu, MF_STRING, MENU_INPUT_CONFIG, TEXT( "Config" ) );
	AppendMenu( input_menu, MF_STRING, MENU_INPUT_ABOUT,  TEXT( "About" ) );

	output_menu = CreatePopupMenu();
	AppendMenu( output_menu, MF_STRING, MENU_OUTPUT_CONFIG, TEXT( "Config" ) );
	AppendMenu( output_menu, MF_STRING, MENU_OUTPUT_ABOUT,  TEXT( "About" ) );
	AppendMenu( output_menu, MF_SEPARATOR | MF_GRAYED | MF_DISABLED, MENU_OUTPUT_SEP, NULL );
	AppendMenu( output_menu, MF_STRING, MENU_OUTPUT_ACTIVE, TEXT( "Active" ) );
	AppendMenu( output_menu, MF_STRING, MENU_OUTPUT_LOAD, TEXT( " " ) );

	gen_menu = CreatePopupMenu();
	AppendMenu( gen_menu, MF_STRING, MENU_GEN_CONFIG, TEXT( "Config" ) );
	AppendMenu( gen_menu, MF_SEPARATOR | MF_GRAYED | MF_DISABLED, MENU_GEN_SEP, NULL );
	AppendMenu( gen_menu, MF_STRING, MENU_GEN_LOAD, TEXT( " " ) );

	return true;
}



////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
bool PluginManager::Fill()
{
	if( !WindowListView ) return false;
	
	LVITEM lvi;
	memset( &lvi, 0, sizeof( LVITEM ) );
	lvi.mask = LVIF_TEXT | LVIF_PARAM;

	Plugin * plugin;
	int iIndex;
	vector<Plugin *>::iterator iter = plugins.begin();
	while( iter != plugins.end() )
	{
lvi.mask = LVIF_TEXT | LVIF_PARAM;

		plugin          = *iter;
		lvi.iItem       = 0;
		lvi.lParam      = ( LPARAM )plugin;

		// (0) File		

		lvi.iSubItem    = 0;
		lvi.pszText     = plugin->GetFilename();
		lvi.cchTextMax  = plugin->GetFilenameLen() + 1;
		iIndex = ListView_InsertItem( WindowListView, &lvi );
		lvi.iItem       = iIndex;
lvi.mask = LVIF_TEXT;
		
		// (1) Status
		lvi.iSubItem    = 1;
		if( plugin->IsLoaded() )
		{
			if( plugin->IsActive() )
			{
				lvi.pszText     = TEXT( "Active" );
			}
			else
			{
				lvi.pszText     = TEXT( "Loaded" );
			}
			lvi.cchTextMax      = 7;
		}
		else
		{
			lvi.pszText     = TEXT( "Not loaded" );
			lvi.cchTextMax  = 11;
		}
		ListView_SetItem( WindowListView, &lvi );

		// (2) Name
		lvi.iSubItem    = 2;
		lvi.pszText     = plugin->GetName();
		lvi.cchTextMax  = plugin->GetNameLen() + 1;
		ListView_SetItem( WindowListView, &lvi );
		
		// (3) Type
		lvi.iSubItem    = 3;
		lvi.pszText     = plugin->GetTypeString();
		lvi.cchTextMax  = plugin->GetTypeStringLen() + 1;
		ListView_SetItem( WindowListView, &lvi );
		
		iter++;
	}

	return true;	
}



////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
bool PluginManager::Destroy()
{
	if( !WindowListView ) return false;
	
	DestroyWindow( WindowManager );
	DestroyMenu( input_menu	);
	DestroyMenu( output_menu );
	DestroyMenu( gen_menu );

	return true;
}



////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
bool PluginManager::Popup()
{
	if( !WindowListView ) return false;	
	if( !IsWindowVisible( WindowManager ) )
		ShowWindow( WindowManager, SW_SHOW );
	
	SetActiveWindow( WindowManager );
	
	return true;
}



////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
void UpdatePluginStatus( Plugin * plugin, bool bLoaded, bool bActive )
{
	LVFINDINFO lvfi = {
		LVFI_PARAM,        // UINT flags
		NULL,              // LPCTSTR psz
		( LPARAM )plugin,  // LPARAM lParam
		POINT(),           // POINT pt
		0                  // UINT vkDirection
	};
	
	int iIndex = ListView_FindItem( WindowListView, -1, &lvfi );
	if( iIndex != -1 )
	{
		LVITEM lvi;
		memset( &lvi, 0, sizeof( LVITEM ) );
		lvi.mask = LVIF_TEXT;
		lvi.iItem       = iIndex;
		lvi.iSubItem    = 1;
		if( bLoaded )
		{
			if( bActive )
			{
				lvi.pszText     = TEXT( "Active" );
			}
			else
			{
				lvi.pszText     = TEXT( "Loaded" );
			}
			lvi.cchTextMax      = 7;
		}
		else
		{
				lvi.pszText     = TEXT( "Not loaded" );
				lvi.cchTextMax  = 11;
		}
		ListView_SetItem( WindowListView, &lvi );
	}
}



////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
void ContextMenuInput( InputPlugin * input, POINT * p )
{
	BOOL iIndex = TrackPopupMenu(
		input_menu,
		TPM_LEFTALIGN |
			TPM_TOPALIGN |
			TPM_NONOTIFY |
			TPM_RETURNCMD |
			TPM_RIGHTBUTTON,
		p->x,
		p->y,
		0,
		WindowManager,
		NULL
	);
	
	switch( iIndex )
	{
	case MENU_INPUT_ABOUT:
		input->About( WindowManager );
		break;

	case MENU_INPUT_CONFIG:
		input->Config( WindowManager );
	}
}



////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
void ContextMenuOutput( OutputPlugin * output, POINT * p )
{
	const bool bLoaded = output->IsLoaded();
	const bool bActive = output->IsActive();

	EnableMenuItem(
		output_menu,
		MENU_OUTPUT_CONFIG,
		bLoaded ? MF_ENABLED : MF_GRAYED | MF_DISABLED
	);
	EnableMenuItem(
		output_menu,
		MENU_OUTPUT_ABOUT,
		bLoaded ? MF_ENABLED : MF_GRAYED | MF_DISABLED
	);
	EnableMenuItem(
		output_menu,
		MENU_OUTPUT_ACTIVE,
		bLoaded ? MF_ENABLED : MF_GRAYED | MF_DISABLED
	);
	CheckMenuItem(
		output_menu,
		MENU_OUTPUT_ACTIVE,
		bActive ? MF_CHECKED : MF_UNCHECKED
	);
	

	MENUITEMINFO mii = { 0 };
	mii.cbSize      = sizeof( MENUITEMINFO );
	mii.fMask       = MIIM_STATE | MIIM_STRING;
	mii.fState      = bActive ? MFS_GRAYED | MFS_DISABLED : MFS_ENABLED;
	mii.dwTypeData  = ( LPTSTR )( bLoaded ? TEXT( "Unload" ) : TEXT( "Load" ) );
	mii.cch         = bLoaded ? 7 : 5;
	
	SetMenuItemInfo(
		output_menu,       // HMENU hMenu
		MENU_OUTPUT_LOAD,  // UINT uItem
		FALSE,             // BOOL fByPosition,
		&mii               // LPMENUITEMINFO lpmii
	);
	

	BOOL iIndex = TrackPopupMenu(
		output_menu,
		TPM_LEFTALIGN |
			TPM_TOPALIGN |
			TPM_NONOTIFY |
			TPM_RETURNCMD |
			TPM_RIGHTBUTTON,
		p->x,
		p->y,
		0,
		WindowManager,
		NULL
	);

	switch( iIndex )
	{
	case MENU_OUTPUT_CONFIG:
		output->Config( WindowManager );
		break;

	case MENU_OUTPUT_ABOUT:
		output->About( WindowManager );
		break;

	case MENU_OUTPUT_ACTIVE:
		{
			if( Playback::IsPlaying() )
			{
				MessageBox( WindowManager, TEXT( "Cannot do this while playing!" ), TEXT( "Error" ), MB_ICONERROR );
				break;
			}
			
			if( bActive )
				output->Stop();
			else
				output->Start();
			
				const bool bActiveNew = output->IsActive();
				if( bActiveNew != bActive )
				{
					UpdatePluginStatus( output, bLoaded, bActiveNew );
				}

			break;
		}

	case MENU_OUTPUT_LOAD:
		{
			if( bLoaded )
				output->Unload();
			else
				output->Load();
				
			const bool bLoadedNew = output->IsLoaded();
			if( bLoadedNew != bLoaded )
			{
				UpdatePluginStatus( output, bLoadedNew, bActive );
			}

			break;
		}
	}
}



////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
void ContextMenuVis( VisPlugin * vis, POINT * p )
{
	HMENU vis_menu = CreatePopupMenu();

	const bool bLoaded = vis->IsLoaded();
	const bool bPluginActive = vis->IsActive();

	VisModule * mod;
	HMENU * menus = NULL;

	const int iModCount = ( int )vis->modules.size();
	if( iModCount == 1 )
	{
		// Single module
		mod = vis->modules[ 0 ];
		const UINT uFlagsActive = MF_STRING |
			( mod->IsActive() ? MF_CHECKED : MF_UNCHECKED ) |
			( bLoaded ? MF_ENABLED : MF_DISABLED | MF_GRAYED );
		const UINT uFlagsConfig = MF_STRING |
			( bLoaded ? MF_ENABLED : MF_DISABLED | MF_GRAYED );
		AppendMenu( vis_menu, uFlagsActive, 1, TEXT( "Active" ) );
		AppendMenu( vis_menu, uFlagsConfig, 2, TEXT( "Config" ) );
	}
	else
	{
		// Two or more	
		menus = new HMENU[ iModCount ];
		int iIndex = 0;
		vector <VisModule *>::iterator iter = vis->modules.begin();
		while( iter != vis->modules.end() )
		{
			mod = *iter;

			menus[ iIndex ] = CreatePopupMenu();
			UINT uFlags = MF_STRING | ( mod->IsActive() ? MF_CHECKED : MF_UNCHECKED );
			AppendMenu( menus[ iIndex ], uFlags,    iIndex * 2 + 1, TEXT( "Active" ) );
			AppendMenu( menus[ iIndex ], MF_STRING, iIndex * 2 + 2, TEXT( "Config" ) );

			uFlags = MF_STRING | MF_POPUP | ( ( !bLoaded || ( bPluginActive && !mod->IsActive() ) ) ? MF_GRAYED | MF_DISABLED : 0 );
			AppendMenu( vis_menu, uFlags, ( UINT_PTR )menus[ iIndex ], mod->GetName() );
			
			iIndex++;
			iter++;	
		}
	}



	const int iPos = ( int )vis->modules.size() * 2 + 1;
	AppendMenu( vis_menu, MF_SEPARATOR | MF_GRAYED | MF_DISABLED, iPos, NULL );
	
	AppendMenu(
		vis_menu,
		MF_STRING | ( bPluginActive ? MF_DISABLED | MF_GRAYED : MF_ENABLED ),
		iPos + 1,
		bLoaded ? TEXT( "Unload" ) : TEXT( "Load" )
	);

	
	BOOL iIndex = TrackPopupMenu(
		vis_menu,                // HMENU hMenu
		TPM_LEFTALIGN |          // UINT uFlags
			TPM_TOPALIGN |       // .
			TPM_NONOTIFY |       // .
			TPM_RETURNCMD |      // .
			TPM_RIGHTBUTTON,     // .
		p->x,                    // int x
		p->y,                    // int y
		0,                       // int nReserved
		WindowManager,           // HWND hWnd
		NULL                     // HWND prcRect
	);
	
	if( iIndex )
	{
		if( iIndex >= iPos )
		{
			// Load/unload
			if( bLoaded )
				vis->Unload();
			else
				vis->Load();
				
			const bool bLoadedNew = vis->IsLoaded();
				
			if( bLoadedNew != bLoaded )
				UpdatePluginStatus( vis, bLoadedNew, FALSE );
		}
		else
		{
			int iWhich = ( iIndex - 1 ) / 2;
			if( iIndex & 1 )
			{
				// Active
				const bool bPluginActive = vis->IsActive();
				
				if( !vis->modules[ iWhich ]->IsActive() )
					vis->modules[ iWhich ]->Start();
				else
					vis->modules[ iWhich ]->Stop();
					
				const bool bPluginActiveNew = vis->IsActive();
				
				if( bPluginActiveNew && ( bPluginActiveNew != bPluginActive ) )
					UpdatePluginStatus( vis, TRUE, bPluginActiveNew );
			}
			else
			{
				// Config	
				vis->modules[ iWhich ]->Config();
			}
		}
	}
	
	DestroyMenu( vis_menu );
	if( iModCount > 1 )
	{
		for( int i = 0; i < iModCount; i++ )
			DestroyMenu( menus[ i ] );
		delete [] menus;
	}
}



////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
void ContextMenuDsp( DspPlugin * dsp, POINT * p )
{
	HMENU context_menu = CreatePopupMenu();
	
	const bool bLoaded = dsp->IsLoaded();
	const bool bPluginActive = dsp->IsActive();
	
	DspModule * mod;

	HMENU * menus = NULL;
	
	HMENU * add_rem_menus = NULL;

	const int iEach = ( 3 * active_dsp_count + 2 );

	const int iModCount = ( int )dsp->modules.size();
	if( iModCount == 1 )
	{
		// Single module
		mod = dsp->modules[ 0 ];
		
		add_rem_menus = new HMENU[ 2 ];
		add_rem_menus[ 0 ] = CreatePopupMenu();
		add_rem_menus[ 1 ] = CreatePopupMenu();

		int i;
		TCHAR szHere[ 12 ];
		for( i = 0; i < active_dsp_count; i++ )
		{
			TCHAR * szName = active_dsp_mods[ i ]->GetName();
			
			// Entry for "Add"
			_stprintf( szHere, TEXT( "Here (%i)" ), i + 1 );
			AppendMenu(
				add_rem_menus[ 0 ],
				MF_STRING,
				2 * i + 1,
				szHere
			);
			AppendMenu(
				add_rem_menus[ 0 ],
				MF_STRING | MF_DISABLED | MF_GRAYED,
				2 * i + 2,
				szName
			);
			
			// Entry for "Remove"
			AppendMenu(
				add_rem_menus[ 1 ],
				MF_STRING,
				2 * active_dsp_count + 2 + i,
				szName
			);
		}
		_stprintf( szHere, TEXT( "Here (%i)" ), i + 1 );
		AppendMenu(
			add_rem_menus[ 0 ],
			MF_STRING,
			2 * i + 1,
			szHere
		);


		// Main entries
		const UINT uFlagsAdd = MF_STRING | MF_POPUP |
			( ( bLoaded && !mod->IsActive() ) ? 0 : MF_DISABLED | MF_GRAYED );
		const UINT uFlagsRemove = MF_STRING | MF_POPUP |
			( ( bLoaded && active_dsp_count ) ? 0 : MF_GRAYED | MF_DISABLED );
		const UINT uFlagsConfig = MF_STRING |
			( ( bLoaded && mod->IsActive() ) ? 0 : MF_DISABLED | MF_GRAYED );

		AppendMenu(
			context_menu,
			uFlagsAdd,
			( UINT_PTR )add_rem_menus[ 0 ],
			TEXT( "Add" )
		);
		AppendMenu(
			context_menu,
			uFlagsRemove,
			( UINT_PTR )add_rem_menus[ 1 ],
			TEXT( "Remove" )
		);
		AppendMenu(
			context_menu,
			uFlagsConfig,
			3 * active_dsp_count + 1 + 1,
			TEXT( "Config" )
		);
	}
	else
	{
		// Two or more	
		menus = new HMENU[ iModCount ];
		add_rem_menus = new HMENU[ 2 * iModCount ];

		int iIndex = 0;
		vector <DspModule *>::iterator iter = dsp->modules.begin();
		while( iter != dsp->modules.end() )
		{
			mod = *iter;

			menus[ iIndex ] = CreatePopupMenu();
			
			add_rem_menus[ 2 * iIndex     ] = CreatePopupMenu();
			add_rem_menus[ 2 * iIndex + 1 ] = CreatePopupMenu();

			int i;
			TCHAR szHere[ 12 ];
			for( i = 0; i < active_dsp_count; i++ )
			{
				TCHAR * szName = active_dsp_mods[ i ]->GetName();
				
				// Entry for "Add"
				_stprintf( szHere, TEXT( "Here (%i)" ), i + 1 );
				AppendMenu(
					add_rem_menus[ 2 * iIndex     ],
					MF_STRING,
					( iIndex * iEach ) + ( 2 * i + 1 ),
					szHere
				);
				AppendMenu(
					add_rem_menus[ 2 * iIndex     ],
					MF_STRING | MF_DISABLED | MF_GRAYED,
					( iIndex * iEach ) + ( 2 * i + 2 ),
					szName
				);
				
				// Entry for "Remove"
				AppendMenu(
					add_rem_menus[ 2 * iIndex + 1 ],
					MF_STRING,
					( iIndex * iEach ) + ( 2 * active_dsp_count + 2 + i ),
					szName
				);
			}
			_stprintf( szHere, TEXT( "Here (%i)" ), i + 1 );
			AppendMenu(
				add_rem_menus[ 2 * iIndex ],
				MF_STRING,
				( iIndex * iEach ) + ( 2 * i + 1 ),
				szHere
			);
				
			AppendMenu(
				menus[ iIndex ],
				MF_STRING | MF_POPUP | ( mod->IsActive() ? MF_DISABLED | MF_GRAYED : 0 ),
				( UINT_PTR )add_rem_menus[ 2 * iIndex ],
				TEXT( "Add" )
			);
			AppendMenu(
				menus[ iIndex ],
				MF_STRING | MF_POPUP | ( active_dsp_count ? 0 : MF_GRAYED | MF_DISABLED ),
				( UINT_PTR )add_rem_menus[ 2 * iIndex + 1 ],
				TEXT( "Remove" )
			);
			AppendMenu(
				menus[ iIndex ],
				MF_STRING | ( mod->IsActive() ? 0 : MF_DISABLED | MF_GRAYED ),
				( iIndex * iEach ) + ( 3 * active_dsp_count + 1 + 1 ),
				TEXT( "Config" )
			);
			AppendMenu( context_menu, MF_STRING | MF_POPUP | ( bLoaded ? 0 : ( MF_DISABLED | MF_GRAYED ) ), ( UINT_PTR )menus[ iIndex ], mod->GetName() );
			
			iIndex++;
			iter++;	
		}
	}

	
	const int iPos = ( int )dsp->modules.size() * ( 3 * active_dsp_count + 1 + 1 ) + 1;
	AppendMenu( context_menu, MF_SEPARATOR | MF_GRAYED | MF_DISABLED, iPos, NULL );
	
	UINT uFlags = MF_STRING;

	AppendMenu(
		context_menu,
		uFlags | ( bPluginActive ? MF_DISABLED | MF_GRAYED : MF_ENABLED ),
		iPos + 1,
		bLoaded ? TEXT( "Unload" ) : TEXT( "Load" )
	);

	
	BOOL iIndex = TrackPopupMenu(
		context_menu,            // HMENU hMenu
		TPM_LEFTALIGN |          // UINT uFlags
			TPM_TOPALIGN |       // .
			TPM_NONOTIFY |       // .
			TPM_RETURNCMD |      // .
			TPM_RIGHTBUTTON,     // .
		p->x,                    // int x
		p->y,                    // int y
		0,                       // int nReserved
		WindowManager,           // HWND hWnd
		NULL                     // HWND prcRect
	);

	/*

	Example menu

	Mod1
		Add
			Here     [ 1]
			Active1  [ 2]
			Here     [ 3]
			Active2  [ 4]
			Here     [ 5]
		Remove
			Active1  [ 6]
			Active2  [ 7]
		Config       [ 8]
	Mod2
		Add
			Here     [ 9]
			Active1  [10]
			...
	---
	Load/Unload      [11]

	*/


	if( iIndex )
	{
		if( iIndex >= iPos )
		{
			// Load/unload
			if( bLoaded )
				dsp->Unload();
			else
				dsp->Load();
				
			const bool bLoadedNew = dsp->IsLoaded();
				
			if( bLoadedNew != bLoaded )
				UpdatePluginStatus( dsp, bLoadedNew, FALSE );
		}
		else
		{
			int iWhichMod = ( iIndex - 1 ) / iEach;
			int iRem = iIndex % iEach;
			if( iRem == 0 )
			{
				// Config
				dsp->modules[ iWhichMod ]->Config();

			}
			else if( iRem < 2 * active_dsp_count + 2 )
			{
				// Add module
				const bool bPluginActive = dsp->IsActive();
				
				dsp->modules[ iWhichMod ]->Start( iRem / 2 );
				
				const bool bPluginActiveNew = dsp->IsActive();
				if( bPluginActiveNew != bPluginActive )
				{
					UpdatePluginStatus( dsp, TRUE, bPluginActiveNew );
				}
			}
			else
			{
				// Remove module
				const bool bPluginActive = dsp->IsActive();
				
				iWhichMod = iRem - ( 2 * active_dsp_count + 2 );
/*
				TCHAR szBuffer[ 5000 ];
				_stprintf( szBuffer, TEXT( "REM <%i> <%i>" ), iIndex, iWhichMod );
				Console::Append( szBuffer );
*/
				active_dsp_mods[ iWhichMod ]->Stop();

				const bool bPluginActiveNew = dsp->IsActive();
				if( bPluginActiveNew != bPluginActive )
				{
					UpdatePluginStatus( dsp, TRUE, bPluginActiveNew );
				}
			}
		}
	}

	DestroyMenu( context_menu );
	if( iModCount > 1 )
	{
		for( int i = 0; i < iModCount; i++ )
			DestroyMenu( menus[ i ] );
		delete [] menus;
	}
	
	for( int j = 0; j < iModCount; j++ )
		DestroyMenu( add_rem_menus[ j ] );
	delete [] add_rem_menus;
}



////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
void ContextMenuGen( GenPlugin * gen, POINT * p )
{
	const bool bLoaded = gen->IsLoaded();

	EnableMenuItem(
		gen_menu,
		MENU_GEN_CONFIG,
		bLoaded ? MF_ENABLED : MF_GRAYED | MF_DISABLED
	);


	MENUITEMINFO mii = { 0 };
	mii.cbSize      = sizeof( MENUITEMINFO );
	mii.fMask       = MIIM_STATE | MIIM_STRING;
	mii.fState      = gen->AllowRuntimeUnload() ? MFS_ENABLED : MFS_GRAYED | MFS_DISABLED;
	mii.dwTypeData  = ( LPTSTR )( bLoaded ? TEXT( "Unload" ) : TEXT( "Load and activate" ) );
	mii.cch         = bLoaded ? 7 : 18;
	
	SetMenuItemInfo(
		gen_menu,          // HMENU hMenu
		MENU_GEN_LOAD,     // UINT uItem
		FALSE,             // BOOL fByPosition,
		&mii               // LPMENUITEMINFO lpmii
	);
	

	BOOL iIndex = TrackPopupMenu(
		gen_menu,
		TPM_LEFTALIGN |
			TPM_TOPALIGN |
			TPM_NONOTIFY |
			TPM_RETURNCMD |
			TPM_RIGHTBUTTON,
		p->x,
		p->y,
		0,
		WindowManager,
		NULL
	);

	switch( iIndex )
	{
	case MENU_GEN_CONFIG:
		gen->Config();
		break;

	case MENU_GEN_LOAD:
		{
			if( bLoaded )
				gen->Unload();
			else
				gen->Load();
				
			const bool bLoadedNew = gen->IsLoaded();
			if( bLoadedNew != bLoaded )
			{
				UpdatePluginStatus( gen, bLoadedNew, bLoadedNew );
			}

			break;
		}
	}
}



////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
void ContextMenu( Plugin * plugin, POINT * p )
{
	if( !plugin ) return;
	
	switch( plugin->GetType() )
	{
	case PLUGIN_TYPE_INPUT:
		ContextMenuInput( ( InputPlugin * )plugin, p );
		break;

	case PLUGIN_TYPE_OUTPUT:
		ContextMenuOutput( ( OutputPlugin * )plugin, p );
		break;

	case PLUGIN_TYPE_VIS:
		ContextMenuVis( ( VisPlugin * )plugin, p );
		break;

	case PLUGIN_TYPE_DSP:
		ContextMenuDsp( ( DspPlugin * )plugin, p );
		break;

	case PLUGIN_TYPE_GEN:
		ContextMenuGen( ( GenPlugin * )plugin, p );
		break;
	}
}



////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK WndprocListView( HWND hwnd, UINT message, WPARAM wp, LPARAM lp )
{
	static bool bContextOpen = false;

	switch( message )
	{
	case WM_CONTEXTMENU:
		{
			if( ( HWND )wp != WindowListView ) break;
			if( bContextOpen ) break;
			bContextOpen = true;
////////////////////////////////////////////////////////////////////////////////
			
			POINT p = { LOWORD( lp ), HIWORD( lp ) };
			ScreenToClient( WindowListView, &p );
	
			// Which item?
			LVHITTESTINFO hti;
			memset( &hti, 0, sizeof( LVHITTESTINFO ) );
			hti.pt = p;
			ListView_HitTest( WindowListView, &hti );
		
			// Which plugin?
			LVITEM lvi;
			memset( &lvi, 0, sizeof( LVITEM ) );
			lvi.mask      = LVIF_PARAM;
			lvi.iItem     = hti.iItem;
			lvi.iSubItem  = 0;
			
			if( !ListView_GetItem( WindowListView, &lvi ) )
			{
				bContextOpen = false;
				break;
			}
			Plugin * plugin = ( Plugin * )lvi.lParam;
	
			// Context menu
			p.x = LOWORD( lp );
			p.y = HIWORD( lp );
			ContextMenu( plugin, &p );
	
////////////////////////////////////////////////////////////////////////////////
			bContextOpen = false;
			break;
		}
	}
	return CallWindowProc( WndprocListViewBackup, hwnd, message, wp, lp );
}



////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK WndprocManager( HWND hwnd, UINT message, WPARAM wp, LPARAM lp )
{
	switch( message )
	{
	case WM_SIZE:
		{
			// Resize children
			RECT client;
			GetClientRect( WindowManager, &client );
		
			if( WindowListView )
				MoveWindow( WindowListView, 0, 0, client.right - client.left, client.bottom - client.top, TRUE );
			break;
		}
		
	case WM_SYSCOMMAND:
		if( ( wp & 0xFFF0 ) == SC_CLOSE )
		{
			ShowWindow( hwnd, SW_HIDE );
			return 0;
		}
		break;

	case WM_DESTROY:
		cwpcWinPlaceManager.TriggerCallback();
		cwpcWinPlaceManager.RemoveCallback();
		break;

	case WM_SHOWWINDOW:
		bManagerVisible = ( wp == TRUE );
		break;
	}
	return DefWindowProc( hwnd, message, wp, lp );
}
