#include "main.h"
#include <windowsx.h>
#include <time.h>
#include <rpc.h>
#include "../winamp/gen.h"
#include "resource.h"
#include "childwnd.h"
#include "config.h"
#include "../winamp/ipc_pe.h"
#include "../winamp/wa_dlg.h"
#include "../winamp/strutil.h"
#include "ml.h"
#include "ml_ipc.h"
#include "./folderbrowser.h"
#include "./mldwm.h"

#ifndef _ML_HEADER_IMPMLEMENT
#define _ML_HEADER_IMPMLEMENT
#endif // _ML_HEADER_IMPMLEMENT
#include "ml_ipc_0313.h"
#undef _ML_HEADER_IMPMLEMENT

#include "sendto.h"
#include "../gen_hotkeys/wa_hotkeys.h"
#include "MediaLibraryCOM.h"
#include "../nu/CCVersion.h"
#include "../nu/AutoWideFn.h"
#include "../nu/htmlcontainer2.h"
#include <shlwapi.h>

#include "api__gen_ml.h"
#include <api/service/waServiceFactory.h>
#include "./navigation.h"
//#include "./skinnedwnd.h"
#include "./skinning.h"
#include "../nu/ServiceWatcher.h"
#include "MusicID.h"
#include <tataki/export.h>
#include <strsafe.h>
#include "../Winamp/wasabicfg.h"

// {6B0EDF80-C9A5-11d3-9F26-00C04F39FFC6}
static const GUID library_guid = 
{ 0x6b0edf80, 0xc9a5, 0x11d3, { 0x9f, 0x26, 0x0, 0xc0, 0x4f, 0x39, 0xff, 0xc6 } };

int m_calling_getfileinfo;
int IPC_GETMLWINDOW, IPC_LIBRARY_SENDTOMENU, IPC_GET_ML_HMENU;
int config_use_ff_scrollbars=1, config_use_alternate_colors=0;
LARGE_INTEGER freq;
C_Config *g_config;

embedWindowState myWindowState;
prefsDlgRecW myPrefsItem, myPrefsItemPlug;

DEFINE_EXTERNAL_SERVICE(api_service,          WASABI_API_SVC);
DEFINE_EXTERNAL_SERVICE(api_application,      WASABI_API_APP);
DEFINE_EXTERNAL_SERVICE(api_language,         WASABI_API_LNG);
DEFINE_EXTERNAL_SERVICE(obj_ombrowser,        AGAVE_OBJ_BROWSER);
DEFINE_EXTERNAL_SERVICE(api_mldb,             AGAVE_API_MLDB);
DEFINE_EXTERNAL_SERVICE(api_syscb,            WASABI_API_SYSCB);
DEFINE_EXTERNAL_SERVICE(api_threadpool,       AGAVE_API_THREADPOOL);
DEFINE_EXTERNAL_SERVICE(api_decodefile,       AGAVE_API_DECODE);
DEFINE_EXTERNAL_SERVICE(wnd_api,              WASABI_API_WND);
DEFINE_EXTERNAL_SERVICE(api_skin,             WASABI_API_SKIN);
DEFINE_EXTERNAL_SERVICE(api_config,           AGAVE_API_CONFIG);
DEFINE_EXTERNAL_SERVICE(api_palette,          WASABI_API_PALETTE);
#ifndef IGNORE_API_GRACENOTE
DEFINE_EXTERNAL_SERVICE(api_gracenote,        AGAVE_API_GRACENOTE);
#endif
DEFINE_EXTERNAL_SERVICE(JSAPI2::api_security, AGAVE_API_JSAPI2_SECURITY);

ifc_configitem *ieDisableSEH = 0;

// wasabi based services for localisation support
HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;

static ServiceWatcher serviceWatcher;
#ifndef IGNORE_API_GRACENOTE
MusicIDCOM musicIDCOM;
#endif

void config();
void quit();
int init();

BOOL init2(void); 

extern "C"
{
	HWND g_hwnd, g_ownerwnd;

	extern winampGeneralPurposePlugin plugin =
	    {
			GPPHDR_VER_U,
	        "nullsoft(gen_ml.dll)",
	        init,
	        config,
	        quit,
	    };
};
HWND g_PEWindow;

HMENU wa_main_menu = NULL;
HMENU wa_windows_menu = NULL;
HMENU wa_playlists_cmdmenu = NULL;
HMENU last_playlistsmenu = NULL;
HMENU last_viewmenu = NULL;
int last_viewmenu_insert = 0;
int g_safeMode = 0, sneak = 0;

HCURSOR hDragNDropCursor;
int profile = 0;

wchar_t pluginPath[MAX_PATH] = {0};
static wchar_t preferencesName[128];


HMENU g_context_menus;

extern C_ItemList m_plugins;
extern HNAVCTRL hNavigation;

//xp theme disabling shit
static HMODULE m_uxdll;
HRESULT (__stdcall *SetWindowTheme)(HWND hwnd, LPCWSTR pszSubAppName, LPCWSTR pszSubIdList);
BOOL (__stdcall *IsAppThemed)(void);

template <class api_T>
void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
{
	if (WASABI_API_SVC)
	{
		waServiceFactory *factory = WASABI_API_SVC->service_getServiceByGuid(factoryGUID_t);
		if (factory)
			api_t = reinterpret_cast<api_T *>( factory->getInterface() );
	}
}

template <class api_T>
void ServiceRelease(api_T *api_t, GUID factoryGUID_t)
{
	if (WASABI_API_SVC && api_t)
	{
		waServiceFactory *factory = WASABI_API_SVC->service_getServiceByGuid(factoryGUID_t);
		if (factory)
			factory->releaseInterface(api_t);
	}
	api_t = NULL;
}

bool IsVisible()
{
	return g_hwnd && IsWindowVisible(g_ownerwnd);
}

void MLVisibleChanged(BOOL fVisible)
{
	static BOOL visible = FALSE;
	if (fVisible != visible)
	{
		visible = fVisible;
		plugin_SendMessage(ML_MSG_MLVISIBLE, visible, 0, 0);
	}
}

BOOL MlWindow_SetMinimizedMode(BOOL fMinimized)
{
	if (FALSE != fMinimized)
		return SetPropW(g_ownerwnd, L"MLWindow_MinimizedMode", (HANDLE)1);
	
	RemovePropW(g_ownerwnd, L"MLWindow_MinimizedMode");
	return TRUE;
}

BOOL MlWindow_IsMinimizedMode(void)
{
	return  (0 != GetPropW(g_ownerwnd, L"MLWindow_MinimizedMode"));
}

void toggleVisible(int closecb)
{
	BOOL fVisible, fMinimized;
	HWND rootWindow;
	fVisible = (0 != (WS_VISIBLE & GetWindowLongPtrW(g_ownerwnd, GWL_STYLE)));//IsWindowVisible(g_ownerwnd);
	
	rootWindow = GetAncestor(g_ownerwnd, GA_ROOT);
	if (NULL == rootWindow || rootWindow == g_ownerwnd)
	{
		rootWindow = (HWND)(HWND)SENDWAIPC(plugin.hwndParent, IPC_GETDIALOGBOXPARENT, 0);
		if (NULL == rootWindow)
			rootWindow = plugin.hwndParent;
	}

	fMinimized = IsIconic(rootWindow);

	if (FALSE != fVisible || 1 == closecb)
	{
		if (FALSE == fMinimized && FALSE != fVisible)
		{
			HWND hwndFocus = GetFocus();

			if (hwndFocus == g_ownerwnd || IsChild(g_ownerwnd, hwndFocus))
				SendMessageW(plugin.hwndParent, WM_COMMAND, WINAMP_NEXT_WINDOW, 0);

			ShowWindow(g_ownerwnd, SW_HIDE);
		}
	}
	else
	{
		if (init2() && FALSE == fMinimized && FALSE == fVisible)
		{	
			ShowWindow(g_ownerwnd, SW_SHOWNORMAL);
			// make sure that we focus the tree to work around some modern skin quirks
			if(closecb != 2)
			{
				SetFocus(NavCtrlI_GetHWND(hNavigation));
			}
			else
			{
				// delay the focusing on loading as some machines are too fast and 
				// may cause the wrong view to the selected (root instead of child)
				PostMessage(g_ownerwnd,WM_ML_IPC,0,ML_IPC_FOCUS_TREE);
			}
		}
	}

	if (FALSE != fMinimized && 1 != closecb)
	{
		MlWindow_SetMinimizedMode(TRUE);

		if (NULL != g_config)
			g_config->WriteInt(L"visible", (FALSE == fVisible));
		
		UINT menuFlags = (FALSE == fVisible) ? MF_CHECKED : MF_UNCHECKED;
		menuFlags |= MF_BYCOMMAND;

		INT szMenu[] = { 0, 4, };
		for (INT i = 0; i < ARRAYSIZE(szMenu); i++)
		{
			HMENU hMenu = (HMENU)SendMessage(plugin.hwndParent, WM_WA_IPC, szMenu[i], IPC_GET_HMENU);
			if (NULL != hMenu)
				CheckMenuItem(hMenu, WA_MENUITEM_ID, menuFlags);
		}
	}
}


static WNDPROC wa_oldWndProc;

static BOOL Winamp_OnIPC(HWND hwnd, UINT uMsg, INT_PTR param, LRESULT *pResult)
{
	if (IPC_GETMLWINDOW == uMsg && IPC_GETMLWINDOW > 65536)
	{	
		if (param == -1 && !g_hwnd) init2();
		*pResult = (LRESULT)g_hwnd;
		return TRUE;
	}
	else if (IPC_LIBRARY_SENDTOMENU == uMsg && IPC_LIBRARY_SENDTOMENU > 65536)
	{
		librarySendToMenuStruct *s = (librarySendToMenuStruct*)param;
		if (!s || s->mode == 0) 
		{
			*pResult = 0xFFFFFFFF;
			return TRUE;
		}
		if (s->mode == 1)
		{
			if (!s->ctx[0])
			{
				if (!g_hwnd) init2();
				SendToMenu *stm = new SendToMenu();
				if (s->build_start_id && s->build_end_id)
				{
					stm->buildmenu(s->build_hMenu, s->data_type, s->ctx[1], s->ctx[2], s->build_start_id, s->build_end_id);
				}
				else
				{
					stm->buildmenu(s->build_hMenu, s->data_type, s->ctx[1], s->ctx[2]);
				}
				s->ctx[0] = (intptr_t)stm;
				*pResult = 0xFFFFFFFF;
				return TRUE;
			}
		}
		else if (s->mode == 2)
		{
			SendToMenu *stm = (SendToMenu *)s->ctx[0];
			if (stm && stm->isourcmd(s->menu_id))
			{
				*pResult = 0xFFFFFFFF;
				return TRUE;
			}
		}
		else if (s->mode == 3)
		{
			SendToMenu *stm = (SendToMenu *)s->ctx[0];
			if (stm)
			{
				*pResult = stm->handlecmd(s->hwnd, s->menu_id, s->data_type, s->data);
				return TRUE;
			}
		}
		else if (s->mode == 4)
		{
			delete (SendToMenu *)s->ctx[0];
			s->ctx[0] = 0;
		}
		*pResult = TRUE;
		return TRUE;
	}
	else if (IPC_GET_ML_HMENU == uMsg && IPC_GET_ML_HMENU > 65536) 	
	{
		*pResult = (LRESULT)g_context_menus;
		return TRUE;
	}

	switch(uMsg)
	{
		case IPC_CB_RESETFONT:
			PostMessageW(g_hwnd, WM_DISPLAYCHANGE, 0, 0);
			break;

		case IPC_CB_GETTOOLTIPW:
			if (param == 16 && g_config->ReadInt(L"attachlbolt", 0))
			{
				static wchar_t tlStr[64];
				*pResult = (LRESULT)WASABI_API_LNGSTRINGW_BUF(IDS_TOGGLE_LIBRARY,tlStr,64);
				return TRUE;
			}
			break;

		case IPC_GET_EXTENDED_FILE_INFO_HOOKABLE:
			if (!m_calling_getfileinfo)
			{
				extendedFileInfoStruct *extendedInfo;
				extendedInfo = (extendedFileInfoStruct*)param;
				if (NULL != extendedInfo && 
					NULL != extendedInfo->filename &&
					NULL != extendedInfo->metadata)
				{
					if (plugin_SendMessage(ML_IPC_HOOKEXTINFO, param, 0, 0)) 
					{ 
						*pResult = 1; 
						return TRUE; 
					}
				}
			}
			break;

		case IPC_GET_EXTENDED_FILE_INFOW_HOOKABLE:
			if (!m_calling_getfileinfo)
			{
				extendedFileInfoStructW *extendedInfo;
				extendedInfo = (extendedFileInfoStructW*)param;
				if (NULL != extendedInfo && 
					NULL != extendedInfo->filename &&
					NULL != extendedInfo->metadata)
				{
					if (plugin_SendMessage(ML_IPC_HOOKEXTINFOW, param, 0, 0)) 
					{ 
						*pResult = 1; 
						return TRUE; 
					}
				}
			}
			break;

		case IPC_HOOK_TITLES:
			if (NULL != param)
			{
				waHookTitleStruct *hookTitle;
				hookTitle = (waHookTitleStruct*)param;
				if (NULL != hookTitle->filename &&
					plugin_SendMessage(ML_IPC_HOOKTITLE, param, 0, 0)) 
				{ 
					*pResult = 1; 
					return TRUE; 
				}
			}
			break;

		case IPC_HOOK_TITLESW:
			if (NULL != param)
			{
				waHookTitleStructW *hookTitle;
				hookTitle = (waHookTitleStructW*)param;
				if (NULL != hookTitle->filename &&
					plugin_SendMessage(ML_IPC_HOOKTITLEW, param, 0, 0)) 
				{ 
					*pResult = 1; 
					return TRUE; 
				}
			}
			break;

		case IPC_ADD_PREFS_DLG:
		case IPC_ADD_PREFS_DLGW:
			if (param && !((prefsDlgRec*)param)->where)
			{
				prefsDlgRec *p = (prefsDlgRec *)param;
				// we use the dialog proc for the preferences to determine the hinstance of the module and
				// use that to then determine if we set it as a child of the media library preference node
				// it also handles localised versions of the preference pages as the dialog proceedure is
				// going to be in the true plug-in dll and so can be matched to the main ml plugins list!
				MEMORY_BASIC_INFORMATION mbi = {0};
				if(VirtualQuery(p->proc, &mbi, sizeof(mbi)))
				{
					int i = m_plugins.GetSize();
					while (i-- > 0)
					{
						winampMediaLibraryPlugin *mlplugin = (winampMediaLibraryPlugin *)m_plugins.Get(i);
						if (mlplugin->hDllInstance == (HINSTANCE)mbi.AllocationBase)
						{
							p->where = (intptr_t)(INT_PTR)&myPrefsItem;
							break;
						}
					}
				}
			}
			break;

		case IPC_CB_ONSHOWWND:
			if ((HWND)param == g_ownerwnd) MLVisibleChanged(TRUE);
			break;

		case IPC_HOOK_OKTOQUIT:
			{
				if (plugin_SendMessage(ML_MSG_NOTOKTOQUIT, 0, 0, 0))
				{
					*pResult = 0;
					return TRUE;
				}
			}
			break;

		case IPC_PLAYING_FILEW:
			plugin_SendMessage(ML_MSG_PLAYING_FILE, param, 0, 0);
			break;

		case IPC_WRITECONFIG:
			plugin_SendMessage(ML_MSG_WRITE_CONFIG, param, 0, 0);
			break;
	}
	return FALSE;
}

static LRESULT WINAPI wa_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg)
	{
		// far from ideal fix but deals with differing plugin load orders (mainly from FAT32 drives)
		// and not being able to unload/clean up properly the scrollbar bitmaps used - DRO 29/09/07
		case WM_CLOSE:
			SkinnedScrollWnd_Quit();
			break;
		case WM_WA_IPC:
		{
			LRESULT result = 0;
			if (Winamp_OnIPC(hwndDlg, (UINT)lParam, (INT_PTR)wParam, &result)) return result;
			break;
		}
		case WM_SIZE:
			if (wParam == SIZE_RESTORED)
			{
				if (FALSE != MlWindow_IsMinimizedMode())
				{
					MlWindow_SetMinimizedMode(FALSE);
					int showCommand = (0 != g_config->ReadInt(L"visible", 1)) ?  SW_SHOWNA : SW_HIDE;
					ShowWindow(g_ownerwnd, showCommand);
				}
			}
			break;
		case WM_COMMAND:
		case WM_SYSCOMMAND:
			{
				WORD lowP = LOWORD(wParam);
				if (lowP == WA_MENUITEM_ID || lowP == WINAMP_LIGHTNING_CLICK)
				{
					if (lowP != WINAMP_LIGHTNING_CLICK || g_config->ReadInt(L"attachlbolt", 0))
					{
						toggleVisible();
						return 0;
					}
				}
		#if 0 // no radio - don't delete yet - tag will need to do this in ml_online
				else if (lowP == WINAMP_VIDEO_TVBUTTON) // && g_config->ReadInt("attachtv",1))
				{
					if (!g_hwnd || !IsWindowVisible(g_ownerwnd)) toggleVisible();
					PostMessage(g_ownerwnd, WM_NEXTDLGCTL, (WPARAM)g_hwnd, TRUE);
					HWND hwndTree = GetTreeHWND(g_hwnd);
					HTREEITEM hti = findByParam(hwndTree, TREE_INTERNET_VIDEO, TVI_ROOT);
					if (hti)
					{
						TreeView_SelectItem(hwndTree, hti);
						return 0;
					}
				}
		#endif
				// done like this since ml_online can't really subclass winamp to get the notification
				else if (lowP == WINAMP_VIDEO_TVBUTTON)
				{
					if (!g_hwnd || !IsWindowVisible(g_ownerwnd)) toggleVisible();
					HNAVITEM hDefItem = NavCtrlI_FindItemByName(hNavigation, LOCALE_USER_DEFAULT, NICF_INVARIANT_I | NICF_DISPLAY_I | NICF_IGNORECASE_I, L"Shoutcast TV", -1);

					if(!hDefItem)
					{
						// work with the localised version of the Online Services root (if there...)
						wchar_t OSName[64] = {L"Online Services"};
						WASABI_API_LNG->GetStringFromGUIDW(MlOnlineLangGUID,WASABI_API_ORIG_HINST,1,OSName,64);

						// just incase the localised dll was there but the file was missing the translation
						if(!lstrcmpiW(OSName,L"Error loading string"))
							lstrcpynW(OSName,L"Online Services",64);

						hDefItem = NavCtrlI_FindItemByName(hNavigation, LOCALE_USER_DEFAULT, NICF_INVARIANT_I | NICF_DISPLAY_I | NICF_IGNORECASE_I, OSName, -1);
					}
					if (hDefItem)
					{
						NavItemI_Select(hDefItem);
						NavCtrlI_Show(hNavigation, SW_SHOWNA);
					}
					else
					{
						wchar_t titleStr[128] = {0};
						MessageBoxW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_ONLINE_SERVICES_NOT_PRESENT),
								    WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SWITCHING_TO_VIEW,titleStr,128),0);
					}
					return 0;
				}
				if (lowP == WINAMP_SHOWLIBRARY)
				{
					if (!g_hwnd || !IsWindowVisible(g_hwnd)) 
						toggleVisible((2 == HIWORD(wParam) ? 2 : 0));
				}
				else if (lowP == WINAMP_CLOSELIBRARY)
				{
					if (g_hwnd && IsWindowVisible(g_ownerwnd)) toggleVisible();
				}
			} 
			break;
		case WM_DWMCOMPOSITIONCHANGED:
			if (IsWindow(g_hwnd)) PostMessageW(g_hwnd, WM_DWMCOMPOSITIONCHANGED, 0, 0L);
			break;
	}

	return CallWindowProcW(wa_oldWndProc, hwndDlg, uMsg, wParam, lParam);
}

INT_PTR CALLBACK dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);

BOOL init2(void)
{
	if (!g_hwnd)
	{
		WADlg_init(plugin.hwndParent);

		//xp theme disabling shit
		m_uxdll = LoadLibraryA("uxtheme.dll");
		if (m_uxdll)
		{
			IsAppThemed = (BOOL (__stdcall *)(void))GetProcAddress(m_uxdll, "IsAppThemed");
			SetWindowTheme = (HRESULT (__stdcall *)(struct HWND__ *, LPCWSTR , LPCWSTR  ))GetProcAddress(m_uxdll, "SetWindowTheme");
		}
		else
		{
			IsAppThemed = NULL;
			SetWindowTheme = NULL;
		}

		g_context_menus = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);

		// 02/11/08 DrO
		// defaults were 100,100,500,400 and not visible but these now make it align when opened under
		// a clean install starting with a classic skin and is also visible on start now as with modern
		myWindowState.r.left = g_config->ReadInt(L"mw_xpos", 301);
		myWindowState.r.top = g_config->ReadInt(L"mw_ypos", 29);
		myWindowState.r.right = myWindowState.r.left + g_config->ReadInt(L"mw_width", 500);
		myWindowState.r.bottom = myWindowState.r.top + g_config->ReadInt(L"mw_height", 348);
		SET_EMBED_GUID((&myWindowState), library_guid);

		g_ownerwnd = (HWND)SendMessage(plugin.hwndParent, WM_WA_IPC, (LPARAM) & myWindowState, IPC_GET_EMBEDIF);
		if (!g_ownerwnd) return FALSE;

		if (NULL != WASABI_API_APP) WASABI_API_APP->app_registerGlobalWindow(g_ownerwnd);

		SetWindowTextW(g_ownerwnd, WASABI_API_LNGSTRINGW(IDS_WINAMP_LIBRARY));
		g_hwnd = WASABI_API_CREATEDIALOGW(IDD_MAIN, g_ownerwnd, dialogProc);
		if (!g_hwnd)
		{
			DestroyWindow(g_ownerwnd);
			g_ownerwnd = NULL;
			return FALSE;
		}		
	}
	return TRUE;
}

wchar_t WINAMP_INI[MAX_PATH] = {0}, WINAMP_INI_DIR[MAX_PATH] = {0};
MediaLibraryCOM mediaLibraryCOM;
IDispatch *winampExternal = 0;

void TAG_FMT_EXT(const wchar_t *filename, void *f, void *ff, void *p, wchar_t *out, int out_len, int extended)
{
	waFormatTitleExtended fmt; 
	fmt.filename=filename;
	fmt.useExtendedInfo=extended;
	fmt.out = out;
	fmt.out_len = out_len;
	fmt.p = p;
	fmt.spec = 0;
	*(void **)&fmt.TAGFUNC = f;
	*(void **)&fmt.TAGFREEFUNC = ff;
	*out = 0;

	int oldCallingGetFileInfo=m_calling_getfileinfo;
	m_calling_getfileinfo=1;
	SendMessage(plugin.hwndParent, WM_WA_IPC, (WPARAM)&fmt, IPC_FORMAT_TITLE_EXTENDED);
	m_calling_getfileinfo=oldCallingGetFileInfo;
}

wchar_t *itemrecordTagFunc(wchar_t *tag, void * p) //return 0 if not found
{
	itemRecord *t = (itemRecord *)p;
	char buf[128] = {0};
	char *value = NULL;

	if (!_wcsicmp(tag, L"artist"))	value = t->artist;
	else if (!_wcsicmp(tag, L"album"))	value = t->album;
	else if (!_wcsicmp(tag, L"filename")) value = t->filename;
	else if (!_wcsicmp(tag, L"title"))	value = t->title;
	else if ( !_wcsicmp( tag, L"ext" ) ) value = t->ext;
	else if (!_wcsicmp(tag, L"year"))
	{
		if (t->year > 0)
		{
			StringCchPrintfA(buf, 128, "%04d", t->year);
			value = buf;
		}
	}
	else if (!_wcsicmp(tag, L"genre"))	value = t->genre;
	else if (!_wcsicmp(tag, L"comment")) value = t->comment;
	else if (!_wcsicmp(tag, L"tracknumber") || !_wcsicmp(tag, L"track"))
	{
		if (t->track > 0)
		{
			StringCchPrintfA(buf, 128, "%02d", t->track);
			value = buf;
		}
	}
	else if (!_wcsicmp(tag, L"rating")) value = getRecordExtendedItem(t, "RATING");
	else if (!_wcsicmp(tag, L"playcount")) value = getRecordExtendedItem(t, "PLAYCOUNT");
	else if (!_wcsicmp(tag, L"bitrate")) value = getRecordExtendedItem(t, "BITRATE");
	else
		return 0;

	if (!value) return reinterpret_cast<wchar_t *>(-1);
	else return AutoWideDup(value);
}

wchar_t *itemrecordWTagFunc(wchar_t *tag, void * p) //return 0 if not found
{
	itemRecordW *t = (itemRecordW *)p;
	wchar_t buf[128] = {0};
	wchar_t *value = NULL;

	// TODO: more fields
	if (!_wcsicmp(tag, L"artist")) value = t->artist;
	else if (!_wcsicmp(tag, L"album")) value = t->album;
	else if (!_wcsicmp(tag, L"albumartist")) value = t->albumartist;
	else if (!_wcsicmp(tag, L"category")) value = t->category;
	else if (!_wcsicmp(tag, L"comment")) value = t->comment;
	else if (!_wcsicmp(tag, L"composer")) value = t->composer;
	else if (!_wcsicmp(tag, L"publisher")) value = t->publisher;
	else if (!_wcsicmp(tag, L"filename")) value = t->filename;
	else if (!_wcsicmp(tag, L"title")) value = t->title;
	else if (!_wcsicmp(tag, L"year"))
	{
		if (t->year > 0)
		{
			StringCchPrintfW(buf, 128, L"%04d", t->year);
			value = buf;
		}
	}
	else if (!_wcsicmp(tag, L"genre"))	value = t->genre;
	else if (!_wcsicmp(tag, L"comment")) value = t->comment;
	else if (!_wcsicmp(tag, L"tracknumber") || !_wcsicmp(tag, L"track"))
	{
		if (t->track > 0)
		{
			StringCchPrintfW(buf, 128, L"%02d", t->track);
			value = buf;
		}
	}
	else if (!_wcsicmp(tag, L"rating"))
	{
		if (t->rating > 0)
		{
			StringCchPrintfW(buf, 128, L"%d", t->rating);
			value = buf;
		}
	}
	else if (!_wcsicmp(tag, L"playcount"))
	{
		if (t->playcount > 0)
		{
			StringCchPrintfW(buf, 128, L"%d", t->playcount);
			value = buf;
		}
	}
	else if (!_wcsicmp(tag, L"bitrate"))
	{
		if (t->bitrate > 0)
		{
			StringCchPrintfW(buf, 128, L"%d", t->bitrate);
			value = buf;
		}
	}
	else
		return 0;

	if (!value) return reinterpret_cast<wchar_t *>(-1);
	else return _wcsdup(value); 
}


void fieldTagFuncFree(wchar_t * tag, void * p)
{
	free(tag);
}

void main_playItemRecordList(itemRecordList *obj, int enqueue, int startplaying)
{
	if (obj->Size && !enqueue) SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_DELETE);

	int x;
	wchar_t title[2048]=L"";

	for (x = 0; x < obj->Size; x ++)
	{
		if (obj->Items[x].filename && *obj->Items[x].filename)
		{
			AutoWideFn wfn( obj->Items[ x ].filename );

			TAG_FMT_EXT(wfn, itemrecordTagFunc, fieldTagFuncFree, (void*)&obj->Items[x], title, 2048, 1);

			{
				enqueueFileWithMetaStructW s;
				s.filename = wfn;
				s.title    = title;
				s.ext      = NULL;
				s.length   = obj->Items[x].length;
				SendMessage(plugin.hwndParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
			}
		}
	}

	if (obj->Size && !enqueue && startplaying) SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_STARTPLAY);
}

void main_playItemRecordListW(itemRecordListW *obj, int enqueue, int startplaying)
{
	if (obj->Size && !enqueue) SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_DELETE);

	int x;
	wchar_t title[2048]=L"";

	for (x = 0; x < obj->Size; x ++)
	{
		if (obj->Items[x].filename && *obj->Items[x].filename)
		{
			TAG_FMT_EXT(obj->Items[x].filename, itemrecordWTagFunc, fieldTagFuncFree, (void*)&obj->Items[x], title, 2048, 1);
			{
				enqueueFileWithMetaStructW s;
				s.filename = obj->Items[x].filename;
				s.title    = title;
				s.ext      = NULL;
				s.length   = obj->Items[x].length;
				SendMessage(plugin.hwndParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
			}
		}
	}

	if (obj->Size && !enqueue && startplaying) SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_STARTPLAY);
}

void OpenMediaLibraryPreferences()
{
	SendMessage(plugin.hwndParent, WM_WA_IPC, (WPARAM)&myPrefsItem, IPC_OPENPREFSTOPAGE);
}

int AddTreeImageBmp(int resourceId)
{
	HMLIMGLST hmlilNavigation = MLNavCtrl_GetImageList(g_hwnd);
	MLIMAGESOURCE mlis = {sizeof(MLIMAGESOURCE),0};
	MLIMAGELISTITEM item = {0};
	item.cbSize = sizeof(MLIMAGELISTITEM);
	item.hmlil = hmlilNavigation;
	item.filterUID = MLIF_FILTER1_UID;
	item.pmlImgSource = &mlis;

	mlis.hInst = WASABI_API_ORIG_HINST;
	mlis.bpp = 24;
	mlis.lpszName = MAKEINTRESOURCEW(resourceId);
	mlis.type = SRC_TYPE_BMP;
	mlis.flags = ISF_FORCE_BPP;
	return MLImageList_Add(g_hwnd, &item);
}

void SkinnedScrollWnd_Init();
void SkinnedScrollWnd_Quit();
int init()
{
	wchar_t g_path[MAX_PATH] = {0};
	QueryPerformanceFrequency(&freq);

	WASABI_API_SVC = (api_service*)SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_GET_API_SERVICE);
	if (!WASABI_API_SVC || WASABI_API_SVC == (api_service *)1)
		return GEN_INIT_FAILURE;

	HTMLContainer2_Initialize();
	Tataki::Init(WASABI_API_SVC);

	// loader so that we can get the localisation service api for use
	ServiceBuild(WASABI_API_LNG, languageApiGUID);
	ServiceBuild(WASABI_API_SYSCB, syscbApiServiceGuid);
	ServiceBuild(AGAVE_API_DECODE, decodeFileGUID);
	ServiceBuild(AGAVE_API_JSAPI2_SECURITY, JSAPI2::api_securityGUID);
	ServiceBuild(AGAVE_OBJ_BROWSER, OBJ_OmBrowser);
	#ifndef IGNORE_API_GRACENOTE
	ServiceBuild(AGAVE_API_GRACENOTE, gracenoteApiGUID);
	#endif
	ServiceBuild(WASABI_API_APP, applicationApiServiceGuid);
	ServiceBuild(AGAVE_API_CONFIG, AgaveConfigGUID);
	ServiceBuild(WASABI_API_PALETTE, PaletteManagerGUID);	
	ServiceBuild(AGAVE_API_THREADPOOL, ThreadPoolGUID);
	// no guarantee that AGAVE_API_MLDB will be available yet, so we'll start a watcher for it
	serviceWatcher.WatchWith(WASABI_API_SVC);
	serviceWatcher.WatchFor(&AGAVE_API_MLDB, mldbApiGuid);
	serviceWatcher.WatchFor(&WASABI_API_SKIN, skinApiServiceGuid);
	serviceWatcher.WatchFor(&WASABI_API_WND,wndApiServiceGuid);
	WASABI_API_SYSCB->syscb_registerCallback(&serviceWatcher);
	SkinnedScrollWnd_Init();

	// need to have this initialised before we try to do anything with localisation features
	WASABI_API_START_LANG(plugin.hDllInstance,GenMlLangGUID);

	// Build plugin description string...
	static wchar_t szDescription[256];
	StringCchPrintfW(szDescription, ARRAYSIZE(szDescription),
					 WASABI_API_LNGSTRINGW(IDS_NULLSOFT_ML_STR),
					 LOWORD(PLUGIN_VERSION) >> 8,
					 PLUGIN_VERSION & 0xFF);
	plugin.description = (char*)szDescription;

	DispatchInfo dispatchInfo;
	dispatchInfo.name = L"MediaLibrary";
	dispatchInfo.dispatch = &mediaLibraryCOM;
	SendMessage(plugin.hwndParent, WM_WA_IPC, (WPARAM)&dispatchInfo, IPC_ADD_DISPATCH_OBJECT);

	#ifndef IGNORE_API_GRACENOTE
	dispatchInfo.name = L"MusicID";
	dispatchInfo.dispatch = &musicIDCOM;
	SendMessage(plugin.hwndParent, WM_WA_IPC, (WPARAM)&dispatchInfo, IPC_ADD_DISPATCH_OBJECT);
	#endif

	IPC_LIBRARY_SENDTOMENU = (INT)SendMessage(plugin.hwndParent, WM_WA_IPC, (WPARAM)&"LibrarySendToMenu", IPC_REGISTER_WINAMP_IPCMESSAGE);
	IPC_GETMLWINDOW = (INT)SendMessage(plugin.hwndParent, WM_WA_IPC, (WPARAM)&"LibraryGetWnd", IPC_REGISTER_WINAMP_IPCMESSAGE);
	IPC_GET_ML_HMENU = (INT)SendMessage(plugin.hwndParent, WM_WA_IPC, (WPARAM)&"LibraryGetHmenu", IPC_REGISTER_WINAMP_IPCMESSAGE);

	lstrcpynW(WINAMP_INI, (wchar_t*)SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_GETINIFILEW), MAX_PATH);
	lstrcpynW(WINAMP_INI_DIR, (wchar_t*)SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_GETINIDIRECTORYW), MAX_PATH);

	PathCombineW(g_path, WINAMP_INI_DIR, L"Plugins");
	CreateDirectoryW(g_path, NULL);

	wchar_t *dir = (wchar_t*)SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_GETPLUGINDIRECTORYW);
	if (dir == (wchar_t *)1 || dir == 0)
		lstrcpynW(pluginPath, g_path, MAX_PATH);
	else
		lstrcpynW(pluginPath, dir, MAX_PATH);

	hDragNDropCursor = LoadCursor(plugin.hDllInstance, MAKEINTRESOURCE(ML_IDC_DRAGDROP));
	profile = GetPrivateProfileIntW(L"winamp", L"profile", 0, WINAMP_INI);

	wchar_t configName[1024 + 32] = {0};
	StringCchPrintfW(configName, 1024 + 32, L"%s\\gen_ml.ini", g_path);
	g_config = new C_Config(configName);
	config_use_ff_scrollbars = g_config->ReadInt(L"ffsb", 1);
	config_use_alternate_colors = g_config->ReadInt(L"alternate_items", 1);
	
	int vis = g_config->ReadInt(L"visible", 1);
	wa_main_menu = (HMENU)SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_GET_HMENU);
	wa_windows_menu = (HMENU)SendMessage(plugin.hwndParent, WM_WA_IPC, 4, IPC_GET_HMENU);
	wa_playlists_cmdmenu = NULL;
	if (wa_main_menu || wa_windows_menu)
	{
		if (wa_main_menu)
		{
			MENUITEMINFOW i = {sizeof(i), MIIM_ID | MIIM_STATE | MIIM_TYPE, MFT_STRING, vis ? (UINT)MFS_CHECKED : 0, WA_MENUITEM_ID};
			int prior_item = GetMenuItemID(wa_main_menu,9);
			if(prior_item <= 0) prior_item = GetMenuItemID(wa_main_menu,8);
			i.dwTypeData = WASABI_API_LNGSTRINGW(IDS_ML_ALT_L_SHORTCUT);

			// append before the video menu entry (more reliable than inserting into position '9' in the menu
			InsertMenuItemW(wa_main_menu, prior_item, FALSE, &i);
			SendMessage(plugin.hwndParent, WM_WA_IPC, 1, IPC_ADJUST_OPTIONSMENUPOS);
		}

		if (wa_windows_menu)
		{
			MENUITEMINFOW i = {sizeof(i), MIIM_ID | MIIM_STATE | MIIM_TYPE, MFT_STRING, vis ? (UINT)MFS_CHECKED : 0, WA_MENUITEM_ID};
			int prior_item = GetMenuItemID(wa_windows_menu,3);
			if(prior_item <= 0) prior_item = GetMenuItemID(wa_windows_menu,2);
			i.dwTypeData = WASABI_API_LNGSTRINGW(IDS_ML_ALT_L_SHORTCUT);
			InsertMenuItemW(wa_windows_menu, prior_item, FALSE, &i);
			SendMessage(plugin.hwndParent, WM_WA_IPC, 1, IPC_ADJUST_FFWINDOWSMENUPOS);
		}
	}

	// subclass the winamp window to get our leet menu item to work
	wa_oldWndProc = (WNDPROC)(LONG_PTR)SetWindowLongPtrW(plugin.hwndParent, GWLP_WNDPROC, (LONGX86)(LONG_PTR)wa_newWndProc);

	myPrefsItem.dlgID = IDD_PREFSFR;
	myPrefsItem.name = WASABI_API_LNGSTRINGW_BUF(IDS_MEDIA_LIBRARY,preferencesName,128);
	myPrefsItem.proc = (void*)PrefsProc;
	myPrefsItem.hInst = WASABI_API_LNG_HINST;
	myPrefsItem.where = -6; // to become root based item
	SendMessage(plugin.hwndParent, WM_WA_IPC, (WPARAM)&myPrefsItem, IPC_ADD_PREFS_DLGW);

	myPrefsItemPlug.dlgID = IDD_MLPLUGINS;
	myPrefsItemPlug.name = preferencesName;
	myPrefsItemPlug.proc = (void*)PluginsProc;
	myPrefsItemPlug.hInst = WASABI_API_LNG_HINST;
	myPrefsItemPlug.where = 1;
	SendMessage(plugin.hwndParent, WM_WA_IPC, (WPARAM)&myPrefsItemPlug, IPC_ADD_PREFS_DLGW);

	g_PEWindow = (HWND)SendMessage(plugin.hwndParent, WM_WA_IPC, IPC_GETWND_PE, IPC_GETWND);
	g_safeMode = SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_IS_SAFEMODE);

	// we're gonna go ahead and make this directory just to be safe.
	// if a plugin tries to use it as an INI directory but it doesn't exist, things go wrong
	wchar_t mldir[MAX_PATH] = {0};
	PathCombineW(mldir, g_path, L"ml");
	CreateDirectoryW(mldir, NULL);
	PathCombineW(mldir, mldir, L"views");
	CreateDirectoryW(mldir, NULL);

	//add general hotkey
	int m_genhotkeys_add_ipc = (INT)SendMessage(plugin.hwndParent, WM_WA_IPC, (WPARAM)&"GenHotkeysAdd", IPC_REGISTER_WINAMP_IPCMESSAGE);

	static genHotkeysAddStruct ghas = {
		(char*)_wcsdup(WASABI_API_LNGSTRINGW(IDS_ML_GHK_STR)),
	    HKF_BRING_TO_FRONT|HKF_UNICODE_NAME,
	    WM_COMMAND,
	    WA_MENUITEM_ID,
	    0,
		// specifically set the id str now so that it'll work correctly with whatever lngpack is in use
		"ML: Show/Hide Media Library"
	};
	if (m_genhotkeys_add_ipc > 65536) PostMessage(plugin.hwndParent, WM_WA_IPC, (WPARAM)&ghas, m_genhotkeys_add_ipc); //post so gen_hotkeys will catch it if not inited yet

	init2();

	// register the art view window classes
	{
		extern void InitSmoothScrollList();
		extern void InitHeaderIconList();
		InitSmoothScrollList();
		InitHeaderIconList();
		RegisterFolderBrowserControl(plugin.hDllInstance);
	}

	NavCtrlI_BeginUpdate(hNavigation, NUF_LOCK_NONE_I);
	loadMlPlugins();

#if 0
	#ifdef _DEBUG
	#define BETA
	#endif
	#ifdef BETA
	sneak = GetPrivateProfileIntW(L"winamp", L"sneak", 0, WINAMP_INI);
	if (!(sneak & 1))
	{
		NAVINSERTSTRUCT nis = {0};
		nis.item.cbSize = sizeof(NAVITEM);
		nis.item.pszText = L"Winamp Labs";
		nis.item.pszInvariant = L"winamp_labs";
		nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL | NIMF_STYLE;
		nis.item.iImage = nis.item.iSelectedImage = AddTreeImageBmp(IDB_TREEITEM_LABS);
		nis.item.style = NIS_BOLD;
		nis.hInsertAfter = NCI_FIRST;
		NAVITEM nvItem = {sizeof(NAVITEM),0,NIMF_ITEMID,};
		nvItem.hItem = MLNavCtrl_InsertItem(g_hwnd, &nis);
	}
	#endif
#endif

	NavCtrlI_EndUpdate(hNavigation);
	
	if (SW_SHOWMINIMIZED == SENDWAIPC(plugin.hwndParent, IPC_INITIAL_SHOW_STATE, 0))
	{
		MlWindow_SetMinimizedMode(TRUE);
	}
	else if (0 != vis)
	{
		PostMessageW(plugin.hwndParent, WM_COMMAND, MAKEWPARAM(WINAMP_SHOWLIBRARY, 2), 0L); 
	}

	return GEN_INIT_SUCCESS;
}


void quit()
{
	serviceWatcher.StopWatching();
	serviceWatcher.Clear();

	MlWindow_SetMinimizedMode(FALSE);

	if (g_ownerwnd)
	{
		g_config->WriteInt(L"mw_xpos", myWindowState.r.left);
		g_config->WriteInt(L"mw_ypos", myWindowState.r.top);
		g_config->WriteInt(L"mw_width", myWindowState.r.right - myWindowState.r.left);
		g_config->WriteInt(L"mw_height", myWindowState.r.bottom - myWindowState.r.top);

		if (NULL != WASABI_API_APP) WASABI_API_APP->app_unregisterGlobalWindow(g_ownerwnd);
		DestroyWindow(g_ownerwnd);
		g_ownerwnd = NULL;
	}

	// unload any services from ml_ plugins before unloading the plugins
	ServiceRelease(AGAVE_API_MLDB, mldbApiGuid);	

	#ifndef IGNORE_API_GRACENOTE
	musicIDCOM.Quit();
	#endif

	unloadMlPlugins();	

	WADlg_close();

	if (g_config)
	{
		delete g_config;
		g_config = NULL;
	}

	if (m_uxdll)
	{
		FreeLibrary(m_uxdll);
		m_uxdll = NULL;
	}
	SkinnedScrollWnd_Quit();
	#ifndef IGNORE_API_GRACENOTE
	ServiceRelease(AGAVE_API_GRACENOTE, gracenoteApiGUID);
	#endif
	ServiceRelease(WASABI_API_SYSCB, syscbApiServiceGuid);
	ServiceRelease(AGAVE_API_DECODE, decodeFileGUID);
	ServiceRelease(AGAVE_API_JSAPI2_SECURITY, JSAPI2::api_securityGUID);
	ServiceRelease(AGAVE_OBJ_BROWSER, OBJ_OmBrowser);
	ServiceRelease(WASABI_API_LNG, languageApiGUID);
	ServiceRelease(WASABI_API_WND, wndApiServiceGuid);
	ServiceRelease(WASABI_API_SKIN, skinApiServiceGuid);
	ServiceRelease(WASABI_API_APP, applicationApiServiceGuid);
	ServiceRelease(AGAVE_API_CONFIG, AgaveConfigGUID);
	ServiceRelease(WASABI_API_PALETTE, PaletteManagerGUID);
	
	Tataki::Quit();

	HTMLContainer2_Uninitialize();

	ServiceRelease(AGAVE_API_THREADPOOL, ThreadPoolGUID);
}

void config()
{
	OpenMediaLibraryPreferences();
}

INT MediaLibrary_TrackPopupEx(HMENU hMenu, UINT fuFlags, INT x, INT y, HWND hwnd, LPTPMPARAMS lptpm, HMLIMGLST hmlil, 
							INT width, UINT skinStyle, MENUCUSTOMIZEPROC customProc, ULONG_PTR customParam)
{
	if (NULL == hMenu)
		return NULL;

	return IsSkinnedPopupEnabled(FALSE) ?
		TrackSkinnedPopupMenuEx(hMenu, fuFlags, x, y, hwnd, lptpm, hmlil, width, skinStyle, customProc, customParam) :
		TrackPopupMenuEx(hMenu, fuFlags, x, y, hwnd, lptpm);
}


INT MediaLibrary_TrackPopup(HMENU hMenu, UINT fuFlags, INT x, INT y, HWND hwnd)
{	
	return MediaLibrary_TrackPopupEx(hMenu, fuFlags, x, y, hwnd, NULL, NULL, 0, SMS_USESKINFONT, NULL, NULL);
}

 HANDLE MediaLibrary_InitSkinnedPopupHook(HWND hwnd, HMLIMGLST hmlil, INT width, UINT skinStyle, MENUCUSTOMIZEPROC customProc, ULONG_PTR customParam)
 {
	 if (FALSE == IsSkinnedPopupEnabled(FALSE))
		 return FALSE;

	 return InitSkinnedPopupHook(hwnd, hmlil, width, skinStyle, customProc, customParam);
 }



BOOL 
MediaLibrary_OpenUrl(HWND ownerWindow, const wchar_t *url, BOOL forceExternal)
{	
	BOOL result;
	HCURSOR cursor;

	cursor = LoadCursor(NULL, IDC_APPSTARTING);
	if (NULL != cursor) 
		cursor = SetCursor(cursor);

	if (FALSE != forceExternal)
	{		
		HINSTANCE instance;

		if (NULL == ownerWindow)
			ownerWindow = (HWND)SENDWAIPC(plugin.hwndParent, IPC_GETDIALOGBOXPARENT, 0);

		instance = ShellExecuteW(ownerWindow, L"open", url, NULL, NULL, SW_SHOWNORMAL);
		result = ((INT_PTR)instance > 32) ? TRUE: FALSE;
	}
	else
	{
		SENDWAIPC(plugin.hwndParent, IPC_OPEN_URL, url);
		result = TRUE;
	}
		
	if (NULL != cursor) 
		SetCursor(cursor);

	return result;
}

BOOL
MediaLibrary_OpenHelpUrl(const wchar_t *helpUrl)
{
	HWND ownerWindow;
	
	ownerWindow = (HWND)SENDWAIPC(plugin.hwndParent, IPC_GETDIALOGBOXPARENT, 0);

	return MediaLibrary_OpenUrl(ownerWindow, helpUrl, FALSE);
}

extern "C"
{
	int getFileInfo(const char *filename, const char *metadata, char *dest, int len)
	{
		m_calling_getfileinfo = 1;
		dest[0] = 0;
		extendedFileInfoStruct efis = {
		                                  filename,
		                                  metadata,
		                                  dest,
		                                  (size_t)len,
		                              };
		int r = (INT)SendMessage(plugin.hwndParent, WM_WA_IPC, (WPARAM) & efis, IPC_GET_EXTENDED_FILE_INFO); //will return 1 if wa2 supports this IPC call
		m_calling_getfileinfo = 0;
		return r;
	}

	__declspec(dllexport) winampGeneralPurposePlugin *winampGetGeneralPurposePlugin()
	{
		return &plugin;
	}
};