/**
 * vim: set ts=4 :
 * ===============================================================
 * SourceMod (C)2004-2007 AlliedModders LLC.  All rights reserved.
 * ===============================================================
 *
 * This file is not open source and may not be copied without explicit
 * written permission of AlliedModders LLC.  This file may not be redistributed 
 * in whole or significant part.
 * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
 *
 * Version: $Id$
 */

#include "sm_globals.h"
#include <sh_stack.h>
#include "MenuManager.h"
#include "MenuStyle_Valve.h"
#include "MenuStyle_Radio.h"
#include "HandleSys.h"
#include "PluginSys.h"

#if defined CreateMenu
#undef CreateMenu
#endif

/**
 * And God said, "let there be menus," and behold, there were menus.  
 * God saw the menus and they were good.  And the evening and the morning
 * were the third day.
 */

enum MenuAction
{
	MenuAction_Start = (1<<0),		/**< A menu has been started (nothing passed) */
	MenuAction_Display = (1<<1),	/**< A menu is about to be displayed (param1=client, param2=MenuPanel Handle) */
	MenuAction_Select = (1<<2),		/**< An item was selected (param1=client, param2=item) */
	MenuAction_Cancel = (1<<3),		/**< The menu was cancelled (param1=client, param2=item) */
	MenuAction_End = (1<<4),		/**< A menu's display/selection cycle is complete (nothing passed). */
	MenuAction_VoteEnd = (1<<5),	/**< (VOTE ONLY): A vote sequence has ended (param1=chosen item) */
};

class CPanelHandler : public IMenuHandler
{
	friend class MenuNativeHelpers;
public:
	CPanelHandler()
	{
	}
	void OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason);
	void OnMenuSelect(IBaseMenu *menu, int client, unsigned int item);
private:
	IPluginFunction *m_pFunc;
	IPlugin *m_pPlugin;
};

class CMenuHandler : public IMenuHandler
{
	friend class MenuNativeHelpers;
public:
	CMenuHandler(IPluginFunction *pBasic, int flags);
public:
	void OnMenuStart(IBaseMenu *menu);
	void OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display);
	void OnMenuSelect(IBaseMenu *menu, int client, unsigned int item);
	void OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason);
	void OnMenuEnd(IBaseMenu *menu);
	void OnMenuDestroy(IBaseMenu *menu);
#if 0
	void OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style);
	void OnMenuDisplayItem(IBaseMenu *menu, int client, unsigned int item, const char **display);
#endif
private:
	IPluginFunction *m_pBasic;
	int m_Flags;
};

/**
 * GLOBAL CLASS FOR HELPERS
 */

class MenuNativeHelpers : 
	public SMGlobalClass,
	public IHandleTypeDispatch,
	public IPluginsListener
{
public:
	virtual void OnSourceModAllInitialized()
	{
		m_PanelType = g_HandleSys.CreateType("IMenuPanel", this, 0, NULL, NULL, g_pCoreIdent, NULL);
		g_PluginSys.AddPluginsListener(this);
	}

	virtual void OnSourceModShutdown()
	{
		g_HandleSys.RemoveType(m_PanelType, g_pCoreIdent);

		while (!m_FreePanelHandlers.empty())
		{
			delete m_FreePanelHandlers.front();
			m_FreePanelHandlers.pop();
		}

		while (!m_FreeMenuHandlers.empty())
		{
			delete m_FreeMenuHandlers.front();
			m_FreeMenuHandlers.pop();
		}
	}

	virtual void OnHandleDestroy(HandleType_t type, void *object)
	{
		IMenuPanel *panel = (IMenuPanel *)object;
		panel->DeleteThis();
	}

	/**
	 *   It is extremely important that unloaded plugins don't crash.
	 * Thus, if a plugin unloads, we run through every handler we have.
	 * This means we do almost no runtime work for keeping track of
	 * our panel handlers (we don't have to store a list of the running 
	 * ones), but when push comes to shove, we have to scan them all
	 * in case any of them are active.
	 */
	virtual void OnPluginUnloaded(IPlugin *plugin)
	{
		for (size_t i = 0; i < m_PanelHandlers.size(); i++)
		{
			if (m_PanelHandlers[i]->m_pPlugin == plugin)
			{
				m_PanelHandlers[i]->m_pPlugin = NULL;
				m_PanelHandlers[i]->m_pFunc = NULL;
			}
		}
	}

	inline HandleType_t GetPanelType()
	{
		return m_PanelType;
	}

	CPanelHandler *GetPanelHandler(IPluginFunction *pFunction)
	{
		CPanelHandler *handler;
		if (m_FreePanelHandlers.empty())
		{
			handler = new CPanelHandler;
			m_PanelHandlers.push_back(handler);
		} else {
			handler = m_FreePanelHandlers.front();
			m_FreePanelHandlers.pop();
		}
		handler->m_pFunc = pFunction;
		handler->m_pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
		return handler;
	}

	void FreePanelHandler(CPanelHandler *handler)
	{
		handler->m_pFunc = NULL;
		handler->m_pPlugin = NULL;
		m_FreePanelHandlers.push(handler);
	}

	CMenuHandler *GetMenuHandler(IPluginFunction *pFunction, int flags)
	{
		CMenuHandler *handler;
		if (m_FreeMenuHandlers.empty())
		{
			handler = new CMenuHandler(pFunction, flags);
		} else {
			handler = m_FreeMenuHandlers.front();
			m_FreeMenuHandlers.pop();
			handler->m_pBasic = pFunction;
			handler->m_Flags = flags;
		}
		return handler;
	}

	void FreeMenuHandler(CMenuHandler *handler)
	{
		m_FreeMenuHandlers.push(handler);
	}

private:
	HandleType_t m_PanelType;
	CStack<CPanelHandler *> m_FreePanelHandlers;
	CStack<CMenuHandler *> m_FreeMenuHandlers;
	CVector<CPanelHandler *> m_PanelHandlers;
} g_MenuHelpers;

/**
 * BASIC PANEL HANDLER WRAPPER
 */

void CPanelHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
{
	if (m_pFunc)
	{
		m_pFunc->PushCell(BAD_HANDLE);
		m_pFunc->PushCell(MenuAction_Cancel);
		m_pFunc->PushCell(client);
		m_pFunc->PushCell(reason);
		m_pFunc->Execute(NULL);
	}
	g_MenuHelpers.FreePanelHandler(this);
}

void CPanelHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
{
	if (m_pFunc)
	{
		m_pFunc->PushCell(BAD_HANDLE);
		m_pFunc->PushCell(MenuAction_Select);
		m_pFunc->PushCell(client);
		m_pFunc->PushCell(item);
		m_pFunc->Execute(NULL);
	}
	g_MenuHelpers.FreePanelHandler(this);
}

/**
 * MENU HANDLER WRAPPER
 */
CMenuHandler::CMenuHandler(IPluginFunction *pBasic, int flags) : 
	m_pBasic(pBasic), m_Flags(flags)
{
	/* :TODO: We can probably cache the handle ahead of time */
}

void CMenuHandler::OnMenuStart(IBaseMenu *menu)
{
	if ((m_Flags & (int)MenuAction_Start) == (int)MenuAction_Start)
	{
		m_pBasic->PushCell(menu->GetHandle());
		m_pBasic->PushCell(MenuAction_Start);
		m_pBasic->PushCell(0);
		m_pBasic->PushCell(0);
		m_pBasic->Execute(NULL);
	}
}

void CMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *panel)
{
	if ((m_Flags & (int)MenuAction_Display) == (int)MenuAction_Display)
	{
		HandleSecurity sec;
		sec.pIdentity = m_pBasic->GetParentContext()->GetIdentity();
		sec.pOwner = g_pCoreIdent;
	
		HandleAccess access;
		g_HandleSys.InitAccessDefaults(NULL, &access);
		access.access[HandleAccess_Delete] = HANDLE_RESTRICT_IDENTITY|HANDLE_RESTRICT_OWNER;

		Handle_t hndl =  g_HandleSys.CreateHandleEx(g_MenuHelpers.GetPanelType(), panel, &sec, &access, NULL);

		m_pBasic->PushCell(menu->GetHandle());
		m_pBasic->PushCell(MenuAction_Display);
		m_pBasic->PushCell(client);
		m_pBasic->PushCell(hndl);
		m_pBasic->Execute(NULL);

		g_HandleSys.FreeHandle(hndl, &sec);
	}
}

void CMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
{
	m_pBasic->PushCell(menu->GetHandle());
	m_pBasic->PushCell(MenuAction_Select);
	m_pBasic->PushCell(client);
	m_pBasic->PushCell(item);
	m_pBasic->Execute(NULL);
}

void CMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
{
	m_pBasic->PushCell(menu->GetHandle());
	m_pBasic->PushCell(MenuAction_Cancel);
	m_pBasic->PushCell(client);
	m_pBasic->PushCell(reason);
	m_pBasic->Execute(NULL);
}

void CMenuHandler::OnMenuEnd(IBaseMenu *menu)
{
	m_pBasic->PushCell(menu->GetHandle());
	m_pBasic->PushCell(MenuAction_End);
	m_pBasic->PushCell(0);
	m_pBasic->PushCell(0);
	m_pBasic->Execute(NULL);
}

void CMenuHandler::OnMenuDestroy(IBaseMenu *menu)
{
	g_MenuHelpers.FreeMenuHandler(this);
}

/**
 * INLINE FUNCTIONS FOR NATIVES
 */

inline Handle_t MakePanelHandle(IMenuPanel *panel, IPluginContext *pContext)
{
	return g_HandleSys.CreateHandle(g_MenuHelpers.GetPanelType(), panel, pContext->GetIdentity(), g_pCoreIdent, NULL);
}

inline HandleError ReadPanelHandle(Handle_t hndl, IMenuPanel **panel)
{
	HandleSecurity sec;
	sec.pIdentity = g_pCoreIdent;
	sec.pOwner = NULL;
	return g_HandleSys.ReadHandle(hndl, g_MenuHelpers.GetPanelType(), &sec, (void **)panel);
}

inline IMenuStyle *GetStyleFromCell(cell_t cell)
{
	enum MenuStyle
	{
		MenuStyle_Default = 0,		/**< The "default" menu style for the mod */
		MenuStyle_Valve = 1,		/**< The Valve provided menu style (Used on HL2DM) */
		MenuStyle_Radio = 2,		/**< The simpler menu style commonly used on CS:S */
	};

	if (cell == MenuStyle_Valve)
	{
		return &g_ValveMenuStyle;
	} else if (cell == MenuStyle_Radio
			   && g_RadioMenuStyle.IsSupported())
	{
		return &g_RadioMenuStyle;
	}

	return g_Menus.GetDefaultStyle();
}

/***********************************
 **** NATIVE DEFINITIONS ***********
 ***********************************/

static cell_t CreateMenu(IPluginContext *pContext, const cell_t *params)
{
	IMenuStyle *style = g_Menus.GetDefaultStyle();
	IPluginFunction *pFunction;

	if ((pFunction=pContext->GetFunctionById(params[1])) == NULL)
	{
		return pContext->ThrowNativeError("Function id %x is invalid", params[1]);
	}

	CMenuHandler *handler = g_MenuHelpers.GetMenuHandler(pFunction, params[2]);
	IBaseMenu *menu = style->CreateMenu(handler, pContext->GetIdentity());

	Handle_t hndl = menu->GetHandle();
	if (!hndl)
	{
		menu->Destroy();
		return BAD_HANDLE;
	}

	return hndl;
}

static cell_t DisplayMenu(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IBaseMenu *menu;

	if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	return menu->Display(params[2], params[3]) ? 1 : 0;
}

static cell_t AddMenuItem(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IBaseMenu *menu;

	if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	char *info;
	ItemDrawInfo dr;

	pContext->LocalToString(params[2], &info);
	pContext->LocalToString(params[3], (char **)&dr.display);
	dr.style = params[4];

	return menu->AppendItem(info, dr) ? 1 : 0;
}

static cell_t InsertMenuItem(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IBaseMenu *menu;

	if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	char *info;
	ItemDrawInfo dr;

	pContext->LocalToString(params[3], &info);
	pContext->LocalToString(params[4], (char **)&dr.display);
	dr.style = params[5];

	return menu->InsertItem(params[2], info, dr) ? 1 : 0;
}

static cell_t RemoveMenuItem(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IBaseMenu *menu;

	if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	return menu->RemoveItem(params[2]) ? 1 : 0;
}

static cell_t RemoveAllMenuItems(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IBaseMenu *menu;

	if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	menu->RemoveAllItems();

	return 1;
}

static cell_t GetMenuItem(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IBaseMenu *menu;

	if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	ItemDrawInfo dr;
	const char *info;

	if ((info=menu->GetItemInfo(params[2], &dr)) == NULL)
	{
		return 0;
	}

	pContext->StringToLocalUTF8(params[3], params[4], info, NULL);
	pContext->StringToLocalUTF8(params[6], params[7], dr.display ? dr.display : "", NULL);

	cell_t *addr;
	pContext->LocalToPhysAddr(params[5], &addr);
	*addr = dr.style;

	return 1;
}

static cell_t SetMenuPagination(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IBaseMenu *menu;

	if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	return menu->SetPagination(params[2]) ? 1 : 0;
}

static cell_t GetMenuPagination(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IBaseMenu *menu;

	if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	return menu->GetPagination();
}

static cell_t GetMenuItemCount(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IBaseMenu *menu;

	if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	return menu->GetItemCount();
}

static cell_t SetMenuTitle(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IBaseMenu *menu;

	if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	char buffer[1024];
	g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);

	menu->SetDefaultTitle(buffer);

	return 1;
}

static cell_t CreatePanelFromMenu(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IBaseMenu *menu;

	if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	IMenuPanel *panel = menu->CreatePanel();
	hndl = MakePanelHandle(panel, pContext);
	if (!hndl)
	{
		panel->DeleteThis();
	}

	return hndl;
}

static cell_t GetMenuExitButton(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IBaseMenu *menu;

	if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	return menu->GetExitButton() ? 1 : 0;
}

static cell_t SetMenuExitButton(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IBaseMenu *menu;

	if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	return menu->SetExitButton(params[2] ? true : false) ? 1 : 0;
}

static cell_t CancelMenu(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IBaseMenu *menu;

	if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	menu->Cancel();

	return 1;
}

static cell_t GetMenuStyle(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IBaseMenu *menu;

	if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	return menu->GetDrawStyle()->GetHandle();
}

static cell_t GetMenuStyleHandle(IPluginContext *pContext, const cell_t *params)
{
	IMenuStyle *style = GetStyleFromCell(params[1]);
	if (!style)
	{
		return BAD_HANDLE;
	}

	return style->GetHandle();
}

static cell_t CreatePanel(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IMenuStyle *style;

	if (hndl != 0)
	{
		if ((err=g_Menus.ReadStyleHandle(params[1], &style)) != HandleError_None)
		{
			return pContext->ThrowNativeError("MenuStyle handle %x is invalid (error %d)", hndl, err);
		}
	} else {
		style = g_Menus.GetDefaultStyle();
	}

	IMenuPanel *panel = style->CreatePanel();

	hndl = MakePanelHandle(panel, pContext);
	if (!hndl)
	{
		panel->DeleteThis();
		return BAD_HANDLE;
	}

	return hndl;
}

static cell_t CreateMenuEx(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IMenuStyle *style;

	if (hndl != 0)
	{
		if ((err=g_Menus.ReadStyleHandle(params[1], &style)) != HandleError_None)
		{
			return pContext->ThrowNativeError("MenuStyle handle %x is invalid (error %d)", hndl, err);
		}
	} else {
		style = g_Menus.GetDefaultStyle();
	}

	IPluginFunction *pFunction;
	if ((pFunction=pContext->GetFunctionById(params[2])) == NULL)
	{
		return pContext->ThrowNativeError("Function id %x is invalid", params[2]);
	}

	CMenuHandler *handler = g_MenuHelpers.GetMenuHandler(pFunction, params[3]);

	IBaseMenu *pMenu = style->CreateMenu(handler, pContext->GetIdentity());
	hndl = pMenu->GetHandle();
	if (!hndl)
	{
		pMenu->Destroy();
		return BAD_HANDLE;
	}

	return hndl;
}

static cell_t GetClientMenu(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[2];
	HandleError err;
	IMenuStyle *style;

	if (hndl != 0)
	{
		if ((err=g_Menus.ReadStyleHandle(params[1], &style)) != HandleError_None)
		{
			return pContext->ThrowNativeError("MenuStyle handle %x is invalid (error %d)", hndl, err);
		}
	} else {
		style = g_Menus.GetDefaultStyle();
	}

	return style->GetClientMenu(params[1], NULL);
}

static cell_t CancelClientMenu(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[3];
	HandleError err;
	IMenuStyle *style;

	if (hndl != 0)
	{
		if ((err=g_Menus.ReadStyleHandle(params[1], &style)) != HandleError_None)
		{
			return pContext->ThrowNativeError("MenuStyle handle %x is invalid (error %d)", hndl, err);
		}
	} else {
		style = g_Menus.GetDefaultStyle();
	}

	return style->CancelClientMenu(params[1], params[2] ? true : false) ? 1 : 0;
}

static cell_t GetMaxPageItems(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IMenuStyle *style;

	if (hndl != 0)
	{
		if ((err=g_Menus.ReadStyleHandle(params[1], &style)) != HandleError_None)
		{
			return pContext->ThrowNativeError("MenuStyle handle %x is invalid (error %d)", hndl, err);
		}
	} else {
		style = g_Menus.GetDefaultStyle();
	}

	return style->GetMaxPageItems();
}

static cell_t GetPanelStyle(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IMenuPanel *panel;

	if ((err=ReadPanelHandle(hndl, &panel)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	return panel->GetParentStyle()->GetHandle();
}

static cell_t SetPanelTitle(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IMenuPanel *panel;

	if ((err=ReadPanelHandle(hndl, &panel)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	char *text;
	pContext->LocalToString(params[2], &text);

	panel->DrawTitle(text, params[3] ? true : false);

	return 1;
}

static cell_t DrawPanelItem(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IMenuPanel *panel;

	if ((err=ReadPanelHandle(hndl, &panel)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	ItemDrawInfo dr;
	pContext->LocalToString(params[2], (char **)&dr.display);
	dr.style = params[3];

	return panel->DrawItem(dr);
}

static cell_t DrawPanelText(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IMenuPanel *panel;

	if ((err=ReadPanelHandle(hndl, &panel)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	char *text;
	pContext->LocalToString(params[2], &text);

	return panel->DrawRawLine(text) ? 1 : 0;
}

static cell_t CanPanelDrawFlags(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IMenuPanel *panel;

	if ((err=ReadPanelHandle(hndl, &panel)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	return panel->CanDrawItem(params[2]);
}

static cell_t SendPanelToClient(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IMenuPanel *panel;

	if ((err=ReadPanelHandle(hndl, &panel)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	IPluginFunction *pFunction;
	if ((pFunction=pContext->GetFunctionById(params[3])) == NULL)
	{
		return pContext->ThrowNativeError("Function id %x is invalid", params[3]);
	}

	CPanelHandler *handler = g_MenuHelpers.GetPanelHandler(pFunction);
	if (!panel->SendDisplay(params[2], handler, params[4]))
	{
		g_MenuHelpers.FreePanelHandler(handler);
	}

	return 1;
}

static cell_t SetPanelKeys(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IMenuPanel *panel;

	if ((err=ReadPanelHandle(hndl, &panel)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	return panel->SetSelectableKeys(params[2]);
}

static cell_t GetPanelCurrentKey(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IMenuPanel *panel;

	if ((err=ReadPanelHandle(hndl, &panel)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	return panel->GetCurrentKey();
}

static cell_t SetPanelCurrentKey(IPluginContext *pContext, const cell_t *params)
{
	Handle_t hndl = (Handle_t)params[1];
	HandleError err;
	IMenuPanel *panel;

	if ((err=ReadPanelHandle(hndl, &panel)) != HandleError_None)
	{
		return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err);
	}

	return panel->SetCurrentKey(params[2]) ? 1 : 0;
}

REGISTER_NATIVES(menuNatives)
{
	{"AddMenuItem",				AddMenuItem},
	{"CanPanelDrawFlags",		CanPanelDrawFlags},
	{"CancelClientMenu",		CancelClientMenu},
	{"CancelMenu",				CancelMenu},
	{"CreateMenu",				CreateMenu},
	{"CreateMenuEx",			CreateMenuEx},
	{"CreatePanel",				CreatePanel},
	{"CreatePanelFromMenu",		CreatePanelFromMenu},\
	{"DisplayMenu",				DisplayMenu},
	{"DrawPanelItem",			DrawPanelItem},
	{"DrawPanelText",			DrawPanelText},
	{"GetClientMenu",			GetClientMenu},
	{"GetMaxPageItems",			GetMaxPageItems},
	{"GetMenuExitButton",		GetMenuExitButton},
	{"GetMenuItem",				GetMenuItem},
	{"GetMenuItemCount",		GetMenuItemCount},
	{"GetMenuPagination",		GetMenuPagination},
	{"GetMenuStyle",			GetMenuStyle},
	{"GetMenuStyleHandle",		GetMenuStyleHandle},
	{"GetPanelCurrentKey",		GetPanelCurrentKey},
	{"GetPanelStyle",			GetPanelStyle},
	{"InsertMenuItem",			InsertMenuItem},
	{"RemoveAllMenuItems",		RemoveAllMenuItems},
	{"RemoveMenuItem",			RemoveMenuItem},
	{"SendPanelToClient",		SendPanelToClient},
	{"SetMenuExitButton",		SetMenuExitButton},
	{"SetMenuPagination",		SetMenuPagination},
	{"SetMenuTitle",			SetMenuTitle},
	{"SetPanelCurrentKey",		SetPanelCurrentKey},
	{"SetPanelTitle",			SetPanelTitle},
	{"SetPanelKeys",			SetPanelKeys},
	{NULL,						NULL},
};