#ifndef TRAY_ICON_H_
#define TRAY_ICON_H_

#include <Windows.h>
#include <shellapi.h>
#include <string>
#include <map>
#include <assert.h>
#include <TCHAR.H>

struct TrayIconListener;

class TrayIcon {
public:
	explicit TrayIcon(LPCWSTR name = L"",
		bool visible = false, HICON hIcon = NULL, bool destroyIconInDestructor = false);
	virtual ~TrayIcon();
	
	virtual void setName(LPCWSTR name);
	LPCWSTR getName() const { return m_name.c_str(); }
	
	virtual bool setVisible(bool visible);
	bool isVisible() const { return m_visible; }
	
	// The destructor may destroy the specified hIcon. If you want to avoid that, call
	// setIcon(NULL, false) or setDestroyIconInDestructor(false).
	virtual void setIcon(HICON hNewIcon, bool destroyCurrentIcon = true);
	HICON getIcon() const { return m_hIcon; }
	
	void setDestroyIconInDestructor(bool b)	{ m_destroyIconInDestructor = b; }
	bool getDestroyIconInDestructor() const { return m_destroyIconInDestructor; }

	enum NotificationIcon {
		iconNone, // NIIF_NONE(0)
		iconInfo, // NIIF_INFO(1)
		iconWarning, // NIIF_WARNING(2)
		iconError // NIIF_ERROR(3)
	};	
	bool notify(LPCWSTR title, LPCWSTR msg, NotificationIcon icon = iconNone);

	typedef void (*TrayIconMessageFunc)(TrayIcon* pTrayIcon, UINT uMsg);
	void setListener(TrayIconMessageFunc func) { m_messageFunc = func; }
	void setListener(TrayIconListener *listener) { m_listener = listener; }

	void setUserData(const void* data) { m_userData = data; }
	const void* getUserData() { return m_userData; }
protected:
	virtual void onMessage(UINT uMsg);
private:
	void fillNotifyIconData(NOTIFYICONDATAW& data);
	HICON internalGetIcon() const;
	bool addIcon();
	bool removeIcon();
	void onTaskbarCreated();
private:
	UINT m_id;
	std::wstring m_name;
	HICON m_hIcon;
	bool m_visible;
	bool m_destroyIconInDestructor;	
	TrayIconMessageFunc m_messageFunc;
	TrayIconListener* m_listener;
	const void* m_userData;
	
	static LRESULT CALLBACK MessageProcessorWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
	static HWND GetMessageProcessorHWND();
};

struct TrayIconListener {
	virtual void onTrayIconMouseMove(TrayIcon* pTrayIcon) {}

	virtual void onTrayIconLButtonDown(TrayIcon* pTrayIcon) {}
	virtual void onTrayIconLButtonUp(TrayIcon* pTrayIcon) {}
	virtual void onTrayIconLButtonDblClk(TrayIcon* pTrayIcon) {}

	virtual void onTrayIconRButtonDown(TrayIcon* pTrayIcon) {}
	virtual void onTrayIconRButtonUp(TrayIcon* pTrayIcon) {}
	virtual void onTrayIconRButtonDblClk(TrayIcon* pTrayIcon) {}

	virtual void onTrayIconMButtonDown(TrayIcon* pTrayIcon) {}
	virtual void onTrayIconMButtonUp(TrayIcon* pTrayIcon) {}
	virtual void onTrayIconMButtonDblClk(TrayIcon* pTrayIcon) {}

	virtual void onTrayIconSelect(TrayIcon* pTrayIcon) {}
	virtual void onTrayIconBalloonShow(TrayIcon* pTrayIcon) {}
	virtual void onTrayIconBalloonHide(TrayIcon* pTrayIcon) {}
	virtual void onTrayIconBalloonTimeout(TrayIcon* pTrayIcon) {}
	virtual void onTrayIconBalloonUserClick(TrayIcon* pTrayIcon) {}

	// Use GetCursorPos() if you need the location of the cursor.
	virtual void onTrayIconMessage(TrayIcon* pTrayIcon, UINT uMsg) {
		switch (uMsg) {
		case WM_MOUSEMOVE: onTrayIconMouseMove(pTrayIcon); break;
		case WM_LBUTTONDOWN: onTrayIconLButtonDown(pTrayIcon); break;
		case WM_LBUTTONUP: onTrayIconLButtonUp(pTrayIcon); break;
		case WM_LBUTTONDBLCLK: onTrayIconLButtonDblClk(pTrayIcon); break;
		case WM_RBUTTONDOWN: onTrayIconRButtonDown(pTrayIcon); break;
		case WM_RBUTTONUP: onTrayIconRButtonUp(pTrayIcon); break;
		case WM_RBUTTONDBLCLK: onTrayIconRButtonDblClk(pTrayIcon); break;
		case WM_MBUTTONDOWN: onTrayIconMButtonDown(pTrayIcon); break;
		case WM_MBUTTONUP: onTrayIconMButtonUp(pTrayIcon); break;
		case WM_MBUTTONDBLCLK: onTrayIconMButtonDblClk(pTrayIcon); break;
#ifdef NIN_SELECT
		case NIN_SELECT: onTrayIconSelect(pTrayIcon); break;
		case NIN_BALLOONSHOW: onTrayIconBalloonShow(pTrayIcon); break;
		case NIN_BALLOONHIDE: onTrayIconBalloonHide(pTrayIcon); break;
		case NIN_BALLOONTIMEOUT: onTrayIconBalloonTimeout(pTrayIcon); break;
		case NIN_BALLOONUSERCLICK: onTrayIconBalloonUserClick(pTrayIcon); break;
#endif
		}
	}
};

#endif  // TRAY_ICON_H_